test: add Cortex test suite (77 tests, no LLM calls)
Tests cover: - Smoke: /health, /auth/status, /distill/status (test_health.py) - Persona validation: path traversal, bad names, list_personas (test_persona.py) - Chat API: persona routing, session persistence, error handling (test_api_chat.py) - Files API: ALLOWED set enforcement, read/write, missing files (test_api_files.py) - Webhooks: NC Talk HMAC accept/reject, Google Chat JWT (test_webhooks.py) - Tools: scratch read/write/append/clear, tasks CRUD, cron parser + tools (test_tools.py) - Security: path traversal, replay attack, known gaps documented (test_security.py) All LLM calls mocked — suite runs in ~1.4s. Run: cd cortex && .venv/bin/pytest Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
122
cortex/tests/test_api_chat.py
Normal file
122
cortex/tests/test_api_chat.py
Normal file
@@ -0,0 +1,122 @@
|
||||
"""
|
||||
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
|
||||
Reference in New Issue
Block a user