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,285 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Tests for the julee FastAPI application.
|
|
3
|
+
|
|
4
|
+
This module provides tests for the API endpoints, focusing on testing the
|
|
5
|
+
HTTP layer behavior with proper dependency injection and mocking patterns.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import pytest
|
|
9
|
+
from typing import Generator
|
|
10
|
+
from unittest.mock import patch
|
|
11
|
+
from fastapi.testclient import TestClient
|
|
12
|
+
|
|
13
|
+
from julee.api.app import app
|
|
14
|
+
from julee.api.dependencies import (
|
|
15
|
+
get_knowledge_service_query_repository,
|
|
16
|
+
get_knowledge_service_config_repository,
|
|
17
|
+
)
|
|
18
|
+
from julee.domain.models import KnowledgeServiceQuery
|
|
19
|
+
from julee.repositories.memory import (
|
|
20
|
+
MemoryKnowledgeServiceQueryRepository,
|
|
21
|
+
)
|
|
22
|
+
from julee.repositories.memory.knowledge_service_config import (
|
|
23
|
+
MemoryKnowledgeServiceConfigRepository,
|
|
24
|
+
)
|
|
25
|
+
from julee.api.responses import ServiceStatus
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@pytest.fixture
|
|
29
|
+
def memory_repo() -> MemoryKnowledgeServiceQueryRepository:
|
|
30
|
+
"""Create a memory knowledge service query repository for testing."""
|
|
31
|
+
return MemoryKnowledgeServiceQueryRepository()
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@pytest.fixture
|
|
35
|
+
def memory_config_repo() -> MemoryKnowledgeServiceConfigRepository:
|
|
36
|
+
"""Create a memory knowledge service config repository for testing."""
|
|
37
|
+
return MemoryKnowledgeServiceConfigRepository()
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@pytest.fixture
|
|
41
|
+
def client(
|
|
42
|
+
memory_repo: MemoryKnowledgeServiceQueryRepository,
|
|
43
|
+
memory_config_repo: MemoryKnowledgeServiceConfigRepository,
|
|
44
|
+
) -> Generator[TestClient, None, None]:
|
|
45
|
+
"""Create a test client with memory repository."""
|
|
46
|
+
# Override the dependencies with our memory repositories
|
|
47
|
+
app.dependency_overrides[get_knowledge_service_query_repository] = (
|
|
48
|
+
lambda: memory_repo
|
|
49
|
+
)
|
|
50
|
+
app.dependency_overrides[get_knowledge_service_config_repository] = (
|
|
51
|
+
lambda: memory_config_repo
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
with (
|
|
55
|
+
patch("julee.api.routers.system.check_temporal_health") as mock_temporal,
|
|
56
|
+
patch("julee.api.routers.system.check_storage_health") as mock_storage,
|
|
57
|
+
):
|
|
58
|
+
# Mock health checks to return UP status
|
|
59
|
+
mock_temporal.return_value = ServiceStatus.UP
|
|
60
|
+
mock_storage.return_value = ServiceStatus.UP
|
|
61
|
+
|
|
62
|
+
with TestClient(app) as test_client:
|
|
63
|
+
yield test_client
|
|
64
|
+
|
|
65
|
+
# Clean up the overrides after the test
|
|
66
|
+
app.dependency_overrides.clear()
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
@pytest.fixture
|
|
70
|
+
def sample_knowledge_service_query() -> KnowledgeServiceQuery:
|
|
71
|
+
"""Create a sample knowledge service query for testing."""
|
|
72
|
+
return KnowledgeServiceQuery(
|
|
73
|
+
query_id="test-query-123",
|
|
74
|
+
name="Extract Meeting Summary",
|
|
75
|
+
knowledge_service_id="anthropic-claude",
|
|
76
|
+
prompt="Extract the main summary from this meeting transcript",
|
|
77
|
+
query_metadata={"model": "claude-3", "temperature": 0.2},
|
|
78
|
+
assistant_prompt="Please format as JSON",
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class TestHealthEndpoint:
|
|
83
|
+
"""Test the health check endpoint."""
|
|
84
|
+
|
|
85
|
+
def test_health_check(self, client: TestClient) -> None:
|
|
86
|
+
"""Test that health check returns expected response."""
|
|
87
|
+
response = client.get("/health")
|
|
88
|
+
|
|
89
|
+
assert response.status_code == 200
|
|
90
|
+
data = response.json()
|
|
91
|
+
assert data["status"] == "healthy"
|
|
92
|
+
assert "timestamp" in data
|
|
93
|
+
assert "services" in data
|
|
94
|
+
assert data["services"]["api"] == "up"
|
|
95
|
+
assert data["services"]["temporal"] == "up"
|
|
96
|
+
assert data["services"]["storage"] == "up"
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
class TestKnowledgeServiceQueriesEndpoint:
|
|
100
|
+
"""Test the knowledge service queries endpoint."""
|
|
101
|
+
|
|
102
|
+
def test_get_knowledge_service_queries_empty_list(
|
|
103
|
+
self,
|
|
104
|
+
client: TestClient,
|
|
105
|
+
memory_repo: MemoryKnowledgeServiceQueryRepository,
|
|
106
|
+
) -> None:
|
|
107
|
+
"""Test getting queries when repository is empty."""
|
|
108
|
+
# Memory repository starts empty
|
|
109
|
+
# Note: Current implementation returns empty list as placeholder,
|
|
110
|
+
# this test verifies the endpoint structure works
|
|
111
|
+
|
|
112
|
+
response = client.get("/knowledge_service_queries")
|
|
113
|
+
|
|
114
|
+
assert response.status_code == 200
|
|
115
|
+
data = response.json()
|
|
116
|
+
|
|
117
|
+
# Verify pagination structure
|
|
118
|
+
assert "items" in data
|
|
119
|
+
assert "total" in data
|
|
120
|
+
assert "page" in data
|
|
121
|
+
assert "size" in data
|
|
122
|
+
assert "pages" in data
|
|
123
|
+
|
|
124
|
+
# Should return empty list when repository is empty
|
|
125
|
+
assert data["items"] == []
|
|
126
|
+
assert data["total"] == 0
|
|
127
|
+
|
|
128
|
+
def test_get_knowledge_service_queries_with_pagination_params(
|
|
129
|
+
self,
|
|
130
|
+
client: TestClient,
|
|
131
|
+
memory_repo: MemoryKnowledgeServiceQueryRepository,
|
|
132
|
+
) -> None:
|
|
133
|
+
"""Test getting queries with pagination parameters."""
|
|
134
|
+
response = client.get("/knowledge_service_queries?page=2&size=10")
|
|
135
|
+
|
|
136
|
+
assert response.status_code == 200
|
|
137
|
+
data = response.json()
|
|
138
|
+
|
|
139
|
+
# Verify pagination parameters are handled
|
|
140
|
+
assert "items" in data
|
|
141
|
+
assert "page" in data
|
|
142
|
+
assert "size" in data
|
|
143
|
+
|
|
144
|
+
# Even with pagination params, should work with empty repository
|
|
145
|
+
assert data["items"] == []
|
|
146
|
+
|
|
147
|
+
def test_knowledge_service_queries_endpoint_error_handling(
|
|
148
|
+
self,
|
|
149
|
+
client: TestClient,
|
|
150
|
+
memory_repo: MemoryKnowledgeServiceQueryRepository,
|
|
151
|
+
) -> None:
|
|
152
|
+
"""Test error handling in the queries endpoint."""
|
|
153
|
+
response = client.get("/knowledge_service_queries")
|
|
154
|
+
assert response.status_code == 200
|
|
155
|
+
|
|
156
|
+
# Test passes if no exceptions are raised during repository calls
|
|
157
|
+
|
|
158
|
+
def test_openapi_schema_includes_knowledge_service_queries(
|
|
159
|
+
self, client: TestClient
|
|
160
|
+
) -> None:
|
|
161
|
+
"""Test that the OpenAPI schema includes our endpoint."""
|
|
162
|
+
response = client.get("/openapi.json")
|
|
163
|
+
|
|
164
|
+
assert response.status_code == 200
|
|
165
|
+
openapi_schema = response.json()
|
|
166
|
+
|
|
167
|
+
# Verify our endpoint is in the schema
|
|
168
|
+
paths = openapi_schema.get("paths", {})
|
|
169
|
+
assert "/knowledge_service_queries/" in paths
|
|
170
|
+
|
|
171
|
+
# Verify the endpoint has GET method
|
|
172
|
+
endpoint = paths["/knowledge_service_queries/"]
|
|
173
|
+
assert "get" in endpoint
|
|
174
|
+
|
|
175
|
+
# Verify response model is defined
|
|
176
|
+
get_info = endpoint["get"]
|
|
177
|
+
assert "responses" in get_info
|
|
178
|
+
assert "200" in get_info["responses"]
|
|
179
|
+
|
|
180
|
+
async def test_repository_can_store_and_retrieve_queries(
|
|
181
|
+
self,
|
|
182
|
+
memory_repo: MemoryKnowledgeServiceQueryRepository,
|
|
183
|
+
sample_knowledge_service_query: KnowledgeServiceQuery,
|
|
184
|
+
) -> None:
|
|
185
|
+
"""Test that the memory repository can store and retrieve queries.
|
|
186
|
+
|
|
187
|
+
This demonstrates how the endpoint will work once list_all() is added.
|
|
188
|
+
"""
|
|
189
|
+
# Save a query to the repository
|
|
190
|
+
await memory_repo.save(sample_knowledge_service_query)
|
|
191
|
+
|
|
192
|
+
# Verify it can be retrieved
|
|
193
|
+
retrieved = await memory_repo.get(sample_knowledge_service_query.query_id)
|
|
194
|
+
assert retrieved == sample_knowledge_service_query
|
|
195
|
+
|
|
196
|
+
# This shows we can store and retrieve queries from the repository
|
|
197
|
+
|
|
198
|
+
async def test_get_knowledge_service_queries_with_data(
|
|
199
|
+
self,
|
|
200
|
+
client: TestClient,
|
|
201
|
+
memory_repo: MemoryKnowledgeServiceQueryRepository,
|
|
202
|
+
sample_knowledge_service_query: KnowledgeServiceQuery,
|
|
203
|
+
) -> None:
|
|
204
|
+
"""Test getting queries when repository contains data."""
|
|
205
|
+
# Create a second query for testing
|
|
206
|
+
query2 = KnowledgeServiceQuery(
|
|
207
|
+
query_id="test-query-456",
|
|
208
|
+
name="Extract Attendees",
|
|
209
|
+
knowledge_service_id="openai-service",
|
|
210
|
+
prompt="Extract all attendees from this meeting",
|
|
211
|
+
query_metadata={"model": "gpt-4", "temperature": 0.1},
|
|
212
|
+
assistant_prompt="Format as JSON array",
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
# Save queries to the repository
|
|
216
|
+
await memory_repo.save(sample_knowledge_service_query)
|
|
217
|
+
await memory_repo.save(query2)
|
|
218
|
+
|
|
219
|
+
response = client.get("/knowledge_service_queries")
|
|
220
|
+
|
|
221
|
+
assert response.status_code == 200
|
|
222
|
+
data = response.json()
|
|
223
|
+
|
|
224
|
+
# Verify pagination structure
|
|
225
|
+
assert "items" in data
|
|
226
|
+
assert "total" in data
|
|
227
|
+
assert "page" in data
|
|
228
|
+
assert "size" in data
|
|
229
|
+
|
|
230
|
+
# Should return both queries
|
|
231
|
+
assert data["total"] == 2
|
|
232
|
+
assert len(data["items"]) == 2
|
|
233
|
+
|
|
234
|
+
# Verify the queries are returned (order may vary)
|
|
235
|
+
returned_ids = {item["query_id"] for item in data["items"]}
|
|
236
|
+
expected_ids = {
|
|
237
|
+
sample_knowledge_service_query.query_id,
|
|
238
|
+
query2.query_id,
|
|
239
|
+
}
|
|
240
|
+
assert returned_ids == expected_ids
|
|
241
|
+
|
|
242
|
+
# Verify query data structure
|
|
243
|
+
for item in data["items"]:
|
|
244
|
+
assert "query_id" in item
|
|
245
|
+
assert "name" in item
|
|
246
|
+
assert "knowledge_service_id" in item
|
|
247
|
+
assert "prompt" in item
|
|
248
|
+
|
|
249
|
+
async def test_get_knowledge_service_queries_pagination(
|
|
250
|
+
self,
|
|
251
|
+
client: TestClient,
|
|
252
|
+
memory_repo: MemoryKnowledgeServiceQueryRepository,
|
|
253
|
+
) -> None:
|
|
254
|
+
"""Test pagination with multiple queries."""
|
|
255
|
+
# Create several queries
|
|
256
|
+
queries = []
|
|
257
|
+
for i in range(5):
|
|
258
|
+
query = KnowledgeServiceQuery(
|
|
259
|
+
query_id=f"query-{i:03d}",
|
|
260
|
+
name=f"Query {i}",
|
|
261
|
+
knowledge_service_id="test-service",
|
|
262
|
+
prompt=f"Test prompt {i}",
|
|
263
|
+
)
|
|
264
|
+
queries.append(query)
|
|
265
|
+
await memory_repo.save(query)
|
|
266
|
+
|
|
267
|
+
# Test first page with size 2
|
|
268
|
+
response = client.get("/knowledge_service_queries?page=1&size=2")
|
|
269
|
+
assert response.status_code == 200
|
|
270
|
+
data = response.json()
|
|
271
|
+
|
|
272
|
+
assert data["total"] == 5
|
|
273
|
+
assert data["page"] == 1
|
|
274
|
+
assert data["size"] == 2
|
|
275
|
+
assert len(data["items"]) == 2
|
|
276
|
+
|
|
277
|
+
# Test second page
|
|
278
|
+
response = client.get("/knowledge_service_queries?page=2&size=2")
|
|
279
|
+
assert response.status_code == 200
|
|
280
|
+
data = response.json()
|
|
281
|
+
|
|
282
|
+
assert data["total"] == 5
|
|
283
|
+
assert data["page"] == 2
|
|
284
|
+
assert data["size"] == 2
|
|
285
|
+
assert len(data["items"]) == 2
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Tests for dependency injection components.
|
|
3
|
+
|
|
4
|
+
This module tests the dependency injection utilities, particularly the
|
|
5
|
+
StartupDependenciesProvider that provides clean access to dependencies
|
|
6
|
+
during application startup without exposing internal container details.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import pytest
|
|
10
|
+
from unittest.mock import AsyncMock, MagicMock
|
|
11
|
+
|
|
12
|
+
from julee.api.dependencies import (
|
|
13
|
+
StartupDependenciesProvider,
|
|
14
|
+
DependencyContainer,
|
|
15
|
+
get_startup_dependencies,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@pytest.fixture
|
|
20
|
+
def mock_container() -> AsyncMock:
|
|
21
|
+
"""Create mock dependency container."""
|
|
22
|
+
return AsyncMock(spec=DependencyContainer)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@pytest.fixture
|
|
26
|
+
def mock_minio_client() -> MagicMock:
|
|
27
|
+
"""Create mock Minio client."""
|
|
28
|
+
return MagicMock()
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@pytest.fixture
|
|
32
|
+
def startup_provider(
|
|
33
|
+
mock_container: AsyncMock,
|
|
34
|
+
) -> StartupDependenciesProvider:
|
|
35
|
+
"""Create startup dependencies provider with mock container."""
|
|
36
|
+
return StartupDependenciesProvider(mock_container)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class TestStartupDependenciesProvider:
|
|
40
|
+
"""Test the StartupDependenciesProvider."""
|
|
41
|
+
|
|
42
|
+
def test_initialization(self, mock_container: AsyncMock) -> None:
|
|
43
|
+
"""Test provider initialization."""
|
|
44
|
+
provider = StartupDependenciesProvider(mock_container)
|
|
45
|
+
|
|
46
|
+
assert provider.container == mock_container
|
|
47
|
+
assert provider.logger is not None
|
|
48
|
+
|
|
49
|
+
@pytest.mark.asyncio
|
|
50
|
+
async def test_get_knowledge_service_config_repository(
|
|
51
|
+
self,
|
|
52
|
+
startup_provider: StartupDependenciesProvider,
|
|
53
|
+
mock_container: AsyncMock,
|
|
54
|
+
mock_minio_client: MagicMock,
|
|
55
|
+
) -> None:
|
|
56
|
+
"""Test getting knowledge service config repository."""
|
|
57
|
+
# Setup mock
|
|
58
|
+
mock_container.get_minio_client.return_value = mock_minio_client
|
|
59
|
+
|
|
60
|
+
# Get repository
|
|
61
|
+
repo = await startup_provider.get_knowledge_service_config_repository()
|
|
62
|
+
|
|
63
|
+
# Verify container was called
|
|
64
|
+
mock_container.get_minio_client.assert_called_once()
|
|
65
|
+
|
|
66
|
+
# Verify repository was created with correct client
|
|
67
|
+
assert repo is not None
|
|
68
|
+
# Note: We can't easily test the internal client without exposing
|
|
69
|
+
# implementation details, but we can verify the method completed
|
|
70
|
+
|
|
71
|
+
@pytest.mark.asyncio
|
|
72
|
+
async def test_get_system_initialization_service(
|
|
73
|
+
self,
|
|
74
|
+
startup_provider: StartupDependenciesProvider,
|
|
75
|
+
mock_container: AsyncMock,
|
|
76
|
+
mock_minio_client: MagicMock,
|
|
77
|
+
) -> None:
|
|
78
|
+
"""Test getting system initialization service."""
|
|
79
|
+
# Setup mock
|
|
80
|
+
mock_container.get_minio_client.return_value = mock_minio_client
|
|
81
|
+
|
|
82
|
+
# Get service
|
|
83
|
+
service = await startup_provider.get_system_initialization_service()
|
|
84
|
+
|
|
85
|
+
# Verify service was created
|
|
86
|
+
assert service is not None
|
|
87
|
+
assert hasattr(service, "initialize")
|
|
88
|
+
|
|
89
|
+
# Verify container was called to create dependencies
|
|
90
|
+
# The service may need multiple minio clients for different repos
|
|
91
|
+
assert mock_container.get_minio_client.call_count >= 1
|
|
92
|
+
|
|
93
|
+
@pytest.mark.asyncio
|
|
94
|
+
async def test_get_system_initialization_service_creates_full_chain(
|
|
95
|
+
self,
|
|
96
|
+
startup_provider: StartupDependenciesProvider,
|
|
97
|
+
mock_container: AsyncMock,
|
|
98
|
+
mock_minio_client: MagicMock,
|
|
99
|
+
) -> None:
|
|
100
|
+
"""Test that service creation builds the complete dependency chain."""
|
|
101
|
+
# Setup mock
|
|
102
|
+
mock_container.get_minio_client.return_value = mock_minio_client
|
|
103
|
+
|
|
104
|
+
# Get service
|
|
105
|
+
service = await startup_provider.get_system_initialization_service()
|
|
106
|
+
|
|
107
|
+
# Verify the service has the expected structure
|
|
108
|
+
assert service is not None
|
|
109
|
+
assert hasattr(service, "initialize_system_data_use_case")
|
|
110
|
+
assert service.initialize_system_data_use_case is not None
|
|
111
|
+
|
|
112
|
+
# Verify the use case has the repositories
|
|
113
|
+
use_case = service.initialize_system_data_use_case
|
|
114
|
+
assert hasattr(use_case, "config_repo")
|
|
115
|
+
assert use_case.config_repo is not None
|
|
116
|
+
assert hasattr(use_case, "document_repo")
|
|
117
|
+
assert use_case.document_repo is not None
|
|
118
|
+
assert hasattr(use_case, "query_repo")
|
|
119
|
+
assert use_case.query_repo is not None
|
|
120
|
+
assert hasattr(use_case, "assembly_spec_repo")
|
|
121
|
+
assert use_case.assembly_spec_repo is not None
|
|
122
|
+
|
|
123
|
+
@pytest.mark.asyncio
|
|
124
|
+
async def test_container_error_propagation(
|
|
125
|
+
self,
|
|
126
|
+
startup_provider: StartupDependenciesProvider,
|
|
127
|
+
mock_container: AsyncMock,
|
|
128
|
+
) -> None:
|
|
129
|
+
"""Test that container errors are properly propagated."""
|
|
130
|
+
# Setup mock to raise error
|
|
131
|
+
mock_container.get_minio_client.side_effect = Exception("Container error")
|
|
132
|
+
|
|
133
|
+
# Verify error is propagated
|
|
134
|
+
with pytest.raises(Exception, match="Container error"):
|
|
135
|
+
await startup_provider.get_knowledge_service_config_repository()
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
class TestStartupDependenciesIntegration:
|
|
139
|
+
"""Integration tests for startup dependencies."""
|
|
140
|
+
|
|
141
|
+
@pytest.mark.asyncio
|
|
142
|
+
async def test_get_startup_dependencies_function(self) -> None:
|
|
143
|
+
"""Test the get_startup_dependencies function."""
|
|
144
|
+
provider = await get_startup_dependencies()
|
|
145
|
+
|
|
146
|
+
assert provider is not None
|
|
147
|
+
assert isinstance(provider, StartupDependenciesProvider)
|
|
148
|
+
assert provider.container is not None
|
|
149
|
+
|
|
150
|
+
@pytest.mark.asyncio
|
|
151
|
+
async def test_startup_dependencies_singleton_behavior(self) -> None:
|
|
152
|
+
"""Test that startup dependencies provider behaves as singleton."""
|
|
153
|
+
provider1 = await get_startup_dependencies()
|
|
154
|
+
provider2 = await get_startup_dependencies()
|
|
155
|
+
|
|
156
|
+
# Should be the same instance
|
|
157
|
+
assert provider1 is provider2
|
|
158
|
+
assert provider1.container is provider2.container
|
|
159
|
+
|
|
160
|
+
@pytest.mark.asyncio
|
|
161
|
+
async def test_end_to_end_dependency_creation(self) -> None:
|
|
162
|
+
"""Test complete end-to-end dependency creation flow."""
|
|
163
|
+
# This test verifies the complete flow works without mocking
|
|
164
|
+
# the internal dependencies (integration test style)
|
|
165
|
+
|
|
166
|
+
provider = await get_startup_dependencies()
|
|
167
|
+
|
|
168
|
+
# This should work without throwing errors
|
|
169
|
+
# (though it might fail if Minio isn't available, which is expected)
|
|
170
|
+
try:
|
|
171
|
+
service = await provider.get_system_initialization_service()
|
|
172
|
+
assert service is not None
|
|
173
|
+
|
|
174
|
+
# Verify the service has the expected methods
|
|
175
|
+
assert hasattr(service, "initialize")
|
|
176
|
+
assert hasattr(service, "get_initialization_status")
|
|
177
|
+
assert hasattr(service, "reinitialize")
|
|
178
|
+
|
|
179
|
+
except Exception as e:
|
|
180
|
+
# In test environments, Minio might not be available
|
|
181
|
+
# We just verify that the dependency chain is correctly structured
|
|
182
|
+
# and any errors are related to infrastructure, not our code
|
|
183
|
+
assert "minio" in str(e).lower() or "connection" in str(e).lower()
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
class TestStartupDependenciesProviderEdgeCases:
|
|
187
|
+
"""Test edge cases and error conditions."""
|
|
188
|
+
|
|
189
|
+
@pytest.mark.asyncio
|
|
190
|
+
async def test_multiple_repository_requests(
|
|
191
|
+
self,
|
|
192
|
+
startup_provider: StartupDependenciesProvider,
|
|
193
|
+
mock_container: AsyncMock,
|
|
194
|
+
mock_minio_client: MagicMock,
|
|
195
|
+
) -> None:
|
|
196
|
+
"""Test multiple requests for the same repository type."""
|
|
197
|
+
# Setup mock
|
|
198
|
+
mock_container.get_minio_client.return_value = mock_minio_client
|
|
199
|
+
|
|
200
|
+
# Get repository multiple times
|
|
201
|
+
repo1 = await startup_provider.get_knowledge_service_config_repository()
|
|
202
|
+
repo2 = await startup_provider.get_knowledge_service_config_repository()
|
|
203
|
+
|
|
204
|
+
# Each call should create a new repository instance
|
|
205
|
+
assert repo1 is not None
|
|
206
|
+
assert repo2 is not None
|
|
207
|
+
# They should be different instances (no caching at provider level)
|
|
208
|
+
assert repo1 is not repo2
|
|
209
|
+
|
|
210
|
+
# But container should be called each time (container handles caching)
|
|
211
|
+
assert mock_container.get_minio_client.call_count == 2
|
|
212
|
+
|
|
213
|
+
@pytest.mark.asyncio
|
|
214
|
+
async def test_service_creation_isolation(
|
|
215
|
+
self,
|
|
216
|
+
startup_provider: StartupDependenciesProvider,
|
|
217
|
+
mock_container: AsyncMock,
|
|
218
|
+
mock_minio_client: MagicMock,
|
|
219
|
+
) -> None:
|
|
220
|
+
"""Test that service creation doesn't interfere with operations."""
|
|
221
|
+
# Setup mock
|
|
222
|
+
mock_container.get_minio_client.return_value = mock_minio_client
|
|
223
|
+
|
|
224
|
+
# Get repository first
|
|
225
|
+
repo = await startup_provider.get_knowledge_service_config_repository()
|
|
226
|
+
|
|
227
|
+
# Then get service
|
|
228
|
+
service = await startup_provider.get_system_initialization_service()
|
|
229
|
+
|
|
230
|
+
# Both should be valid
|
|
231
|
+
assert repo is not None
|
|
232
|
+
assert service is not None
|
|
233
|
+
|
|
234
|
+
# Container should have been called multiple times:
|
|
235
|
+
# 1 for direct repo call + 4 for service (config + document + query +
|
|
236
|
+
# assembly spec repos)
|
|
237
|
+
assert mock_container.get_minio_client.call_count == 5
|
|
238
|
+
|
|
239
|
+
def test_provider_with_none_container(self) -> None:
|
|
240
|
+
"""Test provider behavior with None container."""
|
|
241
|
+
# This should not happen in practice, but test defensive behavior
|
|
242
|
+
with pytest.raises(AttributeError):
|
|
243
|
+
provider = StartupDependenciesProvider(None) # type: ignore
|
|
244
|
+
# Any operation should fail gracefully
|
|
245
|
+
provider.container.get_minio_client() # type: ignore
|