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.
Files changed (161) hide show
  1. julee/__init__.py +3 -0
  2. julee/api/__init__.py +20 -0
  3. julee/api/app.py +180 -0
  4. julee/api/dependencies.py +257 -0
  5. julee/api/requests.py +175 -0
  6. julee/api/responses.py +43 -0
  7. julee/api/routers/__init__.py +43 -0
  8. julee/api/routers/assembly_specifications.py +212 -0
  9. julee/api/routers/documents.py +182 -0
  10. julee/api/routers/knowledge_service_configs.py +79 -0
  11. julee/api/routers/knowledge_service_queries.py +293 -0
  12. julee/api/routers/system.py +137 -0
  13. julee/api/routers/workflows.py +234 -0
  14. julee/api/services/__init__.py +20 -0
  15. julee/api/services/system_initialization.py +214 -0
  16. julee/api/tests/__init__.py +14 -0
  17. julee/api/tests/routers/__init__.py +17 -0
  18. julee/api/tests/routers/test_assembly_specifications.py +749 -0
  19. julee/api/tests/routers/test_documents.py +301 -0
  20. julee/api/tests/routers/test_knowledge_service_configs.py +234 -0
  21. julee/api/tests/routers/test_knowledge_service_queries.py +738 -0
  22. julee/api/tests/routers/test_system.py +179 -0
  23. julee/api/tests/routers/test_workflows.py +393 -0
  24. julee/api/tests/test_app.py +285 -0
  25. julee/api/tests/test_dependencies.py +245 -0
  26. julee/api/tests/test_requests.py +250 -0
  27. julee/domain/__init__.py +22 -0
  28. julee/domain/models/__init__.py +49 -0
  29. julee/domain/models/assembly/__init__.py +17 -0
  30. julee/domain/models/assembly/assembly.py +103 -0
  31. julee/domain/models/assembly/tests/__init__.py +0 -0
  32. julee/domain/models/assembly/tests/factories.py +37 -0
  33. julee/domain/models/assembly/tests/test_assembly.py +430 -0
  34. julee/domain/models/assembly_specification/__init__.py +24 -0
  35. julee/domain/models/assembly_specification/assembly_specification.py +172 -0
  36. julee/domain/models/assembly_specification/knowledge_service_query.py +123 -0
  37. julee/domain/models/assembly_specification/tests/__init__.py +0 -0
  38. julee/domain/models/assembly_specification/tests/factories.py +78 -0
  39. julee/domain/models/assembly_specification/tests/test_assembly_specification.py +490 -0
  40. julee/domain/models/assembly_specification/tests/test_knowledge_service_query.py +310 -0
  41. julee/domain/models/custom_fields/__init__.py +0 -0
  42. julee/domain/models/custom_fields/content_stream.py +68 -0
  43. julee/domain/models/custom_fields/tests/__init__.py +0 -0
  44. julee/domain/models/custom_fields/tests/test_custom_fields.py +53 -0
  45. julee/domain/models/document/__init__.py +17 -0
  46. julee/domain/models/document/document.py +150 -0
  47. julee/domain/models/document/tests/__init__.py +0 -0
  48. julee/domain/models/document/tests/factories.py +76 -0
  49. julee/domain/models/document/tests/test_document.py +297 -0
  50. julee/domain/models/knowledge_service_config/__init__.py +17 -0
  51. julee/domain/models/knowledge_service_config/knowledge_service_config.py +86 -0
  52. julee/domain/models/policy/__init__.py +15 -0
  53. julee/domain/models/policy/document_policy_validation.py +220 -0
  54. julee/domain/models/policy/policy.py +203 -0
  55. julee/domain/models/policy/tests/__init__.py +0 -0
  56. julee/domain/models/policy/tests/factories.py +47 -0
  57. julee/domain/models/policy/tests/test_document_policy_validation.py +420 -0
  58. julee/domain/models/policy/tests/test_policy.py +546 -0
  59. julee/domain/repositories/__init__.py +27 -0
  60. julee/domain/repositories/assembly.py +45 -0
  61. julee/domain/repositories/assembly_specification.py +52 -0
  62. julee/domain/repositories/base.py +146 -0
  63. julee/domain/repositories/document.py +49 -0
  64. julee/domain/repositories/document_policy_validation.py +52 -0
  65. julee/domain/repositories/knowledge_service_config.py +54 -0
  66. julee/domain/repositories/knowledge_service_query.py +44 -0
  67. julee/domain/repositories/policy.py +49 -0
  68. julee/domain/use_cases/__init__.py +17 -0
  69. julee/domain/use_cases/decorators.py +107 -0
  70. julee/domain/use_cases/extract_assemble_data.py +649 -0
  71. julee/domain/use_cases/initialize_system_data.py +842 -0
  72. julee/domain/use_cases/tests/__init__.py +7 -0
  73. julee/domain/use_cases/tests/test_extract_assemble_data.py +548 -0
  74. julee/domain/use_cases/tests/test_initialize_system_data.py +455 -0
  75. julee/domain/use_cases/tests/test_validate_document.py +1228 -0
  76. julee/domain/use_cases/validate_document.py +736 -0
  77. julee/fixtures/assembly_specifications.yaml +70 -0
  78. julee/fixtures/documents.yaml +178 -0
  79. julee/fixtures/knowledge_service_configs.yaml +37 -0
  80. julee/fixtures/knowledge_service_queries.yaml +27 -0
  81. julee/repositories/__init__.py +17 -0
  82. julee/repositories/memory/__init__.py +31 -0
  83. julee/repositories/memory/assembly.py +84 -0
  84. julee/repositories/memory/assembly_specification.py +125 -0
  85. julee/repositories/memory/base.py +227 -0
  86. julee/repositories/memory/document.py +149 -0
  87. julee/repositories/memory/document_policy_validation.py +104 -0
  88. julee/repositories/memory/knowledge_service_config.py +123 -0
  89. julee/repositories/memory/knowledge_service_query.py +120 -0
  90. julee/repositories/memory/policy.py +87 -0
  91. julee/repositories/memory/tests/__init__.py +0 -0
  92. julee/repositories/memory/tests/test_document.py +212 -0
  93. julee/repositories/memory/tests/test_document_policy_validation.py +161 -0
  94. julee/repositories/memory/tests/test_policy.py +443 -0
  95. julee/repositories/minio/__init__.py +31 -0
  96. julee/repositories/minio/assembly.py +103 -0
  97. julee/repositories/minio/assembly_specification.py +170 -0
  98. julee/repositories/minio/client.py +570 -0
  99. julee/repositories/minio/document.py +530 -0
  100. julee/repositories/minio/document_policy_validation.py +120 -0
  101. julee/repositories/minio/knowledge_service_config.py +187 -0
  102. julee/repositories/minio/knowledge_service_query.py +211 -0
  103. julee/repositories/minio/policy.py +106 -0
  104. julee/repositories/minio/tests/__init__.py +0 -0
  105. julee/repositories/minio/tests/fake_client.py +213 -0
  106. julee/repositories/minio/tests/test_assembly.py +374 -0
  107. julee/repositories/minio/tests/test_assembly_specification.py +391 -0
  108. julee/repositories/minio/tests/test_client_protocol.py +57 -0
  109. julee/repositories/minio/tests/test_document.py +591 -0
  110. julee/repositories/minio/tests/test_document_policy_validation.py +192 -0
  111. julee/repositories/minio/tests/test_knowledge_service_config.py +374 -0
  112. julee/repositories/minio/tests/test_knowledge_service_query.py +438 -0
  113. julee/repositories/minio/tests/test_policy.py +559 -0
  114. julee/repositories/temporal/__init__.py +38 -0
  115. julee/repositories/temporal/activities.py +114 -0
  116. julee/repositories/temporal/activity_names.py +34 -0
  117. julee/repositories/temporal/proxies.py +159 -0
  118. julee/services/__init__.py +18 -0
  119. julee/services/knowledge_service/__init__.py +48 -0
  120. julee/services/knowledge_service/anthropic/__init__.py +12 -0
  121. julee/services/knowledge_service/anthropic/knowledge_service.py +331 -0
  122. julee/services/knowledge_service/anthropic/tests/test_knowledge_service.py +318 -0
  123. julee/services/knowledge_service/factory.py +138 -0
  124. julee/services/knowledge_service/knowledge_service.py +160 -0
  125. julee/services/knowledge_service/memory/__init__.py +13 -0
  126. julee/services/knowledge_service/memory/knowledge_service.py +278 -0
  127. julee/services/knowledge_service/memory/test_knowledge_service.py +345 -0
  128. julee/services/knowledge_service/test_factory.py +112 -0
  129. julee/services/temporal/__init__.py +38 -0
  130. julee/services/temporal/activities.py +86 -0
  131. julee/services/temporal/activity_names.py +22 -0
  132. julee/services/temporal/proxies.py +41 -0
  133. julee/util/__init__.py +0 -0
  134. julee/util/domain.py +119 -0
  135. julee/util/repos/__init__.py +0 -0
  136. julee/util/repos/minio/__init__.py +0 -0
  137. julee/util/repos/minio/file_storage.py +213 -0
  138. julee/util/repos/temporal/__init__.py +11 -0
  139. julee/util/repos/temporal/client_proxies/file_storage.py +68 -0
  140. julee/util/repos/temporal/data_converter.py +123 -0
  141. julee/util/repos/temporal/minio_file_storage.py +12 -0
  142. julee/util/repos/temporal/proxies/__init__.py +0 -0
  143. julee/util/repos/temporal/proxies/file_storage.py +58 -0
  144. julee/util/repositories.py +55 -0
  145. julee/util/temporal/__init__.py +22 -0
  146. julee/util/temporal/activities.py +123 -0
  147. julee/util/temporal/decorators.py +473 -0
  148. julee/util/tests/__init__.py +1 -0
  149. julee/util/tests/test_decorators.py +770 -0
  150. julee/util/validation/__init__.py +29 -0
  151. julee/util/validation/repository.py +100 -0
  152. julee/util/validation/type_guards.py +369 -0
  153. julee/worker.py +211 -0
  154. julee/workflows/__init__.py +26 -0
  155. julee/workflows/extract_assemble.py +215 -0
  156. julee/workflows/validate_document.py +228 -0
  157. julee-0.1.0.dist-info/METADATA +195 -0
  158. julee-0.1.0.dist-info/RECORD +161 -0
  159. julee-0.1.0.dist-info/WHEEL +5 -0
  160. julee-0.1.0.dist-info/licenses/LICENSE +674 -0
  161. 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