Spaces:
Running
Running
| """Tests for the case data schema.""" | |
| import json | |
| from pathlib import Path | |
| import pytest | |
| from pydantic import ValidationError | |
| from turnabout.core.schema import ( | |
| CaseData, | |
| Character, | |
| Contradiction, | |
| CourtProceedings, | |
| CourtRound, | |
| DialogueLine, | |
| DialogueTree, | |
| EvidenceAcquisition, | |
| EvidenceItem, | |
| EvidencePresentResponse, | |
| Examinable, | |
| InvestigationGate, | |
| Location, | |
| Statement, | |
| Testimony, | |
| WinCondition, | |
| ) | |
| CASES_DIR = Path(__file__).parent.parent / "turnabout" / "cases" | |
| def _make_minimal_case(**overrides) -> dict: | |
| base = { | |
| "id": "test_001", | |
| "title": "Test Case", | |
| "difficulty": "easy", | |
| "description": "A test case.", | |
| "characters": [ | |
| {"id": "defendant", "name": "D", "description": "d", "role": "defendant"}, | |
| {"id": "witness1", "name": "W", "description": "w", "role": "witness"}, | |
| ], | |
| "evidence": [ | |
| { | |
| "id": "ev1", | |
| "name": "E1", | |
| "item_type": "document", | |
| "description": "desc", | |
| "detail": "detail", | |
| "source": "pre_given", | |
| } | |
| ], | |
| "court": { | |
| "penalty_limit": 5, | |
| "rounds": [ | |
| { | |
| "witness_id": "witness1", | |
| "order": 0, | |
| "initial_testimony_id": "t1", | |
| "testimonies": [ | |
| { | |
| "id": "t1", | |
| "title": "Testimony", | |
| "witness_id": "witness1", | |
| "preamble": "The witness testifies.", | |
| "statements": [ | |
| { | |
| "id": "s1", | |
| "text": "I saw something.", | |
| "press_response": "Well...", | |
| "contradiction": { | |
| "evidence_id": "ev1", | |
| "explanation": "This contradicts!", | |
| "is_primary": True, | |
| }, | |
| } | |
| ], | |
| } | |
| ], | |
| } | |
| ], | |
| "win_condition": {"required_contradiction_ids": ["s1"]}, | |
| }, | |
| } | |
| base.update(overrides) | |
| return base | |
| class TestMinimalCase: | |
| def test_parse_minimal(self): | |
| case = CaseData(**_make_minimal_case()) | |
| assert case.id == "test_001" | |
| assert case.difficulty == "easy" | |
| assert len(case.characters) == 2 | |
| assert len(case.evidence) == 1 | |
| assert case.court.penalty_limit == 5 | |
| def test_get_character(self): | |
| case = CaseData(**_make_minimal_case()) | |
| assert case.get_character("defendant") is not None | |
| assert case.get_character("nonexistent") is None | |
| def test_get_evidence(self): | |
| case = CaseData(**_make_minimal_case()) | |
| assert case.get_evidence("ev1") is not None | |
| assert case.get_evidence("nonexistent") is None | |
| def test_get_testimony(self): | |
| case = CaseData(**_make_minimal_case()) | |
| assert case.get_testimony("t1") is not None | |
| assert case.get_testimony("nonexistent") is None | |
| def test_get_statement(self): | |
| case = CaseData(**_make_minimal_case()) | |
| s = case.get_statement("s1") | |
| assert s is not None | |
| assert s.contradiction is not None | |
| assert s.contradiction.evidence_id == "ev1" | |
| def test_pre_given_evidence(self): | |
| case = CaseData(**_make_minimal_case()) | |
| pre = case.get_pre_given_evidence() | |
| assert len(pre) == 1 | |
| assert pre[0].id == "ev1" | |
| def test_required_contradictions(self): | |
| case = CaseData(**_make_minimal_case()) | |
| stmts = case.get_required_contradiction_statements() | |
| assert len(stmts) == 1 | |
| assert stmts[0].id == "s1" | |
| class TestValidation: | |
| def test_invalid_difficulty(self): | |
| with pytest.raises(ValidationError): | |
| CaseData(**_make_minimal_case(difficulty="medium")) | |
| def test_investigation_evidence_requires_acquisition(self): | |
| data = _make_minimal_case() | |
| data["evidence"].append( | |
| { | |
| "id": "ev2", | |
| "name": "E2", | |
| "item_type": "physical", | |
| "description": "d", | |
| "detail": "d", | |
| "source": "investigation", | |
| "acquisition": None, | |
| } | |
| ) | |
| with pytest.raises(ValidationError, match="investigation evidence must have acquisition"): | |
| CaseData(**data) | |
| def test_investigation_evidence_with_acquisition_ok(self): | |
| data = _make_minimal_case() | |
| data["locations"] = [ | |
| { | |
| "id": "loc1", | |
| "name": "L1", | |
| "description": "d", | |
| "examinables": [{"id": "obj1", "name": "O1", "description": "d"}], | |
| } | |
| ] | |
| data["evidence"].append( | |
| { | |
| "id": "ev2", | |
| "name": "E2", | |
| "item_type": "physical", | |
| "description": "d", | |
| "detail": "d", | |
| "source": "investigation", | |
| "acquisition": { | |
| "method": "examine", | |
| "location_id": "loc1", | |
| "target_id": "obj1", | |
| }, | |
| } | |
| ) | |
| case = CaseData(**data) | |
| assert len(case.evidence) == 2 | |
| class TestStolenPrototype: | |
| def case(self): | |
| path = CASES_DIR / "stolen_prototype.json" | |
| if not path.exists(): | |
| pytest.skip("stolen_prototype.json not yet created") | |
| with open(path) as f: | |
| data = json.load(f) | |
| return CaseData(**data) | |
| def test_loads(self, case): | |
| assert case.id == "stolen_prototype" | |
| assert case.title == "The Stolen Prototype" | |
| def test_characters(self, case): | |
| assert len(case.characters) == 5 | |
| roles = {c.role for c in case.characters} | |
| assert "defendant" in roles | |
| assert "witness" in roles | |
| def test_evidence_count(self, case): | |
| assert len(case.evidence) == 7 | |
| pre_given = case.get_pre_given_evidence() | |
| assert len(pre_given) == 2 | |
| investigation = [e for e in case.evidence if e.source == "investigation"] | |
| assert len(investigation) == 5 | |
| def test_locations(self, case): | |
| assert len(case.locations) == 5 | |
| hale_office = case.get_location("hale_office") | |
| assert hale_office is not None | |
| assert not hale_office.available_from_start | |
| assert "hale_suspicion" in hale_office.unlock_prerequisites | |
| def test_court_structure(self, case): | |
| assert case.court.penalty_limit == 5 | |
| assert len(case.court.rounds) == 1 | |
| rnd = case.court.rounds[0] | |
| assert rnd.witness_id == "morrison" | |
| assert len(rnd.testimonies) == 2 | |
| def test_win_condition(self, case): | |
| wc = case.court.win_condition | |
| assert len(wc.required_contradiction_ids) == 3 | |
| assert "patrol_1" in wc.required_contradiction_ids | |
| assert "revised_2b" in wc.required_contradiction_ids | |
| assert "revised_3" in wc.required_contradiction_ids | |
| def test_investigation_gate(self, case): | |
| gate = case.investigation_gate | |
| assert gate is not None | |
| assert len(gate.required_evidence) == 4 | |
| assert "cloning_device" in gate.required_evidence | |
| def test_all_contradictions_reference_valid_evidence(self, case): | |
| ev_ids = case.get_all_evidence_ids() | |
| for rnd in case.court.rounds: | |
| for t in rnd.testimonies: | |
| for s in t.statements: | |
| if s.contradiction: | |
| assert s.contradiction.evidence_id in ev_ids, ( | |
| f"Statement {s.id} contradiction references unknown evidence " | |
| f"{s.contradiction.evidence_id}" | |
| ) | |