""" tests/test_config.py — Smoke tests for config loading and model integrity. Run with: pytest tests/test_config.py -v """ from pathlib import Path import pytest from src.core.config import load_config, AppConfig from src.core.models import ( Scene, TrailerBeat, MatchResult, VibeHit, EditClip, EditTimeline, BeatType, DialogueLine, ) CONFIG_PATH = Path(__file__).parents[1] / "config.toml" # --------------------------------------------------------------------------- # Config loader # --------------------------------------------------------------------------- class TestConfigLoader: def test_loads_without_error(self) -> None: cfg = load_config(CONFIG_PATH) assert isinstance(cfg, AppConfig) def test_project_meta(self) -> None: cfg = load_config(CONFIG_PATH) assert cfg.version == "2.0.0" assert cfg.log_level in ("DEBUG", "INFO", "WARNING", "ERROR") def test_cv_thresholds_in_range(self) -> None: cfg = load_config(CONFIG_PATH) ds = cfg.cv.deep_scan assert 0.0 < ds.match_threshold < 1.0 assert ds.coarse_step_seconds > 0 def test_vibe_check_crop_fractions(self) -> None: cfg = load_config(CONFIG_PATH) vc = cfg.cv.vibe_check assert 0.0 < vc.crop_top_fraction < 1.0 assert 0.0 < vc.crop_bottom_fraction < 1.0 assert vc.crop_top_fraction + vc.crop_bottom_fraction < 1.0 def test_missing_config_raises(self, tmp_path: Path) -> None: with pytest.raises(FileNotFoundError): load_config(tmp_path / "nonexistent.toml") def test_paths_are_path_objects(self) -> None: cfg = load_config(CONFIG_PATH) assert isinstance(cfg.paths.source_movie, Path) assert isinstance(cfg.paths.reference_trailer, Path) # --------------------------------------------------------------------------- # Data models — construction & properties # --------------------------------------------------------------------------- class TestSceneModel: def test_duration(self) -> None: s = Scene( scene_id=0, source_path=Path("dummy.mp4"), start_s=10.0, end_s=25.5, start_frame=240, end_frame=612, ) assert s.duration_s == pytest.approx(15.5) assert s.midpoint_s == pytest.approx(17.75) def test_immutable(self) -> None: s = Scene( scene_id=0, source_path=Path("x.mp4"), start_s=0.0, end_s=1.0, start_frame=0, end_frame=24, ) with pytest.raises(Exception): # FrozenInstanceError s.scene_id = 99 # type: ignore[misc] class TestTrailerBeatModel: def test_beat_type_default(self) -> None: b = TrailerBeat( beat_id=0, trailer_path=Path("trailer.mp4"), start_s=0.0, end_s=3.0, start_frame=0, end_frame=72, ) assert b.beat_type == BeatType.UNKNOWN class TestMatchResultModel: def test_duration_computed(self) -> None: mr = MatchResult( beat_id=0, scene_id=3, source_path=Path("movie.mp4"), in_point_s=120.0, out_point_s=123.5, in_point_frame=2880, match_score=0.87, ) assert mr.duration_s == pytest.approx(3.5) def test_repr_contains_key_info(self) -> None: mr = MatchResult( beat_id=1, scene_id=7, source_path=Path("movie.mp4"), in_point_s=60.0, out_point_s=63.0, in_point_frame=1440, match_score=0.91, ) r = repr(mr) assert "beat=1" in r assert "scene=7" in r class TestEditTimeline: def _make_clip(self, idx: int, t_start: float, t_end: float) -> EditClip: beat = TrailerBeat( beat_id=idx, trailer_path=Path("t.mp4"), start_s=t_start, end_s=t_end, start_frame=0, end_frame=1, ) match = MatchResult( beat_id=idx, scene_id=0, source_path=Path("m.mp4"), in_point_s=0.0, out_point_s=t_end - t_start, in_point_frame=0, match_score=0.9, ) return EditClip( clip_index=idx, beat=beat, match=match, timeline_start_s=t_start, timeline_end_s=t_end, ) def test_total_duration(self) -> None: clips = (self._make_clip(0, 0.0, 5.0), self._make_clip(1, 5.0, 9.0)) tl = EditTimeline(title="Test Trailer", frame_rate=23.976, clips=clips) assert tl.total_duration_s == pytest.approx(9.0) assert tl.clip_count == 2 def test_empty_timeline(self) -> None: tl = EditTimeline(title="Empty", frame_rate=24.0, clips=()) assert tl.total_duration_s == 0.0