"""Tests for formscout/types.py — contract validation.""" import pytest from formscout.types import ( IngestResult, SegmentResult, Pose2DResult, Body3DResult, MovementResult, BiomechFeatures, ScoreResult, RetrievalResult, JudgeResult, ReportResult, PipelineState, ) class TestIngestResult: def test_frozen(self): r = IngestResult(frames=[], fps=30.0, duration=2.0, n_people=1, width=1920, height=1080) with pytest.raises(Exception): r.fps = 60.0 def test_defaults(self): r = IngestResult(frames=[], fps=30.0, duration=0.0, n_people=0, width=0, height=0) assert r.confidence == 1.0 assert r.notes == "" class TestMovementResult: def test_valid_tests(self): r = MovementResult(test_name="deep_squat", side="na", confidence=0.9) assert r.test_name == "deep_squat" def test_invalid_test_raises(self): with pytest.raises(ValueError, match="test_name"): MovementResult(test_name="jumping_jacks", side="na", confidence=0.5) def test_invalid_side_raises(self): with pytest.raises(ValueError, match="side"): MovementResult(test_name="deep_squat", side="both", confidence=0.5) class TestBiomechFeatures: def test_valid_views(self): f = BiomechFeatures( test_name="deep_squat", view="2d", side="na", angles={}, alignments={}, symmetry_delta=None, timing={}, confidence=0.8, ) assert f.view == "2d" def test_invalid_view_raises(self): with pytest.raises(ValueError, match="view"): BiomechFeatures( test_name="deep_squat", view="4d", side="na", angles={}, alignments={}, symmetry_delta=None, timing={}, confidence=0.8, ) class TestScoreResult: def test_valid_score(self): r = ScoreResult(score=3, rationale="good", confidence=0.9) assert r.score == 3 def test_invalid_score_raises(self): with pytest.raises(ValueError): ScoreResult(score=4, rationale="bad", confidence=0.9) def test_score_minus_one_invalid_when_not_needs_human(self): with pytest.raises(ValueError): ScoreResult(score=-1, rationale="x", confidence=0.5) class TestJudgeResult: def test_needs_human_score_must_be_none(self): with pytest.raises(ValueError): JudgeResult( score=2, rationale="pain", compensation_tags=[], corrective_hint="", confidence=0.5, needs_human=True, ) def test_needs_human_with_none_score(self): r = JudgeResult( score=None, rationale="pain observed", compensation_tags=[], corrective_hint="", confidence=0.5, needs_human=True, ) assert r.needs_human is True assert r.score is None def test_valid_score(self): r = JudgeResult( score=2, rationale="ok", compensation_tags=["heel_rise"], corrective_hint="work on ankle mobility", confidence=0.85, ) assert r.score == 2 class TestPipelineState: def test_mutable(self): s = PipelineState(video_path="/tmp/test.mp4") s.ingest = IngestResult(frames=[], fps=30.0, duration=1.0, n_people=1, width=640, height=480) assert s.ingest is not None def test_defaults(self): s = PipelineState(video_path="test.mp4") assert s.ingest is None assert s.errors == [] assert s.warnings == []