Initial project import
This commit is contained in:
@@ -0,0 +1,144 @@
|
||||
"""
|
||||
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
|
||||
Reference in New Issue
Block a user