"""Integration tests for REST API routes."""
import pytest
from navi.llm.base import Message
class TestHealth:
def test_health(self, client):
response = client.get("/health")
assert response.status_code == 200
data = response.json()
assert data["status"] == "ok"
assert "embed" in data
def test_health_embed(self, client):
response = client.get("/health/embed")
assert response.status_code == 200
data = response.json()
assert "ok" in data
class TestAgents:
def test_list_profiles(self, client):
response = client.get("/agents/profiles")
assert response.status_code == 200
data = response.json()
assert len(data) >= 2
assert any(p["id"] == "secretary" for p in data)
def test_list_tools(self, client):
response = client.get("/agents/tools")
assert response.status_code == 200
data = response.json()
assert len(data) >= 2
names = {t["name"] for t in data}
assert "test_tool" in names
class TestSessions:
async def test_create_session(self, client):
response = client.post("/sessions", json={"profile_id": "secretary"})
assert response.status_code == 201
data = response.json()
assert "session_id" in data
assert data["profile_id"] == "secretary"
def test_create_session_invalid_profile(self, client):
response = client.post("/sessions", json={"profile_id": "nonexistent"})
assert response.status_code == 404
@pytest.mark.anyio
async def test_list_sessions(self, client, make_session):
session = await make_session("secretary", [Message(role="user", content="hi")])
response = client.get("/sessions")
assert response.status_code == 200
data = response.json()
assert any(s["session_id"] == session.id for s in data)
@pytest.mark.anyio
async def test_list_sessions_paginates(self, client, make_session):
sessions = [await make_session("secretary") for _ in range(12)]
response = client.get("/sessions?limit=10&offset=0")
assert response.status_code == 200
data = response.json()
assert len(data["items"]) == 10
assert data["has_more"] is True
assert data["next_offset"] == 10
response = client.get("/sessions?limit=10&offset=10")
assert response.status_code == 200
data = response.json()
assert len(data["items"]) == 2
assert data["has_more"] is False
assert {s["session_id"] for s in data["items"]}.issubset({s.id for s in sessions})
@pytest.mark.anyio
async def test_list_sessions_filters_by_profile_before_pagination(self, client, make_session):
pinned = await make_session("developer")
await make_session("secretary")
for _ in range(12):
await make_session("developer")
response = client.patch(f"/sessions/{pinned.id}/pin", json={"pinned": True})
assert response.status_code == 200
response = client.get("/sessions?limit=10&offset=0&profile_id=developer")
assert response.status_code == 200
data = response.json()
assert len(data["items"]) == 10
assert all(s["profile_id"] == "developer" for s in data["items"])
assert data["items"][0]["session_id"] == pinned.id
assert data["has_more"] is True
@pytest.mark.anyio
async def test_get_session(self, client, make_session):
session = await make_session("secretary")
response = client.get(f"/sessions/{session.id}")
assert response.status_code == 200
data = response.json()
assert data["session_id"] == session.id
assert data["profile_id"] == "secretary"
def test_get_session_not_found(self, client):
response = client.get("/sessions/nonexistent")
assert response.status_code == 404
@pytest.mark.anyio
async def test_pin_session(self, client, make_session):
session = await make_session("secretary")
response = client.patch(f"/sessions/{session.id}/pin", json={"pinned": True})
assert response.status_code == 200
data = response.json()
assert data["pinned"] is True
def test_pin_session_not_found(self, client):
response = client.patch("/sessions/nonexistent/pin", json={"pinned": True})
assert response.status_code == 404
@pytest.mark.anyio
async def test_get_context(self, client, make_session, mock_deps):
session = await make_session("secretary", [Message(role="user", content="hello")])
session.context.append(Message(role="user", content="hello"))
await mock_deps["session_store"].save(session)
response = client.get(f"/sessions/{session.id}/context")
assert response.status_code == 200
data = response.json()
assert data["session_id"] == session.id
assert data["message_count"] == 1
def test_get_context_not_found(self, client):
response = client.get("/sessions/nonexistent/context")
assert response.status_code == 404
@pytest.mark.anyio
async def test_get_planning(self, client, make_session):
session = await make_session("secretary")
session.planning_logs.append({"phases": {}})
response = client.get(f"/sessions/{session.id}/planning")
assert response.status_code == 200
data = response.json()
assert data["session_id"] == session.id
assert len(data["logs"]) == 1
def test_get_planning_not_found(self, client):
response = client.get("/sessions/nonexistent/planning")
assert response.status_code == 404
@pytest.mark.anyio
async def test_delete_session(self, client, make_session):
session = await make_session("secretary")
response = client.delete(f"/sessions/{session.id}")
assert response.status_code == 204
assert client.get(f"/sessions/{session.id}").status_code == 404
def test_delete_session_not_found(self, client):
response = client.delete("/sessions/nonexistent")
assert response.status_code == 404
class TestMessages:
@pytest.mark.anyio
async def test_send_message(self, client, make_session, monkeypatch):
session = await make_session("secretary")
class DummyAgent:
async def run(self, session_id, user_message, images=None):
return "Response text"
# Patch the Agent class in deps so the original get_agent() (captured by
# Depends() at import time) instantiates our dummy when called.
monkeypatch.setattr("navi.api.deps.Agent", lambda *a, **kw: DummyAgent())
response = client.post(f"/sessions/{session.id}/messages", json={"content": "hi"})
assert response.status_code == 200
data = response.json()
assert data["role"] == "assistant"
assert data["content"] == "Response text"
def test_send_message_not_found(self, client):
response = client.post("/sessions/nonexistent/messages", json={"content": "hi"})
assert response.status_code == 404