from argparse import Namespace import io from pathlib import Path from tempfile import TemporaryDirectory import unittest from unittest.mock import patch from car_tracker.collect import collect_browser class BrowserCleanupTests(unittest.TestCase): def setUp(self): temporary = TemporaryDirectory() self.addCleanup(temporary.cleanup) self.path = Path(temporary.name) / "manifest.json" self.path.write_text('{"status":"complete"}') self.args = Namespace(headed=False, searches=[], output=self.path.parent, max_pages=1, delay=1, search_only=True, max_details=None) self.playwright = self.enterContext(patch("playwright.sync_api.sync_playwright")) self.browser = self.playwright.return_value.__enter__.return_value.chromium.launch.return_value self.collect = self.enterContext(patch("car_tracker.collect.collect", return_value=(self.path, "complete"))) self.stderr = self.enterContext(patch("sys.stderr", new_callable=io.StringIO)) def test_browser_close_failure_retains_finalized_result(self): self.browser.close.side_effect = RuntimeError("SYNTHETIC_SECRET") self.assertEqual(collect_browser(self.args), (self.path, "complete")) self.assertTrue((self.path.parent / "browser_cleanup.json").exists()) self.assertNotIn("SYNTHETIC_SECRET", self.stderr.getvalue()) def test_driver_cleanup_failure_retains_finalized_result(self): self.playwright.return_value.__exit__.side_effect = RuntimeError("cleanup failed") self.assertEqual(collect_browser(self.args), (self.path, "complete")) def test_collection_failure_is_not_suppressed(self): self.collect.side_effect = OSError("write failed") with self.assertRaises(OSError): collect_browser(self.args) def test_cleanup_interruption_remains_cancellation_with_manifest_path(self): self.browser.close.side_effect = KeyboardInterrupt with self.assertRaises(KeyboardInterrupt): collect_browser(self.args) self.assertIn(str(self.path), self.stderr.getvalue())