| """Stateless REST endpoint for the guided interview. |
| |
| Raw HTTP callers can't satisfy Gradio's hidden gr.State input on the UI |
| endpoints (/info advertises 2 params, the REST validator wants 3 — upstream |
| inconsistency, found in the June 6 live API check). interview_api carries the |
| state as an opaque JSON string instead, so a plain curl works end to end. |
| """ |
| from __future__ import annotations |
|
|
| import json |
|
|
| import pytest |
|
|
| import app as app_mod |
|
|
|
|
| def test_interview_api_starts_fresh_and_returns_state(): |
| result = app_mod.interview_api("Sam, 34") |
| assert result["reply"].endswith("?") |
| assert not result["done"] |
| assert not result["early_exit"] |
| state = json.loads(result["state"]) |
| assert state["user_turns"] == ["Sam, 34"] |
| assert state["patient_age"] == 34 |
|
|
|
|
| def test_interview_api_round_trip_continues_interview(): |
| first = app_mod.interview_api("Sam, 34") |
| second = app_mod.interview_api("My crown feels high when I bite.", first["state"]) |
| state = json.loads(second["state"]) |
| assert state["user_turns"] == ["Sam, 34", "My crown feels high when I bite."] |
| assert not second["done"] |
|
|
|
|
| def test_interview_api_runs_deterministic_sentinel(): |
| """The emergency interrupt must fire over the raw API exactly as in the UI.""" |
| result = app_mod.interview_api( |
| "I have no pain but I cannot breathe properly and my throat is swelling" |
| ) |
| assert result["early_exit"] |
| assert result["done"] |
| assert result["hard_findings"] |
| assert "urgent" in result["reply"].lower() or "emergency" in result["reply"].lower() |
|
|
|
|
| def test_interview_api_rejects_malformed_state(): |
| with pytest.raises(ValueError): |
| app_mod.interview_api("hello", "{not valid json") |
|
|
|
|
| def test_interview_api_rejects_state_with_wrong_fields(): |
| with pytest.raises(ValueError): |
| app_mod.interview_api("hello", json.dumps({"phase": "demographics", "bogus": 1})) |
|
|
|
|
| def test_interview_api_rejects_unknown_phase(): |
| """Crafted phase must raise a clean ValueError, not 500 via KeyError in step().""" |
| with pytest.raises(ValueError): |
| app_mod.interview_api("hello", json.dumps({"phase": "hacked"})) |
|
|
|
|
| def test_interview_api_rejects_oversized_turn_history(): |
| """Replayed token may not exceed the interview's own turn cap (prompt inflation).""" |
| bloated = json.dumps({"user_turns": ["tooth hurts"] * 1000}) |
| with pytest.raises(ValueError): |
| app_mod.interview_api("hello", bloated) |
|
|