import json from contextlib import nullcontext from pathlib import Path import tempfile from types import SimpleNamespace import unittest from unittest.mock import Mock, patch from car_tracker.diagnostics import capture_403 from car_tracker.collect import collect_browser class DiagnosticTests(unittest.TestCase): def setUp(self): self.root = Path(self.enterContext(tempfile.TemporaryDirectory())) / "diagnostics/http-403" request = SimpleNamespace(url="https://www.leboncoin.fr/ad/voitures/123", method="GET", redirected_from=None, all_headers=lambda: {"cookie": "private-cookie"}) self.response = SimpleNamespace(status=403, url=request.url, request=request, all_headers=lambda: {"set-cookie": "private-response-cookie"}, body=lambda: b"Forbidden synthetic response") self.page = SimpleNamespace(url=request.url, content=lambda: "synthetic DOM", context=SimpleNamespace(cookies=Mock(return_value=[{"name": "datadome", "value": "secret"}])), screenshot=Mock(return_value=b"synthetic-png")) self.browser = SimpleNamespace(version="synthetic-browser") self.enterContext(patch("car_tracker.diagnostics.version", return_value="synthetic-playwright")) def capture(self): return capture_403(self.root, self.response, self.page, self.browser, {"delay_seconds": 5}) def test_capture_is_private_and_does_not_navigate(self): report = self.capture() self.assertFalse(report["errors"]) self.assertEqual(self.root.stat().st_mode & 0o777, 0o700) self.assertEqual(self.root.parent.stat().st_mode & 0o777, 0o700) for path in self.root.iterdir(): self.assertEqual(path.stat().st_mode & 0o777, 0o600) self.assertEqual(json.loads((self.root / "request-headers.json").read_text())["cookie"], "private-cookie") self.assertEqual((self.root / "response-body.html").read_bytes(), self.response.body()) self.page.context.cookies.assert_called_once_with(["https://www.leboncoin.fr"]) self.page.screenshot.assert_called_once_with(timeout=3000, animations="allow") self.assertNotIn("secret", json.dumps(report)) def test_one_capture_failure_does_not_prevent_other_artifacts(self): self.response.body = Mock(side_effect=RuntimeError("private body with token")) report = self.capture() self.assertEqual(report["errors"], {"response-body.html": "RuntimeError"}) self.assertTrue((self.root / "viewport.png").exists()) self.assertNotIn("token", (self.root / "capture.json").read_text()) def test_documents_are_disk_bounded_and_truncation_is_recorded(self): with patch("car_tracker.diagnostics.MAX_DOCUMENT_BYTES", 10): report = self.capture() self.assertEqual((self.root / "response-body.html").stat().st_size, 10) self.assertIn("response-body.html", report["truncated"]) self.assertIn("rendered-page.html", report["truncated"]) def test_existing_capture_is_not_overwritten(self): self.capture() with self.assertRaises(FileExistsError): self.capture() def test_browser_403_captures_before_close_and_never_retries(self): self.page.goto = Mock(return_value=self.response) context = self.page.context context.new_page = Mock(return_value=self.page) self.browser.new_context = Mock(return_value=context) self.browser.close = Mock() def body_before_close(): self.browser.close.assert_not_called() return b"synthetic forbidden response" self.response.body = body_before_close playwright = SimpleNamespace(chromium=SimpleNamespace(launch=Mock(return_value=self.browser))) args = SimpleNamespace(headed=True, delay=5, max_pages=1, max_details=None, search_only=False, output=self.root.parent.parent, searches=[{"family": "synthetic", "text": "synthetic"}]) module = SimpleNamespace(sync_playwright=lambda: nullcontext(playwright)) with patch("playwright_stealth.Stealth") as stealth, patch.dict( "sys.modules", {"playwright.sync_api": module}, ): stealth.return_value.use_sync.side_effect = lambda manager: manager path, status = collect_browser(args) manifest = json.loads(path.read_text()) self.assertEqual(status, "failed") self.assertEqual(manifest["diagnostics"]["capture_status"], "complete") self.assertTrue((path.parent / "diagnostics/http-403/response-body.html").exists()) metadata = json.loads((path.parent / "diagnostics/http-403/metadata.json").read_text()) self.assertTrue(metadata["settings"]["stealth"]) self.page.goto.assert_called_once() self.browser.close.assert_called_once()