"""Phase 1 tests: Documents CRUD endpoints. Covers: - GET /documents listing with chunk counts - GET /documents/{id}/chunks - DELETE /documents/{id} - DELETE /chunks/{id} """ import pytest from fastapi.testclient import TestClient from unittest.mock import MagicMock, patch class TestDocumentsRouter: """Documents CRUD endpoint tests.""" @pytest.fixture def client(self): """Create test client with mocked dependencies.""" from app.main import app return TestClient(app) def test_list_documents_empty(self, client): """Should return empty list when no documents exist.""" with patch("app.routers.documents.RAGService") as mock_rag_class: mock_rag = MagicMock() mock_rag.list_documents.return_value = ([], 0, 0) mock_rag_class.return_value = mock_rag response = client.get("/api/v1/documents") assert response.status_code == 200 data = response.json() assert data["documents"] == [] assert data["total_documents"] == 0 assert data["total_chunks"] == 0 def test_list_documents_with_data(self, client): """Should return grouped documents with chunk counts.""" doc_list = [ { "document_id": "abc-123", "filename": "report.pdf", "chunk_count": 3, "upload_date": "2026-04-23", }, { "document_id": "def-456", "filename": "notes.txt", "chunk_count": 1, "upload_date": "2026-04-22", }, ] with patch("app.routers.documents.RAGService") as mock_rag_class: mock_rag = MagicMock() mock_rag.list_documents.return_value = (doc_list, 2, 4) mock_rag_class.return_value = mock_rag response = client.get("/api/v1/documents") assert response.status_code == 200 data = response.json() assert data["total_documents"] == 2 assert data["total_chunks"] == 4 assert len(data["documents"]) == 2 assert data["documents"][0]["document_id"] == "abc-123" assert data["documents"][0]["filename"] == "report.pdf" assert data["documents"][0]["chunk_count"] == 3 def test_list_chunks_for_document(self, client): """Should return all chunks for a given document_id.""" chunks = [ { "chunk_id": "abc-123_0", "chunk_index": 0, "content_summary": "First chunk summary", "page_number": 1, "chunk_file_path": None, }, { "chunk_id": "abc-123_1", "chunk_index": 1, "content_summary": "Second chunk summary", "page_number": 2, "chunk_file_path": None, }, ] with patch("app.routers.documents.RAGService") as mock_rag_class: mock_rag = MagicMock() mock_rag.list_chunks.return_value = chunks mock_rag_class.return_value = mock_rag response = client.get("/api/v1/documents/abc-123/chunks") assert response.status_code == 200 data = response.json() assert len(data) == 2 assert data[0]["chunk_id"] == "abc-123_0" assert data[0]["chunk_index"] == 0 assert data[0]["content_summary"] == "First chunk summary" assert data[1]["chunk_index"] == 1 def test_list_chunks_document_not_found(self, client): """Should return empty list for nonexistent document.""" with patch("app.routers.documents.RAGService") as mock_rag_class: mock_rag = MagicMock() mock_rag.list_chunks.return_value = [] mock_rag_class.return_value = mock_rag response = client.get("/api/v1/documents/nonexistent-id/chunks") assert response.status_code == 200 data = response.json() assert data == [] def test_delete_document_success(self, client): """Should delete all chunks for a document and return confirmation.""" with patch("app.routers.documents.RAGService") as mock_rag_class: mock_rag = MagicMock() mock_rag.delete_document.return_value = (True, 3) mock_rag_class.return_value = mock_rag response = client.delete("/api/v1/documents/abc-123") assert response.status_code == 200 data = response.json() assert data["deleted"] is True assert "3 chunks removed" in data["message"] def test_delete_document_not_found(self, client): """Should return 404 for nonexistent document.""" with patch("app.routers.documents.RAGService") as mock_rag_class: mock_rag = MagicMock() mock_rag.delete_document.return_value = (False, 0) mock_rag_class.return_value = mock_rag response = client.delete("/api/v1/documents/nonexistent-id") assert response.status_code == 404 assert "not found" in response.json()["detail"].lower() def test_delete_chunk_success(self, client): """Should delete a single chunk and return confirmation.""" with patch("app.routers.documents.RAGService") as mock_rag_class: mock_rag = MagicMock() mock_rag.delete_chunk.return_value = True mock_rag_class.return_value = mock_rag response = client.delete("/api/v1/chunks/abc-123_0") assert response.status_code == 200 data = response.json() assert data["deleted"] is True assert "abc-123_0" in data["message"] def test_delete_chunk_not_found(self, client): """Should return 404 for nonexistent chunk.""" with patch("app.routers.documents.RAGService") as mock_rag_class: mock_rag = MagicMock() mock_rag.delete_chunk.return_value = False mock_rag_class.return_value = mock_rag response = client.delete("/api/v1/chunks/nonexistent-chunk") assert response.status_code == 404 assert "not found" in response.json()["detail"].lower()