"""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, mock_deps):
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"
# Replace the cached agent on the AppContainer with our dummy.
from navi.main import app
app.state.container._agent = 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
class TestAdmin:
def test_list_users(self, client, mock_deps):
mock_deps["fake_conn"].enqueue([
{"id": "u1", "email": "a@b.com", "display_name": "A", "role": "admin",
"permissions": "[]", "created_at": __import__("datetime").datetime.now(__import__("datetime").timezone.utc),
"updated_at": __import__("datetime").datetime.now(__import__("datetime").timezone.utc)}
])
response = client.get("/admin/users")
assert response.status_code == 200
data = response.json()
assert isinstance(data, list)
assert data[0]["id"] == "u1"
@pytest.mark.anyio
async def test_get_session_detail(self, client, make_session):
session = await make_session("secretary", [Message(role="user", content="hi")])
response = client.get(f"/admin/sessions/{session.id}")
assert response.status_code == 200
data = response.json()
assert data["session_id"] == session.id
assert "messages" in data
def test_get_session_detail_not_found(self, client):
response = client.get("/admin/sessions/nonexistent")
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"/admin/sessions/{session.id}")
assert response.status_code == 204
assert client.get(f"/admin/sessions/{session.id}").status_code == 404
def test_delete_session_not_found(self, client):
response = client.delete("/admin/sessions/nonexistent")
assert response.status_code == 404
@pytest.mark.anyio
async def test_get_user_sessions(self, client, make_session):
session = await make_session("secretary")
response = client.get(f"/admin/users/{session.user_id}/sessions")
assert response.status_code == 200
data = response.json()
assert isinstance(data, list)
def test_get_user_detail_not_found(self, client, mock_deps):
mock_deps["fake_conn"].enqueue(None)
response = client.get("/admin/users/nonexistent")
assert response.status_code == 404
def test_list_profiles(self, client):
response = client.get("/admin/profiles")
assert response.status_code == 200
data = response.json()
assert isinstance(data, list)
assert any(p["id"] == "secretary" for p in data)
def test_update_profile_availability(self, client, mock_deps):
mock_deps["fake_conn"].enqueue("INSERT 0 1")
response = client.patch("/admin/profiles/secretary/availability", json={"is_admin_only": True})
assert response.status_code == 200
data = response.json()
assert data["ok"] is True
@pytest.mark.anyio
async def test_list_sessions_pagination(self, client, make_session):
sessions = [await make_session("secretary") for _ in range(5)]
response = client.get("/admin/sessions?limit=2&offset=0")
assert response.status_code == 200
data = response.json()
assert data["total"] == 5
assert len(data["items"]) == 2
assert data["limit"] == 2
assert data["offset"] == 0
@pytest.mark.anyio
async def test_list_sessions_pagination_offset(self, client, make_session):
sessions = [await make_session("secretary") for _ in range(5)]
response = client.get("/admin/sessions?limit=2&offset=4")
assert response.status_code == 200
data = response.json()
assert data["total"] == 5
assert len(data["items"]) == 1
@pytest.mark.anyio
async def test_list_sessions_search(self, client, make_session, mock_deps):
store = mock_deps["session_store"]
s1 = await make_session("secretary")
s1.name = "alpha session"
await store.save(s1)
s2 = await make_session("developer")
s2.name = "beta session"
await store.save(s2)
# search by name
response = client.get("/admin/sessions?search=alpha")
assert response.status_code == 200
data = response.json()
ids = {i["session_id"] for i in data["items"]}
assert s1.id in ids
assert s2.id not in ids
# search by profile
response = client.get("/admin/sessions?search=developer")
assert response.status_code == 200
data = response.json()
ids = {i["session_id"] for i in data["items"]}
assert s2.id in ids
@pytest.mark.anyio
async def test_list_sessions_sort(self, client, make_session, mock_deps):
store = mock_deps["session_store"]
s1 = await make_session("secretary")
s1.name = "aaa"
await store.save(s1)
s2 = await make_session("secretary")
s2.name = "zzz"
await store.save(s2)
response = client.get("/admin/sessions?sort_by=name&sort_order=asc")
assert response.status_code == 200
data = response.json()
names = [i["name"] for i in data["items"]]
assert names.index("aaa") < names.index("zzz")
response = client.get("/admin/sessions?sort_by=name&sort_order=desc")
assert response.status_code == 200
data = response.json()
names = [i["name"] for i in data["items"]]
assert names.index("zzz") < names.index("aaa")