"""End-to-end run flow over the scripted golden engine: interrogate -> suspicion moves, present the breaking exhibit -> the alibi cracks (without a confession), accuse -> graded verdict. Plus anti-leak on the live (pre-verdict) endpoints. """ from __future__ import annotations from starlette.testclient import TestClient from case_zero.api import build_server def _new_run() -> tuple[TestClient, str]: c = TestClient(build_server()) r = c.post("/api/case", json={"caseId": "GRAYMOOR-3107"}) return c, r.json()["runId"] def test_interrogate_moves_suspicion() -> None: c, run = _new_run() r = c.post(f"/api/run/{run}/interrogate/iris", json={"questionId": "q3"}) assert r.status_code == 200 body = r.json() assert body["reply"] assert body["suspicionDelta"] == 18 assert body["suspicion"] == 25 + 18 # iris baseline 25 + q3 delta def test_breaking_evidence_cracks_without_confession() -> None: c, run = _new_run() r = c.post(f"/api/run/{run}/interrogate/iris", json={"presentEvidenceId": "keycard"}).json() assert r["flags"]["rattled"] and r["flags"]["cornered"] # +26 is the strongest tell assert r["suspicion"] == 25 + 26 low = r["reply"].lower() # cracks, but never confesses assert "i did it" not in low assert "i killed" not in low def test_accuse_correct_is_case_closed() -> None: c, run = _new_run() v = c.post( f"/api/run/{run}/accuse", json={"suspectId": "iris", "motiveId": "m_credit", "evidenceIds": ["keycard", "voicemail", "cctv"]}, ).json() assert v["correct"] is True assert v["verdict"]["stamp"] == "CASE CLOSED" assert v["verdict"]["killerName"] == "IRIS CALLOWAY" assert v["score"]["points"] == 100 def test_accuse_wrong_is_mistrial() -> None: c, run = _new_run() v = c.post( f"/api/run/{run}/accuse", json={"suspectId": "wexler", "motiveId": "m_money", "evidenceIds": ["receipt"]}, ).json() assert v["correct"] is False assert v["verdict"]["stamp"] == "MISTRIAL" assert v["score"]["points"] < 100 def test_live_endpoints_do_not_leak_sealed() -> None: c, run = _new_run() turn = c.post(f"/api/run/{run}/interrogate/iris", json={"questionId": "q0"}).text hint = c.get(f"/api/run/{run}/hint", params={"screen": "interro"}).text for blob in (turn, hint): for token in ('"killer"', '"correctMotive"', '"keyEvidence"', '"d":'): assert token not in blob def test_hint_is_screen_aware_and_spoiler_safe() -> None: c, run = _new_run() h = c.get(f"/api/run/{run}/hint", params={"screen": "accuse"}).json()["hint"] assert h assert "iris" not in h.lower() # never names the killer def test_unknown_run_404() -> None: c = TestClient(build_server()) assert c.post("/api/run/nope/interrogate/iris", json={"questionId": "q0"}).status_code == 404