diff --git a/src/backend/api/routes/topics.py b/src/backend/api/routes/topics.py index 1985e67..12034b5 100644 --- a/src/backend/api/routes/topics.py +++ b/src/backend/api/routes/topics.py @@ -9,8 +9,10 @@ from src.backend.db.database import ( get_topic, update_topic, delete_topic, + list_requests, ) from src.backend.models.api import TopicCreate, TopicUpdate, ok, fail +from src.backend.services.worker_client import WorkerClient router = APIRouter( prefix="/topics", @@ -91,3 +93,38 @@ async def delete(topic_id: str, db=Depends(_get_db)): content=fail("NOT_FOUND", "Topic not found or has active requests"), ) return ok({"deleted": True}) + + +@router.post("/{topic_id}/summarize", summary="Regenerate topic summary") +async def summarize_topic(topic_id: str, db=Depends(_get_db)): + topic = await get_topic(db, topic_id) + if not topic: + return JSONResponse( + status_code=404, + content=fail("NOT_FOUND", "Topic not found"), + ) + + completed = await list_requests(db, status="completed", topic_id=topic_id) + if not completed: + return JSONResponse( + status_code=400, + content=fail("NO_DATA", "No completed requests to summarize"), + ) + + exchanges = [ + {"prompt": r["prompt"], "result": r["result"]} + for r in completed + if r["result"] + ] + + worker = WorkerClient() + result = await worker.summarize(exchanges=exchanges, mode="complete") + + if not result.get("success"): + return JSONResponse( + status_code=502, + content=fail("WORKER_ERROR", "Worker failed to generate summary"), + ) + + updated = await update_topic(db, topic_id, summary=result["summary"]) + return ok(updated) diff --git a/tests/test_api_topics.py b/tests/test_api_topics.py index 2d4a87e..f80d580 100644 --- a/tests/test_api_topics.py +++ b/tests/test_api_topics.py @@ -148,3 +148,58 @@ async def test_delete_topic(client): resp = await client.get(f"/api/topics/{topic_id}") assert resp.status_code == 404 + + +@pytest.mark.asyncio +async def test_summarize_topic(client): + create_resp = await client.post( + "/api/topics", + json={"name": "summarize-test", "system_prompt": "You help."}, + ) + topic_id = create_resp.json()["data"]["id"] + + from src.backend.db.database import get_db, create_request, complete_request + + db = await get_db() + try: + req1 = await create_request(db, "Question 1", topic_id) + await complete_request(db, req1["id"], "Answer 1") + req2 = await create_request(db, "Question 2", topic_id) + await complete_request(db, req2["id"], "Answer 2") + finally: + await db.close() + + from unittest.mock import patch, AsyncMock, MagicMock + + mock_worker = MagicMock() + mock_response = MagicMock() + mock_response.status_code = 200 + mock_response.json.return_value = { + "success": True, + "summary": "Comprehensive summary of all exchanges.", + } + + mock_client = AsyncMock() + mock_client.post.return_value = mock_response + mock_client.__aenter__ = AsyncMock(return_value=mock_client) + mock_client.__aexit__ = AsyncMock(return_value=False) + + with patch("src.backend.services.worker_client.httpx.AsyncClient", return_value=mock_client): + resp = await client.post(f"/api/topics/{topic_id}/summarize") + + assert resp.status_code == 200 + data = resp.json() + assert data["success"] is True + assert "summary" in data["data"] + + +@pytest.mark.asyncio +async def test_summarize_topic_no_requests(client): + create_resp = await client.post( + "/api/topics", + json={"name": "empty-topic", "system_prompt": "Empty."}, + ) + topic_id = create_resp.json()["data"]["id"] + + resp = await client.post(f"/api/topics/{topic_id}/summarize") + assert resp.status_code == 400