169 lines
6.1 KiB
Python
169 lines
6.1 KiB
Python
"""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()
|