Spaces:
Running on Zero
Running on Zero
| """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 == [] | |