diff --git a/src/backend/api/routes/sessions.py b/src/backend/api/routes/sessions.py new file mode 100644 index 0000000..0f98e75 --- /dev/null +++ b/src/backend/api/routes/sessions.py @@ -0,0 +1,37 @@ +from fastapi import APIRouter, Depends +from starlette.responses import JSONResponse + +from src.backend.api.dependencies import verify_api_key +from src.backend.db.database import get_db, list_sessions, close_session +from src.backend.models.api import ok, fail + +router = APIRouter( + prefix="/sessions", + tags=["sessions"], + dependencies=[Depends(verify_api_key)], +) + + +async def _get_db(): + db = await get_db() + try: + yield db + finally: + await db.close() + + +@router.get("", summary="List sessions") +async def list_all(db=Depends(_get_db)): + sessions = await list_sessions(db) + return ok(sessions) + + +@router.delete("/{session_id}", summary="Close session") +async def delete(session_id: str, db=Depends(_get_db)): + closed = await close_session(db, session_id) + if not closed: + return JSONResponse( + status_code=404, + content=fail("NOT_FOUND", "Session not found"), + ) + return ok({"closed": True}) diff --git a/tests/test_api_sessions.py b/tests/test_api_sessions.py new file mode 100644 index 0000000..29b27a8 --- /dev/null +++ b/tests/test_api_sessions.py @@ -0,0 +1,74 @@ +import pytest +from httpx import AsyncClient, ASGITransport + + +@pytest.fixture +def app(): + from src.backend.main import app + return app + + +@pytest.fixture +async def client(app): + transport = ASGITransport(app=app) + async with AsyncClient( + transport=transport, + base_url="http://test", + headers={"X-Api-Key": "test-key-12345"}, + ) as ac: + yield ac + + +@pytest.fixture +async def topic_id(client): + resp = await client.post( + "/api/topics", + json={"name": "sessions-test", "system_prompt": "Test prompt."}, + ) + return resp.json()["data"]["id"] + + +@pytest.mark.asyncio +async def test_list_sessions_empty(client): + resp = await client.get("/api/sessions") + assert resp.status_code == 200 + assert resp.json()["data"] == [] + + +@pytest.mark.asyncio +async def test_list_sessions_with_data(client, topic_id): + resp1 = await client.post( + "/api/requests", + json={"prompt": "First", "topic_id": topic_id, "session_id": "new"}, + ) + session_id = resp1.json()["data"]["session_id"] + + await client.post( + "/api/requests", + json={"prompt": "Second", "topic_id": topic_id, "session_id": session_id}, + ) + + resp = await client.get("/api/sessions") + assert resp.status_code == 200 + sessions = resp.json()["data"] + assert len(sessions) == 1 + assert sessions[0]["session_id"] == session_id + assert sessions[0]["request_count"] == 2 + + +@pytest.mark.asyncio +async def test_delete_session(client, topic_id): + resp1 = await client.post( + "/api/requests", + json={"prompt": "Hello", "topic_id": topic_id, "session_id": "new"}, + ) + session_id = resp1.json()["data"]["session_id"] + + resp = await client.delete(f"/api/sessions/{session_id}") + assert resp.status_code == 200 + + resp = await client.post( + "/api/requests", + json={"prompt": "Again", "topic_id": topic_id, "session_id": session_id}, + ) + assert resp.status_code == 410