""" Tests for POST /chat — persona routing, session handling, LLM mocking. """ import json import pytest def _parse_sse(text: str) -> list[dict]: """Extract JSON payloads from an SSE response body.""" events = [] for line in text.splitlines(): if line.startswith("data: "): try: events.append(json.loads(line[6:])) except json.JSONDecodeError: pass return events @pytest.mark.anyio async def test_chat_basic(client, mock_llm): r = await client.post("/chat", json={"message": "Hello", "persona": "inara"}) assert r.status_code == 200 events = _parse_sse(r.text) responses = [e for e in events if e.get("type") == "response"] assert len(responses) == 1 assert responses[0]["response"] == "Hello, I am a test response." assert responses[0]["backend"] == "claude" assert "session_id" in responses[0] @pytest.mark.anyio async def test_chat_default_persona(client, mock_llm): """persona defaults to 'inara' when not specified.""" r = await client.post("/chat", json={"message": "Hi"}) assert r.status_code == 200 events = _parse_sse(r.text) assert any(e.get("type") == "response" for e in events) @pytest.mark.anyio async def test_chat_unknown_persona(client, mock_llm): r = await client.post("/chat", json={"message": "Hi", "persona": "nobody"}) assert r.status_code == 200 events = _parse_sse(r.text) errors = [e for e in events if e.get("type") == "error"] assert len(errors) == 1 assert "nobody" in errors[0]["message"] @pytest.mark.anyio async def test_chat_path_traversal_persona(client, mock_llm): r = await client.post("/chat", json={"message": "Hi", "persona": "../../etc"}) assert r.status_code == 200 events = _parse_sse(r.text) errors = [e for e in events if e.get("type") == "error"] assert len(errors) == 1 @pytest.mark.anyio async def test_chat_session_persists(client, mock_llm): """Same session_id reuses history.""" r1 = await client.post("/chat", json={"message": "First message", "session_id": "test-sess-1"}) r2 = await client.post("/chat", json={"message": "Second message", "session_id": "test-sess-1"}) assert r1.status_code == 200 assert r2.status_code == 200 @pytest.mark.anyio async def test_sessions_list(client, mock_llm): """After a chat, the session appears in /sessions.""" await client.post("/chat", json={"message": "Hello", "session_id": "test-list-sess"}) r = await client.get("/sessions") assert r.status_code == 200 sessions = r.json()["sessions"] ids = [s["session_id"] for s in sessions] assert "test-list-sess" in ids @pytest.mark.anyio async def test_session_history(client, mock_llm): await client.post("/chat", json={"message": "Hello", "session_id": "test-hist-sess"}) r = await client.get("/history/test-hist-sess") assert r.status_code == 200 msgs = r.json()["messages"] assert any(m["role"] == "user" and "Hello" in m["content"] for m in msgs) @pytest.mark.anyio async def test_session_delete(client, mock_llm): await client.post("/chat", json={"message": "Hello", "session_id": "test-del-sess"}) r = await client.delete("/sessions/test-del-sess") assert r.status_code == 200 assert r.json()["ok"] is True @pytest.mark.anyio async def test_session_delete_unknown(client): r = await client.delete("/sessions/does-not-exist") assert r.status_code == 404 @pytest.mark.anyio async def test_backend_get(client): r = await client.get("/backend") assert r.status_code == 200 assert r.json()["primary"] in ("claude", "gemini") @pytest.mark.anyio async def test_backend_set(client): r = await client.post("/backend", json={"primary": "gemini"}) assert r.status_code == 200 assert r.json()["primary"] == "gemini" # Reset await client.post("/backend", json={"primary": "claude"}) @pytest.mark.anyio async def test_backend_set_invalid(client): r = await client.post("/backend", json={"primary": "gpt-4"}) assert r.status_code == 400