""" Topics API tests — async only (Plan 05 cutover). Legacy sync tests (using the flat-file storage layer and sync TestClient) were updated to async in Plan 05 to match the new session-injected API routes. """ from __future__ import annotations import pytest async def test_list_topics_empty(async_client): resp = await async_client.get("/api/topics") assert resp.status_code == 200 assert resp.json()["topics"] == [] async def test_create_topic(async_client): resp = await async_client.post( "/api/topics", json={"name": "Finance", "description": "Financial docs", "color": "#ff0000"}, ) assert resp.status_code == 200 data = resp.json() assert data["name"] == "Finance" assert data["color"] == "#ff0000" assert "id" in data async def test_create_topic_deduplication(async_client): await async_client.post("/api/topics", json={"name": "Finance"}) resp = await async_client.post("/api/topics", json={"name": "finance"}) # case-insensitive assert resp.status_code == 200 topics = (await async_client.get("/api/topics")).json()["topics"] assert len(topics) == 1 async def test_update_topic(async_client): create = (await async_client.post("/api/topics", json={"name": "Old Name"})).json() resp = await async_client.patch(f"/api/topics/{create['id']}", json={"name": "New Name"}) assert resp.status_code == 200 assert resp.json()["name"] == "New Name" async def test_update_topic_not_found(async_client): resp = await async_client.patch( "/api/topics/00000000-0000-0000-0000-000000000000", json={"name": "X"}, ) assert resp.status_code == 404 async def test_delete_topic(async_client): create = (await async_client.post("/api/topics", json={"name": "ToDelete"})).json() resp = await async_client.delete(f"/api/topics/{create['id']}") assert resp.status_code == 200 assert resp.json()["success"] is True topics = (await async_client.get("/api/topics")).json()["topics"] assert not any(t["name"] == "ToDelete" for t in topics) async def test_delete_topic_cascades_to_documents(async_client, db_session, sample_txt): # Create a topic topic = (await async_client.post("/api/topics", json={"name": "Legal"})).json() # Upload doc (no auto classify) with open(sample_txt, "rb") as f: upload = ( await async_client.post( "/api/documents/upload", files={"file": ("sample.txt", f, "text/plain")}, data={"auto_classify": "false"}, ) ).json() # Manually set topic via the storage service from services import storage await storage.update_document_topics(db_session, upload["id"], ["Legal"]) # Delete topic await async_client.delete(f"/api/topics/{topic['id']}") # Verify document no longer has the topic doc = (await async_client.get(f"/api/documents/{upload['id']}")).json() assert "Legal" not in doc["topics"] async def test_delete_topic_not_found(async_client): resp = await async_client.delete("/api/topics/nonexistent") assert resp.status_code == 404 # --------------------------------------------------------------------------- # Wave 0 xfail stubs for Phase 3 topic namespace tests — Plan 03-03 # --------------------------------------------------------------------------- @pytest.mark.xfail(strict=False, reason="implemented in plan 03-03") async def test_topic_namespace(async_client, auth_user, db_session): """GET /api/topics returns only system topics (user_id=NULL) + auth_user-owned topics. DOC-04: layered topic namespace — system topics (user_id=NULL) are visible to all users; per-user topics (user_id=current_user.id) are visible only to that user. A different user's topics must not appear (CONTEXT.md D-08, D-17). Test setup: seed one system topic, one auth_user-owned topic, one topic owned by a different user. GET /api/topics must return exactly the first two. """ assert True # scaffold @pytest.mark.xfail(strict=False, reason="implemented in plan 03-03") async def test_admin_create_system_topic(async_client, admin_user): """POST /api/admin/topics returns 201 and creates a Topic with user_id=NULL. D-09: only admin can create system topics via POST /api/admin/topics. The created topic has user_id=NULL and is visible to all users. """ assert True # scaffold @pytest.mark.xfail(strict=False, reason="implemented in plan 03-03") async def test_regular_user_cannot_create_system_topic(async_client, auth_user): """POST /api/admin/topics with auth_user.headers returns 403. D-09: the admin topics endpoint requires get_current_admin; regular users receive 403 Forbidden. """ assert True # scaffold @pytest.mark.xfail(strict=False, reason="implemented in plan 03-03") async def test_topics_require_auth(async_client): """Anonymous GET /api/topics (no Authorization header) returns 401 or 403. D-17: /api/topics/* gains get_current_user in Phase 3 — anonymous access must be rejected. """ assert True # scaffold