import csv import io import json import sys import tempfile import unittest from pathlib import Path from unittest.mock import MagicMock sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "src")) import review_export def _make_db(rows): db = MagicMock() db.fetchall.return_value = rows return db class Story07ReviewExportTests(unittest.TestCase): _ROWS = [ { "video_id": 1, "file_path": "/data/input/a.mp4", "confidence_score": 0.62, "routing_decision": "REVIEW", "model_version": "v1.0", "ground_truth": True, "annotated_at": None, "notes": "ok", "confidence_scores": json.dumps([0.60, 0.62, 0.65]), }, ] def test_fetch_annotated_passes_correct_where_clause(self): db = _make_db(self._ROWS) review_export.fetch_annotated(db, annotated_only=True, model_version="v1.0") call_args = db.fetchall.call_args sql = call_args[0][0] self.assertIn("rq.annotated = TRUE", sql) self.assertIn("v.model_version = %s", sql) def test_fetch_annotated_normalises_confidence_scores(self): db = _make_db(self._ROWS) records = review_export.fetch_annotated(db) self.assertIsInstance(records[0]["contributing_frames"], list) self.assertEqual(records[0]["contributing_frames"], [0.60, 0.62, 0.65]) def test_fetch_annotated_handles_null_confidence_scores(self): rows = [{**self._ROWS[0], "confidence_scores": None}] db = _make_db(rows) records = review_export.fetch_annotated(db) self.assertEqual(records[0]["contributing_frames"], []) def test_export_json_writes_valid_utf8_file(self): with tempfile.TemporaryDirectory() as tmpdir: records = review_export.fetch_annotated(_make_db(self._ROWS)) path = review_export.export_json(records, f"{tmpdir}/out.json") loaded = json.loads(Path(path).read_text(encoding="utf-8")) self.assertEqual(len(loaded), 1) self.assertEqual(loaded[0]["video_id"], 1) self.assertTrue(loaded[0]["ground_truth"]) def test_export_csv_writes_valid_csv(self): with tempfile.TemporaryDirectory() as tmpdir: records = review_export.fetch_annotated(_make_db(self._ROWS)) path = review_export.export_csv(records, f"{tmpdir}/out.csv") content = Path(path).read_text(encoding="utf-8") reader = csv.DictReader(io.StringIO(content)) rows = list(reader) self.assertEqual(len(rows), 1) self.assertEqual(rows[0]["routing_decision"], "REVIEW") # contributing_frames should be a JSON string in CSV frames = json.loads(rows[0]["contributing_frames"]) self.assertEqual(frames, [0.60, 0.62, 0.65]) def test_export_csv_empty_returns_empty_file(self): with tempfile.TemporaryDirectory() as tmpdir: path = review_export.export_csv([], f"{tmpdir}/empty.csv") self.assertEqual(Path(path).read_text(), "") def test_ground_truth_filter_appears_in_query(self): db = _make_db([]) review_export.fetch_annotated(db, ground_truth=False) sql = db.fetchall.call_args[0][0] self.assertIn("rq.ground_truth = %s", sql) class Story07AppSyntaxTest(unittest.TestCase): def test_app_module_compiles(self): """Ensure ui/app.py has no syntax errors.""" app_path = Path(__file__).resolve().parents[1] / "ui" / "app.py" source = app_path.read_text(encoding="utf-8") compile(source, str(app_path), "exec") if __name__ == "__main__": unittest.main()