feat(api): wire /dm/narrate — 200 prose | 502 model_error | 422 invalid log

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-10 08:31:38 -05:00
parent 2027e7a9b9
commit 5a35802075
2 changed files with 45 additions and 1 deletions

View File

@@ -12,6 +12,8 @@ from fastapi.responses import JSONResponse
from pydantic import BaseModel
from .canon_log import validate_canon_log
from .narrate import run as narrate_run
from .ollama_client import ModelError
app = FastAPI(title="coc-rpg proxy", version="0.0.1")
@@ -52,7 +54,10 @@ def health() -> dict:
@app.post("/dm/narrate")
def narrate(req: TurnRequest = Depends(valid_turn)) -> dict:
return {"detail": "not implemented"}
try:
return {"prose": narrate_run(req.canon_log)}
except ModelError as exc:
raise HTTPException(status_code=502, detail={"model_error": str(exc)})
@app.post("/dm/adjudicate")

View File

@@ -0,0 +1,39 @@
import json
from pathlib import Path
from fastapi.testclient import TestClient
import app.main as main
import app.narrate as narrate
from app.ollama_client import ModelError
client = TestClient(main.app)
VALID = json.loads((Path(__file__).parent / "fixtures" / "canon_log_valid.json").read_text())
def test_valid_log_returns_prose(monkeypatch):
monkeypatch.setattr(narrate.ollama_client, "chat", lambda *a, **k: "You stand on the wharf.")
monkeypatch.setattr(narrate.call_log, "record", lambda **kw: None) # keep test output pristine
r = client.post("/dm/narrate", json={"canon_log": VALID})
assert r.status_code == 200
assert r.json() == {"prose": "You stand on the wharf."}
def test_model_error_maps_to_502(monkeypatch):
def boom(*a, **k):
raise ModelError("upstream down")
monkeypatch.setattr(narrate.ollama_client, "chat", boom)
monkeypatch.setattr(narrate.call_log, "record", lambda **kw: None)
r = client.post("/dm/narrate", json={"canon_log": VALID})
assert r.status_code == 502
assert "model_error" in r.json()["detail"]
def test_invalid_log_still_422(monkeypatch):
monkeypatch.setattr(narrate.ollama_client, "chat", lambda *a, **k: "x")
bad = json.loads(json.dumps(VALID))
bad["player"]["luck"] = 5 # §7 leak — schema rejects
r = client.post("/dm/narrate", json={"canon_log": bad})
assert r.status_code == 422
assert "canon_log_errors" in r.json()["detail"]