| """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 == [] |
|
|