75 lines
2.6 KiB
Python
75 lines
2.6 KiB
Python
import json
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
from fastapi.testclient import TestClient
|
|
|
|
import app.narrate as narrate
|
|
from app.main import app
|
|
|
|
client = TestClient(app)
|
|
VALID_LOG = json.load(open(Path(__file__).parent / "fixtures" / "canon_log_valid.json"))
|
|
|
|
ROLE_ENDPOINTS = [
|
|
"/dm/narrate", "/dm/adjudicate", "/dm/improvise",
|
|
"/npc/speak", "/party/banter",
|
|
]
|
|
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def _stub_narrate_model_call(monkeypatch):
|
|
# /dm/narrate is wired to a real Ollama call (Task 7). These tests exercise
|
|
# canon-log validation across ALL role endpoints, not model behavior, so
|
|
# stub the model boundary — keeps the default suite hermetic (no network).
|
|
monkeypatch.setattr(narrate.ollama_client, "chat", lambda *a, **k: "stubbed prose")
|
|
monkeypatch.setattr(narrate.call_log, "record", lambda **kw: None)
|
|
|
|
|
|
def test_health_ok():
|
|
assert client.get("/health").json() == {"status": "ok"}
|
|
|
|
|
|
def test_every_role_endpoint_accepts_a_valid_canon_log():
|
|
for path in ROLE_ENDPOINTS:
|
|
if path == "/npc/speak":
|
|
# /npc/speak has additional required fields
|
|
body = {
|
|
"canon_log": VALID_LOG, "npc_id": "brannoc_thane",
|
|
"disposition": 0, "available_moves": [], "utterance": "test"
|
|
}
|
|
else:
|
|
body = {"canon_log": VALID_LOG}
|
|
r = client.post(path, json=body)
|
|
assert r.status_code == 200, f"{path} rejected a valid log: {r.text}"
|
|
|
|
|
|
def test_every_role_endpoint_rejects_an_invalid_canon_log():
|
|
bad = json.loads(json.dumps(VALID_LOG))
|
|
bad["player"]["luck"] = 5 # §7 leak
|
|
for path in ROLE_ENDPOINTS:
|
|
if path == "/npc/speak":
|
|
# /npc/speak has additional required fields
|
|
body = {
|
|
"canon_log": bad, "npc_id": "brannoc_thane",
|
|
"disposition": 0, "available_moves": [], "utterance": "test"
|
|
}
|
|
else:
|
|
body = {"canon_log": bad}
|
|
r = client.post(path, json=body)
|
|
assert r.status_code == 422, f"{path} accepted an invalid log"
|
|
assert "canon_log_errors" in r.json()["detail"]
|
|
|
|
|
|
def test_missing_canon_log_is_a_422():
|
|
r = client.post("/dm/narrate", json={})
|
|
assert r.status_code == 422
|
|
assert "canon_log_errors" in r.json()["detail"]
|
|
|
|
|
|
def test_malformed_body_uses_the_unified_error_shape():
|
|
# A pydantic body failure returns the SAME envelope as a schema failure,
|
|
# so the client only ever parses one 422 shape.
|
|
r = client.post("/dm/narrate", json={"canon_log": "not-a-dict"})
|
|
assert r.status_code == 422
|
|
assert "canon_log_errors" in r.json()["detail"]
|