julee 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- julee/__init__.py +3 -0
- julee/api/__init__.py +20 -0
- julee/api/app.py +180 -0
- julee/api/dependencies.py +257 -0
- julee/api/requests.py +175 -0
- julee/api/responses.py +43 -0
- julee/api/routers/__init__.py +43 -0
- julee/api/routers/assembly_specifications.py +212 -0
- julee/api/routers/documents.py +182 -0
- julee/api/routers/knowledge_service_configs.py +79 -0
- julee/api/routers/knowledge_service_queries.py +293 -0
- julee/api/routers/system.py +137 -0
- julee/api/routers/workflows.py +234 -0
- julee/api/services/__init__.py +20 -0
- julee/api/services/system_initialization.py +214 -0
- julee/api/tests/__init__.py +14 -0
- julee/api/tests/routers/__init__.py +17 -0
- julee/api/tests/routers/test_assembly_specifications.py +749 -0
- julee/api/tests/routers/test_documents.py +301 -0
- julee/api/tests/routers/test_knowledge_service_configs.py +234 -0
- julee/api/tests/routers/test_knowledge_service_queries.py +738 -0
- julee/api/tests/routers/test_system.py +179 -0
- julee/api/tests/routers/test_workflows.py +393 -0
- julee/api/tests/test_app.py +285 -0
- julee/api/tests/test_dependencies.py +245 -0
- julee/api/tests/test_requests.py +250 -0
- julee/domain/__init__.py +22 -0
- julee/domain/models/__init__.py +49 -0
- julee/domain/models/assembly/__init__.py +17 -0
- julee/domain/models/assembly/assembly.py +103 -0
- julee/domain/models/assembly/tests/__init__.py +0 -0
- julee/domain/models/assembly/tests/factories.py +37 -0
- julee/domain/models/assembly/tests/test_assembly.py +430 -0
- julee/domain/models/assembly_specification/__init__.py +24 -0
- julee/domain/models/assembly_specification/assembly_specification.py +172 -0
- julee/domain/models/assembly_specification/knowledge_service_query.py +123 -0
- julee/domain/models/assembly_specification/tests/__init__.py +0 -0
- julee/domain/models/assembly_specification/tests/factories.py +78 -0
- julee/domain/models/assembly_specification/tests/test_assembly_specification.py +490 -0
- julee/domain/models/assembly_specification/tests/test_knowledge_service_query.py +310 -0
- julee/domain/models/custom_fields/__init__.py +0 -0
- julee/domain/models/custom_fields/content_stream.py +68 -0
- julee/domain/models/custom_fields/tests/__init__.py +0 -0
- julee/domain/models/custom_fields/tests/test_custom_fields.py +53 -0
- julee/domain/models/document/__init__.py +17 -0
- julee/domain/models/document/document.py +150 -0
- julee/domain/models/document/tests/__init__.py +0 -0
- julee/domain/models/document/tests/factories.py +76 -0
- julee/domain/models/document/tests/test_document.py +297 -0
- julee/domain/models/knowledge_service_config/__init__.py +17 -0
- julee/domain/models/knowledge_service_config/knowledge_service_config.py +86 -0
- julee/domain/models/policy/__init__.py +15 -0
- julee/domain/models/policy/document_policy_validation.py +220 -0
- julee/domain/models/policy/policy.py +203 -0
- julee/domain/models/policy/tests/__init__.py +0 -0
- julee/domain/models/policy/tests/factories.py +47 -0
- julee/domain/models/policy/tests/test_document_policy_validation.py +420 -0
- julee/domain/models/policy/tests/test_policy.py +546 -0
- julee/domain/repositories/__init__.py +27 -0
- julee/domain/repositories/assembly.py +45 -0
- julee/domain/repositories/assembly_specification.py +52 -0
- julee/domain/repositories/base.py +146 -0
- julee/domain/repositories/document.py +49 -0
- julee/domain/repositories/document_policy_validation.py +52 -0
- julee/domain/repositories/knowledge_service_config.py +54 -0
- julee/domain/repositories/knowledge_service_query.py +44 -0
- julee/domain/repositories/policy.py +49 -0
- julee/domain/use_cases/__init__.py +17 -0
- julee/domain/use_cases/decorators.py +107 -0
- julee/domain/use_cases/extract_assemble_data.py +649 -0
- julee/domain/use_cases/initialize_system_data.py +842 -0
- julee/domain/use_cases/tests/__init__.py +7 -0
- julee/domain/use_cases/tests/test_extract_assemble_data.py +548 -0
- julee/domain/use_cases/tests/test_initialize_system_data.py +455 -0
- julee/domain/use_cases/tests/test_validate_document.py +1228 -0
- julee/domain/use_cases/validate_document.py +736 -0
- julee/fixtures/assembly_specifications.yaml +70 -0
- julee/fixtures/documents.yaml +178 -0
- julee/fixtures/knowledge_service_configs.yaml +37 -0
- julee/fixtures/knowledge_service_queries.yaml +27 -0
- julee/repositories/__init__.py +17 -0
- julee/repositories/memory/__init__.py +31 -0
- julee/repositories/memory/assembly.py +84 -0
- julee/repositories/memory/assembly_specification.py +125 -0
- julee/repositories/memory/base.py +227 -0
- julee/repositories/memory/document.py +149 -0
- julee/repositories/memory/document_policy_validation.py +104 -0
- julee/repositories/memory/knowledge_service_config.py +123 -0
- julee/repositories/memory/knowledge_service_query.py +120 -0
- julee/repositories/memory/policy.py +87 -0
- julee/repositories/memory/tests/__init__.py +0 -0
- julee/repositories/memory/tests/test_document.py +212 -0
- julee/repositories/memory/tests/test_document_policy_validation.py +161 -0
- julee/repositories/memory/tests/test_policy.py +443 -0
- julee/repositories/minio/__init__.py +31 -0
- julee/repositories/minio/assembly.py +103 -0
- julee/repositories/minio/assembly_specification.py +170 -0
- julee/repositories/minio/client.py +570 -0
- julee/repositories/minio/document.py +530 -0
- julee/repositories/minio/document_policy_validation.py +120 -0
- julee/repositories/minio/knowledge_service_config.py +187 -0
- julee/repositories/minio/knowledge_service_query.py +211 -0
- julee/repositories/minio/policy.py +106 -0
- julee/repositories/minio/tests/__init__.py +0 -0
- julee/repositories/minio/tests/fake_client.py +213 -0
- julee/repositories/minio/tests/test_assembly.py +374 -0
- julee/repositories/minio/tests/test_assembly_specification.py +391 -0
- julee/repositories/minio/tests/test_client_protocol.py +57 -0
- julee/repositories/minio/tests/test_document.py +591 -0
- julee/repositories/minio/tests/test_document_policy_validation.py +192 -0
- julee/repositories/minio/tests/test_knowledge_service_config.py +374 -0
- julee/repositories/minio/tests/test_knowledge_service_query.py +438 -0
- julee/repositories/minio/tests/test_policy.py +559 -0
- julee/repositories/temporal/__init__.py +38 -0
- julee/repositories/temporal/activities.py +114 -0
- julee/repositories/temporal/activity_names.py +34 -0
- julee/repositories/temporal/proxies.py +159 -0
- julee/services/__init__.py +18 -0
- julee/services/knowledge_service/__init__.py +48 -0
- julee/services/knowledge_service/anthropic/__init__.py +12 -0
- julee/services/knowledge_service/anthropic/knowledge_service.py +331 -0
- julee/services/knowledge_service/anthropic/tests/test_knowledge_service.py +318 -0
- julee/services/knowledge_service/factory.py +138 -0
- julee/services/knowledge_service/knowledge_service.py +160 -0
- julee/services/knowledge_service/memory/__init__.py +13 -0
- julee/services/knowledge_service/memory/knowledge_service.py +278 -0
- julee/services/knowledge_service/memory/test_knowledge_service.py +345 -0
- julee/services/knowledge_service/test_factory.py +112 -0
- julee/services/temporal/__init__.py +38 -0
- julee/services/temporal/activities.py +86 -0
- julee/services/temporal/activity_names.py +22 -0
- julee/services/temporal/proxies.py +41 -0
- julee/util/__init__.py +0 -0
- julee/util/domain.py +119 -0
- julee/util/repos/__init__.py +0 -0
- julee/util/repos/minio/__init__.py +0 -0
- julee/util/repos/minio/file_storage.py +213 -0
- julee/util/repos/temporal/__init__.py +11 -0
- julee/util/repos/temporal/client_proxies/file_storage.py +68 -0
- julee/util/repos/temporal/data_converter.py +123 -0
- julee/util/repos/temporal/minio_file_storage.py +12 -0
- julee/util/repos/temporal/proxies/__init__.py +0 -0
- julee/util/repos/temporal/proxies/file_storage.py +58 -0
- julee/util/repositories.py +55 -0
- julee/util/temporal/__init__.py +22 -0
- julee/util/temporal/activities.py +123 -0
- julee/util/temporal/decorators.py +473 -0
- julee/util/tests/__init__.py +1 -0
- julee/util/tests/test_decorators.py +770 -0
- julee/util/validation/__init__.py +29 -0
- julee/util/validation/repository.py +100 -0
- julee/util/validation/type_guards.py +369 -0
- julee/worker.py +211 -0
- julee/workflows/__init__.py +26 -0
- julee/workflows/extract_assemble.py +215 -0
- julee/workflows/validate_document.py +228 -0
- julee-0.1.0.dist-info/METADATA +195 -0
- julee-0.1.0.dist-info/RECORD +161 -0
- julee-0.1.0.dist-info/WHEEL +5 -0
- julee-0.1.0.dist-info/licenses/LICENSE +674 -0
- julee-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Tests for documents API router.
|
|
3
|
+
|
|
4
|
+
This module provides unit tests for the documents API endpoints,
|
|
5
|
+
focusing on the core functionality of listing documents with pagination.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import pytest
|
|
9
|
+
from datetime import datetime, timezone
|
|
10
|
+
from typing import Generator
|
|
11
|
+
from fastapi.testclient import TestClient
|
|
12
|
+
from fastapi import FastAPI
|
|
13
|
+
from fastapi_pagination import add_pagination
|
|
14
|
+
|
|
15
|
+
from julee.api.routers.documents import router
|
|
16
|
+
from julee.api.dependencies import get_document_repository
|
|
17
|
+
from julee.domain.models.document import Document, DocumentStatus
|
|
18
|
+
from julee.repositories.memory import MemoryDocumentRepository
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@pytest.fixture
|
|
22
|
+
def memory_repo() -> MemoryDocumentRepository:
|
|
23
|
+
"""Create a memory document repository for testing."""
|
|
24
|
+
return MemoryDocumentRepository()
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@pytest.fixture
|
|
28
|
+
def app(memory_repo: MemoryDocumentRepository) -> FastAPI:
|
|
29
|
+
"""Create FastAPI app with documents router for testing."""
|
|
30
|
+
app = FastAPI()
|
|
31
|
+
|
|
32
|
+
# Override the dependency with our memory repository
|
|
33
|
+
app.dependency_overrides[get_document_repository] = lambda: memory_repo
|
|
34
|
+
|
|
35
|
+
# Add pagination support (required for the paginate function)
|
|
36
|
+
add_pagination(app)
|
|
37
|
+
|
|
38
|
+
app.include_router(router, prefix="/documents")
|
|
39
|
+
return app
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@pytest.fixture
|
|
43
|
+
def client(app: FastAPI) -> Generator[TestClient, None, None]:
|
|
44
|
+
"""Create test client."""
|
|
45
|
+
with TestClient(app) as test_client:
|
|
46
|
+
yield test_client
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@pytest.fixture
|
|
50
|
+
def sample_documents() -> list[Document]:
|
|
51
|
+
"""Create sample documents for testing."""
|
|
52
|
+
return [
|
|
53
|
+
Document(
|
|
54
|
+
document_id="doc-1",
|
|
55
|
+
original_filename="test-document-1.txt",
|
|
56
|
+
content_type="text/plain",
|
|
57
|
+
size_bytes=1024,
|
|
58
|
+
content_multihash="QmTest1",
|
|
59
|
+
status=DocumentStatus.CAPTURED,
|
|
60
|
+
created_at=datetime(2024, 1, 1, 12, 0, 0, tzinfo=timezone.utc),
|
|
61
|
+
updated_at=datetime(2024, 1, 1, 12, 0, 0, tzinfo=timezone.utc),
|
|
62
|
+
additional_metadata={"type": "test"},
|
|
63
|
+
content_string="test content",
|
|
64
|
+
),
|
|
65
|
+
Document(
|
|
66
|
+
document_id="doc-2",
|
|
67
|
+
original_filename="test-document-2.pdf",
|
|
68
|
+
content_type="application/pdf",
|
|
69
|
+
size_bytes=2048,
|
|
70
|
+
content_multihash="QmTest2",
|
|
71
|
+
status=DocumentStatus.REGISTERED,
|
|
72
|
+
created_at=datetime(2024, 1, 2, 12, 0, 0, tzinfo=timezone.utc),
|
|
73
|
+
updated_at=datetime(2024, 1, 2, 12, 0, 0, tzinfo=timezone.utc),
|
|
74
|
+
additional_metadata={"type": "report"},
|
|
75
|
+
content_string="pdf content",
|
|
76
|
+
),
|
|
77
|
+
]
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class TestListDocuments:
|
|
81
|
+
"""Test cases for the list documents endpoint."""
|
|
82
|
+
|
|
83
|
+
@pytest.mark.asyncio
|
|
84
|
+
async def test_list_documents_success(
|
|
85
|
+
self,
|
|
86
|
+
client: TestClient,
|
|
87
|
+
memory_repo: MemoryDocumentRepository,
|
|
88
|
+
sample_documents: list[Document],
|
|
89
|
+
) -> None:
|
|
90
|
+
"""Test successful document listing."""
|
|
91
|
+
# Setup - add documents to repository
|
|
92
|
+
for doc in sample_documents:
|
|
93
|
+
await memory_repo.save(doc)
|
|
94
|
+
|
|
95
|
+
# Make request
|
|
96
|
+
response = client.get("/documents/")
|
|
97
|
+
|
|
98
|
+
# Assertions
|
|
99
|
+
assert response.status_code == 200
|
|
100
|
+
data = response.json()
|
|
101
|
+
|
|
102
|
+
assert data["total"] == 2
|
|
103
|
+
assert data["page"] == 1
|
|
104
|
+
assert data["size"] == 50 # Default fastapi-pagination size
|
|
105
|
+
assert data["pages"] == 1
|
|
106
|
+
assert len(data["items"]) == 2
|
|
107
|
+
|
|
108
|
+
# Check first document (documents may not be in insertion order)
|
|
109
|
+
doc_ids = [item["document_id"] for item in data["items"]]
|
|
110
|
+
assert "doc-1" in doc_ids
|
|
111
|
+
assert "doc-2" in doc_ids
|
|
112
|
+
|
|
113
|
+
# Find doc-1 and verify its details
|
|
114
|
+
doc1 = next(item for item in data["items"] if item["document_id"] == "doc-1")
|
|
115
|
+
assert doc1["original_filename"] == "test-document-1.txt"
|
|
116
|
+
assert doc1["content_type"] == "text/plain"
|
|
117
|
+
assert doc1["size_bytes"] == 12 # Length of "test content"
|
|
118
|
+
assert doc1["status"] == "captured"
|
|
119
|
+
assert doc1["additional_metadata"] == {"type": "test"}
|
|
120
|
+
|
|
121
|
+
@pytest.mark.asyncio
|
|
122
|
+
async def test_list_documents_with_pagination(
|
|
123
|
+
self,
|
|
124
|
+
client: TestClient,
|
|
125
|
+
memory_repo: MemoryDocumentRepository,
|
|
126
|
+
sample_documents: list[Document],
|
|
127
|
+
) -> None:
|
|
128
|
+
"""Test document listing with custom pagination."""
|
|
129
|
+
# Setup - add documents to repository
|
|
130
|
+
for doc in sample_documents:
|
|
131
|
+
await memory_repo.save(doc)
|
|
132
|
+
|
|
133
|
+
# Make request with pagination
|
|
134
|
+
response = client.get("/documents/?page=1&size=1")
|
|
135
|
+
|
|
136
|
+
# Assertions
|
|
137
|
+
assert response.status_code == 200
|
|
138
|
+
data = response.json()
|
|
139
|
+
|
|
140
|
+
assert data["total"] == 2
|
|
141
|
+
assert data["page"] == 1
|
|
142
|
+
assert data["size"] == 1
|
|
143
|
+
assert data["pages"] == 2
|
|
144
|
+
assert len(data["items"]) == 1
|
|
145
|
+
|
|
146
|
+
def test_list_documents_empty_result(
|
|
147
|
+
self, client: TestClient, memory_repo: MemoryDocumentRepository
|
|
148
|
+
) -> None:
|
|
149
|
+
"""Test document listing when no documents exist."""
|
|
150
|
+
# No setup needed - memory repo starts empty
|
|
151
|
+
|
|
152
|
+
# Make request
|
|
153
|
+
response = client.get("/documents/")
|
|
154
|
+
|
|
155
|
+
# Assertions
|
|
156
|
+
assert response.status_code == 200
|
|
157
|
+
data = response.json()
|
|
158
|
+
|
|
159
|
+
assert data["total"] == 0
|
|
160
|
+
assert data["page"] == 1
|
|
161
|
+
assert data["size"] == 50 # Default fastapi-pagination size
|
|
162
|
+
assert data["pages"] == 0
|
|
163
|
+
assert len(data["items"]) == 0
|
|
164
|
+
|
|
165
|
+
def test_list_documents_invalid_page(self, client: TestClient) -> None:
|
|
166
|
+
"""Test document listing with invalid page parameter."""
|
|
167
|
+
response = client.get("/documents/?page=0")
|
|
168
|
+
assert response.status_code == 422 # Validation error
|
|
169
|
+
|
|
170
|
+
def test_list_documents_invalid_size(self, client: TestClient) -> None:
|
|
171
|
+
"""Test document listing with invalid size parameter."""
|
|
172
|
+
response = client.get("/documents/?size=101")
|
|
173
|
+
assert response.status_code == 422 # Validation error
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
class TestGetDocument:
|
|
177
|
+
"""Test cases for the get document metadata endpoint."""
|
|
178
|
+
|
|
179
|
+
@pytest.mark.asyncio
|
|
180
|
+
async def test_get_document_metadata_success(
|
|
181
|
+
self,
|
|
182
|
+
client: TestClient,
|
|
183
|
+
memory_repo: MemoryDocumentRepository,
|
|
184
|
+
sample_documents: list[Document],
|
|
185
|
+
) -> None:
|
|
186
|
+
"""Test successful document metadata retrieval."""
|
|
187
|
+
# Setup - add document to repository
|
|
188
|
+
doc = sample_documents[0]
|
|
189
|
+
await memory_repo.save(doc)
|
|
190
|
+
|
|
191
|
+
# Make request
|
|
192
|
+
response = client.get(f"/documents/{doc.document_id}")
|
|
193
|
+
|
|
194
|
+
# Assertions
|
|
195
|
+
assert response.status_code == 200
|
|
196
|
+
data = response.json()
|
|
197
|
+
|
|
198
|
+
assert data["document_id"] == doc.document_id
|
|
199
|
+
assert data["original_filename"] == doc.original_filename
|
|
200
|
+
assert data["content_type"] == doc.content_type
|
|
201
|
+
assert data["status"] == doc.status.value
|
|
202
|
+
assert data["additional_metadata"] == doc.additional_metadata
|
|
203
|
+
|
|
204
|
+
# Content should NOT be included in metadata endpoint
|
|
205
|
+
assert data["content_string"] is None
|
|
206
|
+
# Content field is excluded from JSON response
|
|
207
|
+
assert "content" not in data
|
|
208
|
+
|
|
209
|
+
@pytest.mark.asyncio
|
|
210
|
+
async def test_get_document_metadata_not_found(
|
|
211
|
+
self, client: TestClient, memory_repo: MemoryDocumentRepository
|
|
212
|
+
) -> None:
|
|
213
|
+
"""Test document metadata retrieval when document doesn't exist."""
|
|
214
|
+
response = client.get("/documents/nonexistent-id")
|
|
215
|
+
|
|
216
|
+
assert response.status_code == 404
|
|
217
|
+
data = response.json()
|
|
218
|
+
assert "not found" in data["detail"].lower()
|
|
219
|
+
|
|
220
|
+
def test_get_document_metadata_invalid_id_format(self, client: TestClient) -> None:
|
|
221
|
+
"""Test document metadata retrieval with invalid ID format."""
|
|
222
|
+
# Test with empty ID (should be handled by FastAPI path validation)
|
|
223
|
+
response = client.get("/documents/")
|
|
224
|
+
# This should hit the list endpoint instead
|
|
225
|
+
assert response.status_code == 200
|
|
226
|
+
|
|
227
|
+
# Test with very long ID
|
|
228
|
+
very_long_id = "x" * 1000
|
|
229
|
+
response = client.get(f"/documents/{very_long_id}")
|
|
230
|
+
assert response.status_code == 404 # Not found, but valid request
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
class TestGetDocumentContent:
|
|
234
|
+
"""Test cases for the get document content endpoint."""
|
|
235
|
+
|
|
236
|
+
@pytest.mark.asyncio
|
|
237
|
+
async def test_get_document_content_success(
|
|
238
|
+
self,
|
|
239
|
+
client: TestClient,
|
|
240
|
+
memory_repo: MemoryDocumentRepository,
|
|
241
|
+
sample_documents: list[Document],
|
|
242
|
+
) -> None:
|
|
243
|
+
"""Test successful document content retrieval."""
|
|
244
|
+
# Setup - add document to repository
|
|
245
|
+
doc = sample_documents[0]
|
|
246
|
+
await memory_repo.save(doc)
|
|
247
|
+
|
|
248
|
+
# Make request
|
|
249
|
+
response = client.get(f"/documents/{doc.document_id}/content")
|
|
250
|
+
|
|
251
|
+
# Assertions
|
|
252
|
+
assert response.status_code == 200
|
|
253
|
+
assert response.content.decode("utf-8") == "test content"
|
|
254
|
+
assert response.headers["content-type"].startswith(doc.content_type)
|
|
255
|
+
assert doc.original_filename in response.headers["content-disposition"]
|
|
256
|
+
|
|
257
|
+
@pytest.mark.asyncio
|
|
258
|
+
async def test_get_document_content_not_found(
|
|
259
|
+
self, client: TestClient, memory_repo: MemoryDocumentRepository
|
|
260
|
+
) -> None:
|
|
261
|
+
"""Test content retrieval when document doesn't exist."""
|
|
262
|
+
response = client.get("/documents/nonexistent-id/content")
|
|
263
|
+
|
|
264
|
+
assert response.status_code == 404
|
|
265
|
+
data = response.json()
|
|
266
|
+
assert "not found" in data["detail"].lower()
|
|
267
|
+
|
|
268
|
+
@pytest.mark.asyncio
|
|
269
|
+
async def test_get_document_content_no_content(
|
|
270
|
+
self,
|
|
271
|
+
client: TestClient,
|
|
272
|
+
memory_repo: MemoryDocumentRepository,
|
|
273
|
+
) -> None:
|
|
274
|
+
"""Test content retrieval when document has no content."""
|
|
275
|
+
# Create document with content_string first to pass validation
|
|
276
|
+
doc = Document(
|
|
277
|
+
document_id="doc-no-content",
|
|
278
|
+
original_filename="empty.txt",
|
|
279
|
+
content_type="text/plain",
|
|
280
|
+
size_bytes=1,
|
|
281
|
+
content_multihash="empty_hash",
|
|
282
|
+
status=DocumentStatus.CAPTURED,
|
|
283
|
+
additional_metadata={"type": "empty"},
|
|
284
|
+
content_string="temp",
|
|
285
|
+
)
|
|
286
|
+
|
|
287
|
+
# Save document normally, then manually remove content from storage
|
|
288
|
+
await memory_repo.save(doc)
|
|
289
|
+
stored_doc = memory_repo.storage_dict[doc.document_id]
|
|
290
|
+
# Remove content from the stored document
|
|
291
|
+
memory_repo.storage_dict[doc.document_id] = stored_doc.model_copy(
|
|
292
|
+
update={"content": None, "content_string": None}
|
|
293
|
+
)
|
|
294
|
+
|
|
295
|
+
# Make request
|
|
296
|
+
response = client.get(f"/documents/{doc.document_id}/content")
|
|
297
|
+
|
|
298
|
+
# Assertions
|
|
299
|
+
assert response.status_code == 422
|
|
300
|
+
data = response.json()
|
|
301
|
+
assert "has no content" in data["detail"].lower()
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Tests for knowledge service configurations API endpoints.
|
|
3
|
+
|
|
4
|
+
This module tests the API endpoints for knowledge service configurations,
|
|
5
|
+
ensuring they follow consistent patterns with proper error handling,
|
|
6
|
+
pagination, and response formats.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import pytest
|
|
10
|
+
from unittest.mock import AsyncMock
|
|
11
|
+
from fastapi.testclient import TestClient
|
|
12
|
+
from typing import Generator
|
|
13
|
+
|
|
14
|
+
from julee.api.app import app
|
|
15
|
+
from julee.domain.models.knowledge_service_config import (
|
|
16
|
+
KnowledgeServiceConfig,
|
|
17
|
+
ServiceApi,
|
|
18
|
+
)
|
|
19
|
+
from julee.api.dependencies import (
|
|
20
|
+
get_knowledge_service_config_repository,
|
|
21
|
+
)
|
|
22
|
+
from datetime import datetime, timezone
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@pytest.fixture
|
|
26
|
+
def mock_repository() -> AsyncMock:
|
|
27
|
+
"""Create mock knowledge service config repository."""
|
|
28
|
+
return AsyncMock()
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@pytest.fixture
|
|
32
|
+
def client(mock_repository: AsyncMock) -> Generator[TestClient, None, None]:
|
|
33
|
+
"""Create test client with mocked dependencies."""
|
|
34
|
+
app.dependency_overrides[get_knowledge_service_config_repository] = (
|
|
35
|
+
lambda: mock_repository
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
with TestClient(app) as test_client:
|
|
39
|
+
yield test_client
|
|
40
|
+
|
|
41
|
+
# Clean up
|
|
42
|
+
app.dependency_overrides.clear()
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@pytest.fixture
|
|
46
|
+
def sample_configs() -> list[KnowledgeServiceConfig]:
|
|
47
|
+
"""Sample knowledge service configurations for testing."""
|
|
48
|
+
return [
|
|
49
|
+
KnowledgeServiceConfig(
|
|
50
|
+
knowledge_service_id="anthropic-claude",
|
|
51
|
+
name="Anthropic Claude",
|
|
52
|
+
description="Claude 3 for general text analysis and extraction",
|
|
53
|
+
service_api=ServiceApi.ANTHROPIC,
|
|
54
|
+
created_at=datetime(2024, 1, 1, 12, 0, 0, tzinfo=timezone.utc),
|
|
55
|
+
updated_at=datetime(2024, 1, 1, 12, 0, 0, tzinfo=timezone.utc),
|
|
56
|
+
),
|
|
57
|
+
KnowledgeServiceConfig(
|
|
58
|
+
knowledge_service_id="openai-gpt4",
|
|
59
|
+
name="OpenAI GPT-4",
|
|
60
|
+
description="GPT-4 for comprehensive text understanding",
|
|
61
|
+
service_api=ServiceApi.ANTHROPIC, # Only enum value available
|
|
62
|
+
created_at=datetime(2024, 1, 1, 12, 0, 0, tzinfo=timezone.utc),
|
|
63
|
+
updated_at=datetime(2024, 1, 1, 12, 0, 0, tzinfo=timezone.utc),
|
|
64
|
+
),
|
|
65
|
+
KnowledgeServiceConfig(
|
|
66
|
+
knowledge_service_id="memory-service",
|
|
67
|
+
name="Memory Service",
|
|
68
|
+
description="In-memory service for testing and development",
|
|
69
|
+
service_api=ServiceApi.ANTHROPIC, # Only enum value available
|
|
70
|
+
created_at=datetime(2024, 1, 1, 12, 0, 0, tzinfo=timezone.utc),
|
|
71
|
+
updated_at=datetime(2024, 1, 1, 12, 0, 0, tzinfo=timezone.utc),
|
|
72
|
+
),
|
|
73
|
+
]
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class TestGetKnowledgeServiceConfigs:
|
|
77
|
+
"""Test GET /knowledge_service_configs/ endpoint."""
|
|
78
|
+
|
|
79
|
+
def test_get_configs_success(
|
|
80
|
+
self,
|
|
81
|
+
client: TestClient,
|
|
82
|
+
mock_repository: AsyncMock,
|
|
83
|
+
sample_configs: list[KnowledgeServiceConfig],
|
|
84
|
+
) -> None:
|
|
85
|
+
"""Test successful retrieval of knowledge service configurations."""
|
|
86
|
+
# Setup mock
|
|
87
|
+
mock_repository.list_all.return_value = sample_configs
|
|
88
|
+
|
|
89
|
+
# Make request
|
|
90
|
+
response = client.get("/knowledge_service_configs/")
|
|
91
|
+
|
|
92
|
+
# Assert response
|
|
93
|
+
assert response.status_code == 200
|
|
94
|
+
data = response.json()
|
|
95
|
+
|
|
96
|
+
# Check pagination structure
|
|
97
|
+
assert "items" in data
|
|
98
|
+
assert "total" in data
|
|
99
|
+
assert "page" in data
|
|
100
|
+
assert "size" in data
|
|
101
|
+
|
|
102
|
+
# Check data content
|
|
103
|
+
assert len(data["items"]) == 3
|
|
104
|
+
assert data["total"] == 3
|
|
105
|
+
|
|
106
|
+
# Verify first config details
|
|
107
|
+
first_config = data["items"][0]
|
|
108
|
+
assert first_config["knowledge_service_id"] == "anthropic-claude"
|
|
109
|
+
assert first_config["name"] == "Anthropic Claude"
|
|
110
|
+
assert (
|
|
111
|
+
first_config["description"]
|
|
112
|
+
== "Claude 3 for general text analysis and extraction"
|
|
113
|
+
)
|
|
114
|
+
assert first_config["service_api"] == "anthropic"
|
|
115
|
+
|
|
116
|
+
# Verify repository was called
|
|
117
|
+
mock_repository.list_all.assert_called_once()
|
|
118
|
+
|
|
119
|
+
def test_get_configs_empty_list(
|
|
120
|
+
self, client: TestClient, mock_repository: AsyncMock
|
|
121
|
+
) -> None:
|
|
122
|
+
"""Test successful retrieval when no configurations exist."""
|
|
123
|
+
# Setup mock
|
|
124
|
+
mock_repository.list_all.return_value = []
|
|
125
|
+
|
|
126
|
+
# Make request
|
|
127
|
+
response = client.get("/knowledge_service_configs/")
|
|
128
|
+
|
|
129
|
+
# Assert response
|
|
130
|
+
assert response.status_code == 200
|
|
131
|
+
data = response.json()
|
|
132
|
+
|
|
133
|
+
assert data["items"] == []
|
|
134
|
+
assert data["total"] == 0
|
|
135
|
+
|
|
136
|
+
# Verify repository was called
|
|
137
|
+
mock_repository.list_all.assert_called_once()
|
|
138
|
+
|
|
139
|
+
def test_get_configs_single_config(
|
|
140
|
+
self,
|
|
141
|
+
client: TestClient,
|
|
142
|
+
mock_repository: AsyncMock,
|
|
143
|
+
sample_configs: list[KnowledgeServiceConfig],
|
|
144
|
+
) -> None:
|
|
145
|
+
"""Test successful retrieval with a single configuration."""
|
|
146
|
+
# Setup mock with single config
|
|
147
|
+
single_config = [sample_configs[0]]
|
|
148
|
+
mock_repository.list_all.return_value = single_config
|
|
149
|
+
|
|
150
|
+
# Make request
|
|
151
|
+
response = client.get("/knowledge_service_configs/")
|
|
152
|
+
|
|
153
|
+
# Assert response
|
|
154
|
+
assert response.status_code == 200
|
|
155
|
+
data = response.json()
|
|
156
|
+
|
|
157
|
+
assert len(data["items"]) == 1
|
|
158
|
+
assert data["total"] == 1
|
|
159
|
+
assert data["items"][0]["knowledge_service_id"] == "anthropic-claude"
|
|
160
|
+
|
|
161
|
+
# Verify repository was called
|
|
162
|
+
mock_repository.list_all.assert_called_once()
|
|
163
|
+
|
|
164
|
+
def test_get_configs_repository_error(
|
|
165
|
+
self, client: TestClient, mock_repository: AsyncMock
|
|
166
|
+
) -> None:
|
|
167
|
+
"""Test handling of repository errors."""
|
|
168
|
+
# Setup mock to raise exception
|
|
169
|
+
mock_repository.list_all.side_effect = Exception("Database connection failed")
|
|
170
|
+
|
|
171
|
+
# Make request
|
|
172
|
+
response = client.get("/knowledge_service_configs/")
|
|
173
|
+
|
|
174
|
+
# Assert error response
|
|
175
|
+
assert response.status_code == 500
|
|
176
|
+
data = response.json()
|
|
177
|
+
assert "detail" in data
|
|
178
|
+
assert "internal error" in data["detail"].lower()
|
|
179
|
+
|
|
180
|
+
# Verify repository was called
|
|
181
|
+
mock_repository.list_all.assert_called_once()
|
|
182
|
+
|
|
183
|
+
def test_get_configs_response_structure(
|
|
184
|
+
self,
|
|
185
|
+
client: TestClient,
|
|
186
|
+
mock_repository: AsyncMock,
|
|
187
|
+
sample_configs: list[KnowledgeServiceConfig],
|
|
188
|
+
) -> None:
|
|
189
|
+
"""Test that response follows expected pagination structure."""
|
|
190
|
+
# Setup mock
|
|
191
|
+
mock_repository.list_all.return_value = sample_configs
|
|
192
|
+
|
|
193
|
+
# Make request
|
|
194
|
+
response = client.get("/knowledge_service_configs/")
|
|
195
|
+
|
|
196
|
+
# Assert response structure
|
|
197
|
+
assert response.status_code == 200
|
|
198
|
+
data = response.json()
|
|
199
|
+
|
|
200
|
+
# Check required pagination fields
|
|
201
|
+
required_fields = ["items", "total", "page", "size", "pages"]
|
|
202
|
+
for field in required_fields:
|
|
203
|
+
assert field in data, f"Missing required field: {field}"
|
|
204
|
+
|
|
205
|
+
# Check item structure
|
|
206
|
+
if data["items"]:
|
|
207
|
+
item = data["items"][0]
|
|
208
|
+
required_item_fields = [
|
|
209
|
+
"knowledge_service_id",
|
|
210
|
+
"name",
|
|
211
|
+
"description",
|
|
212
|
+
"service_api",
|
|
213
|
+
"created_at",
|
|
214
|
+
"updated_at",
|
|
215
|
+
]
|
|
216
|
+
for field in required_item_fields:
|
|
217
|
+
assert field in item, f"Missing required item field: {field}"
|
|
218
|
+
|
|
219
|
+
def test_get_configs_content_type(
|
|
220
|
+
self,
|
|
221
|
+
client: TestClient,
|
|
222
|
+
mock_repository: AsyncMock,
|
|
223
|
+
sample_configs: list[KnowledgeServiceConfig],
|
|
224
|
+
) -> None:
|
|
225
|
+
"""Test that response has correct content type."""
|
|
226
|
+
# Setup mock
|
|
227
|
+
mock_repository.list_all.return_value = sample_configs
|
|
228
|
+
|
|
229
|
+
# Make request
|
|
230
|
+
response = client.get("/knowledge_service_configs/")
|
|
231
|
+
|
|
232
|
+
# Assert content type
|
|
233
|
+
assert response.status_code == 200
|
|
234
|
+
assert "application/json" in response.headers["content-type"]
|