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,1228 @@
1
+ """
2
+ Tests for ValidateDocumentUseCase.
3
+
4
+ This module provides tests for the validate document use case,
5
+ ensuring proper business logic execution and repository interaction patterns
6
+ following the Clean Architecture principles.
7
+ """
8
+
9
+ import io
10
+ import pytest
11
+ from unittest.mock import AsyncMock
12
+ from datetime import datetime, timezone
13
+ from pydantic import ValidationError
14
+
15
+ from julee.domain.use_cases import ValidateDocumentUseCase
16
+
17
+ from julee.domain.models import (
18
+ Document,
19
+ DocumentStatus,
20
+ ContentStream,
21
+ KnowledgeServiceConfig,
22
+ KnowledgeServiceQuery,
23
+ )
24
+ from julee.domain.models.policy import (
25
+ Policy,
26
+ PolicyStatus,
27
+ DocumentPolicyValidation,
28
+ DocumentPolicyValidationStatus,
29
+ )
30
+ from julee.domain.models.knowledge_service_config import ServiceApi
31
+ from julee.repositories.memory import (
32
+ MemoryDocumentRepository,
33
+ MemoryKnowledgeServiceQueryRepository,
34
+ MemoryKnowledgeServiceConfigRepository,
35
+ MemoryPolicyRepository,
36
+ MemoryDocumentPolicyValidationRepository,
37
+ )
38
+ from julee.services.knowledge_service.memory import (
39
+ MemoryKnowledgeService,
40
+ )
41
+ from julee.services.knowledge_service import QueryResult
42
+
43
+
44
+ class TestValidateDocumentUseCase:
45
+ """Test cases for ValidateDocumentUseCase business logic."""
46
+
47
+ @pytest.fixture
48
+ def document_repo(self) -> MemoryDocumentRepository:
49
+ """Create a memory DocumentRepository for testing."""
50
+ return MemoryDocumentRepository()
51
+
52
+ @pytest.fixture
53
+ def knowledge_service_query_repo(
54
+ self,
55
+ ) -> MemoryKnowledgeServiceQueryRepository:
56
+ """Create a memory KnowledgeServiceQueryRepository for testing."""
57
+ return MemoryKnowledgeServiceQueryRepository()
58
+
59
+ @pytest.fixture
60
+ def knowledge_service_config_repo(
61
+ self,
62
+ ) -> MemoryKnowledgeServiceConfigRepository:
63
+ """Create a memory KnowledgeServiceConfigRepository for testing."""
64
+ return MemoryKnowledgeServiceConfigRepository()
65
+
66
+ @pytest.fixture
67
+ def policy_repo(self) -> MemoryPolicyRepository:
68
+ """Create a memory PolicyRepository for testing."""
69
+ return MemoryPolicyRepository()
70
+
71
+ @pytest.fixture
72
+ def document_policy_validation_repo(
73
+ self,
74
+ ) -> MemoryDocumentPolicyValidationRepository:
75
+ """Create a memory DocumentPolicyValidationRepository for testing."""
76
+ return MemoryDocumentPolicyValidationRepository()
77
+
78
+ @pytest.fixture
79
+ def knowledge_service(self) -> MemoryKnowledgeService:
80
+ """Create a memory KnowledgeService for testing."""
81
+ ks_config = KnowledgeServiceConfig(
82
+ knowledge_service_id="ks-test",
83
+ name="Test Knowledge Service",
84
+ description="Test service",
85
+ service_api=ServiceApi.ANTHROPIC,
86
+ created_at=datetime.now(timezone.utc),
87
+ updated_at=datetime.now(timezone.utc),
88
+ )
89
+ return MemoryKnowledgeService(ks_config)
90
+
91
+ @pytest.fixture
92
+ def use_case(
93
+ self,
94
+ document_repo: MemoryDocumentRepository,
95
+ knowledge_service_query_repo: MemoryKnowledgeServiceQueryRepository,
96
+ knowledge_service_config_repo: MemoryKnowledgeServiceConfigRepository,
97
+ policy_repo: MemoryPolicyRepository,
98
+ document_policy_validation_repo: MemoryDocumentPolicyValidationRepository,
99
+ knowledge_service: MemoryKnowledgeService,
100
+ ) -> ValidateDocumentUseCase:
101
+ """Create ValidateDocumentUseCase with memory repository
102
+ dependencies."""
103
+ return ValidateDocumentUseCase(
104
+ document_repo=document_repo,
105
+ knowledge_service_query_repo=knowledge_service_query_repo,
106
+ knowledge_service_config_repo=knowledge_service_config_repo,
107
+ policy_repo=policy_repo,
108
+ document_policy_validation_repo=document_policy_validation_repo,
109
+ knowledge_service=knowledge_service,
110
+ now_fn=lambda: datetime.now(timezone.utc),
111
+ )
112
+
113
+ def _create_configured_use_case(
114
+ self,
115
+ document_repo: MemoryDocumentRepository,
116
+ knowledge_service_query_repo: MemoryKnowledgeServiceQueryRepository,
117
+ knowledge_service_config_repo: MemoryKnowledgeServiceConfigRepository,
118
+ policy_repo: MemoryPolicyRepository,
119
+ document_policy_validation_repo: MemoryDocumentPolicyValidationRepository,
120
+ memory_service: MemoryKnowledgeService,
121
+ ) -> ValidateDocumentUseCase:
122
+ """Helper to create ValidateDocumentUseCase with configured memory
123
+ service."""
124
+ return ValidateDocumentUseCase(
125
+ document_repo=document_repo,
126
+ knowledge_service_query_repo=knowledge_service_query_repo,
127
+ knowledge_service_config_repo=knowledge_service_config_repo,
128
+ policy_repo=policy_repo,
129
+ document_policy_validation_repo=document_policy_validation_repo,
130
+ knowledge_service=memory_service,
131
+ now_fn=lambda: datetime.now(timezone.utc),
132
+ )
133
+
134
+ @pytest.mark.asyncio
135
+ async def test_validate_document_fails_without_document(
136
+ self, use_case: ValidateDocumentUseCase
137
+ ) -> None:
138
+ """Test that validate_document fails when document doesn't exist."""
139
+ # Arrange
140
+ document_id = "nonexistent-doc"
141
+ policy_id = "policy-123"
142
+
143
+ # Act & Assert
144
+ with pytest.raises(ValueError, match="Document not found"):
145
+ await use_case.validate_document(
146
+ document_id=document_id, policy_id=policy_id
147
+ )
148
+
149
+ @pytest.mark.asyncio
150
+ async def test_validate_document_fails_without_policy(
151
+ self,
152
+ use_case: ValidateDocumentUseCase,
153
+ document_repo: MemoryDocumentRepository,
154
+ ) -> None:
155
+ """Test that validate_document fails when policy doesn't exist."""
156
+ # Arrange - Create document but no policy
157
+ content_text = "Sample document for testing"
158
+ content_bytes = content_text.encode("utf-8")
159
+ document = Document(
160
+ document_id="doc-123",
161
+ original_filename="test_document.txt",
162
+ content_type="text/plain",
163
+ size_bytes=len(content_bytes),
164
+ content_multihash="test-hash-123",
165
+ status=DocumentStatus.CAPTURED,
166
+ content=ContentStream(io.BytesIO(content_bytes)),
167
+ created_at=datetime.now(timezone.utc),
168
+ updated_at=datetime.now(timezone.utc),
169
+ )
170
+ await document_repo.save(document)
171
+
172
+ document_id = "doc-123"
173
+ policy_id = "nonexistent-policy"
174
+
175
+ # Act & Assert
176
+ with pytest.raises(ValueError, match="Policy not found"):
177
+ await use_case.validate_document(
178
+ document_id=document_id, policy_id=policy_id
179
+ )
180
+
181
+ @pytest.mark.asyncio
182
+ async def test_validate_document_propagates_id_generation_error(
183
+ self,
184
+ use_case: ValidateDocumentUseCase,
185
+ document_policy_validation_repo: MemoryDocumentPolicyValidationRepository,
186
+ ) -> None:
187
+ """Test that ID generation errors are properly propagated."""
188
+ # Arrange
189
+ document_id = "doc-456"
190
+ policy_id = "policy-789"
191
+ expected_error = RuntimeError("ID generation failed")
192
+
193
+ # Mock the generate_id method to raise an error
194
+ document_policy_validation_repo.generate_id = AsyncMock( # type: ignore[method-assign]
195
+ side_effect=expected_error
196
+ )
197
+
198
+ # Act & Assert
199
+ with pytest.raises(RuntimeError, match="ID generation failed"):
200
+ await use_case.validate_document(
201
+ document_id=document_id, policy_id=policy_id
202
+ )
203
+
204
+ @pytest.mark.asyncio
205
+ async def test_validate_document_fails_when_query_not_found(
206
+ self,
207
+ use_case: ValidateDocumentUseCase,
208
+ document_repo: MemoryDocumentRepository,
209
+ policy_repo: MemoryPolicyRepository,
210
+ ) -> None:
211
+ """Test that validation fails when query is not found."""
212
+ # Arrange - Create document and policy with non-existent query
213
+ content_text = "Sample content"
214
+ content_bytes = content_text.encode("utf-8")
215
+ document = Document(
216
+ document_id="doc-123",
217
+ original_filename="test.txt",
218
+ content_type="text/plain",
219
+ size_bytes=len(content_bytes),
220
+ content_multihash="test-hash",
221
+ status=DocumentStatus.CAPTURED,
222
+ content=ContentStream(io.BytesIO(content_bytes)),
223
+ created_at=datetime.now(timezone.utc),
224
+ updated_at=datetime.now(timezone.utc),
225
+ )
226
+ await document_repo.save(document)
227
+
228
+ policy = Policy(
229
+ policy_id="policy-123",
230
+ title="Test Policy",
231
+ description="Policy with non-existent query",
232
+ status=PolicyStatus.ACTIVE,
233
+ validation_scores=[("nonexistent-query", 80)],
234
+ created_at=datetime.now(timezone.utc),
235
+ updated_at=datetime.now(timezone.utc),
236
+ )
237
+ await policy_repo.save(policy)
238
+
239
+ # Act & Assert
240
+ with pytest.raises(ValueError, match="Validation query not found"):
241
+ await use_case.validate_document(
242
+ document_id="doc-123", policy_id="policy-123"
243
+ )
244
+
245
+ @pytest.mark.asyncio
246
+ async def test_validate_document_fails_with_score_parse_error(
247
+ self,
248
+ use_case: ValidateDocumentUseCase,
249
+ document_repo: MemoryDocumentRepository,
250
+ policy_repo: MemoryPolicyRepository,
251
+ knowledge_service_query_repo: MemoryKnowledgeServiceQueryRepository,
252
+ knowledge_service_config_repo: MemoryKnowledgeServiceConfigRepository,
253
+ document_policy_validation_repo: MemoryDocumentPolicyValidationRepository,
254
+ ) -> None:
255
+ """Test that validation fails when score cannot be parsed."""
256
+ # Arrange - Create test document
257
+ content_text = "Sample document content"
258
+ content_bytes = content_text.encode("utf-8")
259
+ document = Document(
260
+ document_id="doc-123",
261
+ original_filename="test.txt",
262
+ content_type="text/plain",
263
+ size_bytes=len(content_bytes),
264
+ content_multihash="test-hash",
265
+ status=DocumentStatus.CAPTURED,
266
+ content=ContentStream(io.BytesIO(content_bytes)),
267
+ created_at=datetime.now(timezone.utc),
268
+ updated_at=datetime.now(timezone.utc),
269
+ )
270
+ await document_repo.save(document)
271
+
272
+ # Create policy
273
+ policy = Policy(
274
+ policy_id="policy-123",
275
+ title="Test Policy",
276
+ description="Policy for testing score parsing",
277
+ status=PolicyStatus.ACTIVE,
278
+ validation_scores=[("query-1", 80)],
279
+ created_at=datetime.now(timezone.utc),
280
+ updated_at=datetime.now(timezone.utc),
281
+ )
282
+ await policy_repo.save(policy)
283
+
284
+ # Create knowledge service config and query
285
+ ks_config = KnowledgeServiceConfig(
286
+ knowledge_service_id="ks-123",
287
+ name="Test Knowledge Service",
288
+ description="Test service",
289
+ service_api=ServiceApi.ANTHROPIC,
290
+ created_at=datetime.now(timezone.utc),
291
+ updated_at=datetime.now(timezone.utc),
292
+ )
293
+ await knowledge_service_config_repo.save(ks_config)
294
+
295
+ query = KnowledgeServiceQuery(
296
+ query_id="query-1",
297
+ name="Quality Check",
298
+ knowledge_service_id="ks-123",
299
+ prompt="Rate the quality of this document",
300
+ created_at=datetime.now(timezone.utc),
301
+ updated_at=datetime.now(timezone.utc),
302
+ )
303
+ await knowledge_service_query_repo.save(query)
304
+
305
+ # Create memory service that returns unparseable score
306
+ memory_service = MemoryKnowledgeService(ks_config)
307
+ memory_service.add_canned_query_result(
308
+ QueryResult(
309
+ query_id="result-1",
310
+ query_text="Rate the quality of this document",
311
+ result_data={"response": "not a number"}, # Invalid score format
312
+ execution_time_ms=100,
313
+ created_at=datetime.now(timezone.utc),
314
+ )
315
+ )
316
+
317
+ # Create use case with configured memory service
318
+ configured_use_case = self._create_configured_use_case(
319
+ document_repo=document_repo,
320
+ knowledge_service_query_repo=knowledge_service_query_repo,
321
+ knowledge_service_config_repo=knowledge_service_config_repo,
322
+ policy_repo=policy_repo,
323
+ document_policy_validation_repo=document_policy_validation_repo,
324
+ memory_service=memory_service,
325
+ )
326
+
327
+ # Act & Assert
328
+ with pytest.raises(
329
+ ValueError,
330
+ match="Failed to parse numeric score from response",
331
+ ):
332
+ await configured_use_case.validate_document(
333
+ document_id="doc-123", policy_id="policy-123"
334
+ )
335
+
336
+ @pytest.mark.asyncio
337
+ async def test_full_validation_workflow_success_pass(
338
+ self,
339
+ use_case: ValidateDocumentUseCase,
340
+ document_repo: MemoryDocumentRepository,
341
+ policy_repo: MemoryPolicyRepository,
342
+ knowledge_service_query_repo: MemoryKnowledgeServiceQueryRepository,
343
+ knowledge_service_config_repo: MemoryKnowledgeServiceConfigRepository,
344
+ document_policy_validation_repo: MemoryDocumentPolicyValidationRepository,
345
+ ) -> None:
346
+ """Test complete validation workflow that passes validation."""
347
+ # Arrange - Create test document
348
+ content_text = "High quality document for testing validation"
349
+ content_bytes = content_text.encode("utf-8")
350
+ document = Document(
351
+ document_id="doc-123",
352
+ original_filename="test_document.txt",
353
+ content_type="text/plain",
354
+ size_bytes=len(content_bytes),
355
+ content_multihash="test-hash-123",
356
+ status=DocumentStatus.CAPTURED,
357
+ content=ContentStream(io.BytesIO(content_bytes)),
358
+ created_at=datetime.now(timezone.utc),
359
+ updated_at=datetime.now(timezone.utc),
360
+ )
361
+ await document_repo.save(document)
362
+
363
+ # Create policy with validation criteria
364
+ policy = Policy(
365
+ policy_id="policy-123",
366
+ title="Quality Policy",
367
+ description="Validates document quality",
368
+ status=PolicyStatus.ACTIVE,
369
+ validation_scores=[
370
+ ("quality-query", 80),
371
+ ("clarity-query", 70),
372
+ ],
373
+ created_at=datetime.now(timezone.utc),
374
+ updated_at=datetime.now(timezone.utc),
375
+ )
376
+ await policy_repo.save(policy)
377
+
378
+ # Create knowledge service config
379
+ ks_config = KnowledgeServiceConfig(
380
+ knowledge_service_id="ks-123",
381
+ name="Test Knowledge Service",
382
+ description="Test service",
383
+ service_api=ServiceApi.ANTHROPIC,
384
+ created_at=datetime.now(timezone.utc),
385
+ updated_at=datetime.now(timezone.utc),
386
+ )
387
+ await knowledge_service_config_repo.save(ks_config)
388
+
389
+ # Create knowledge service queries
390
+ quality_query = KnowledgeServiceQuery(
391
+ query_id="quality-query",
392
+ name="Quality Check",
393
+ knowledge_service_id="ks-123",
394
+ prompt="Rate the quality of this document on a scale of 0-100",
395
+ query_metadata={"max_tokens": 10},
396
+ created_at=datetime.now(timezone.utc),
397
+ updated_at=datetime.now(timezone.utc),
398
+ )
399
+ clarity_query = KnowledgeServiceQuery(
400
+ query_id="clarity-query",
401
+ name="Clarity Check",
402
+ knowledge_service_id="ks-123",
403
+ prompt="Rate the clarity of this document on a scale of 0-100",
404
+ query_metadata={"max_tokens": 10},
405
+ created_at=datetime.now(timezone.utc),
406
+ updated_at=datetime.now(timezone.utc),
407
+ )
408
+ await knowledge_service_query_repo.save(quality_query)
409
+ await knowledge_service_query_repo.save(clarity_query)
410
+
411
+ # Create memory service with passing scores
412
+ memory_service = MemoryKnowledgeService(ks_config)
413
+ memory_service.add_canned_query_results(
414
+ [
415
+ QueryResult(
416
+ query_id="result-1",
417
+ query_text="Rate the quality of this document on a "
418
+ "scale of 0-100",
419
+ result_data={"response": "85"}, # Passes requirement of 80
420
+ execution_time_ms=100,
421
+ created_at=datetime.now(timezone.utc),
422
+ ),
423
+ QueryResult(
424
+ query_id="result-2",
425
+ query_text="Rate the clarity of this document on a "
426
+ "scale of 0-100",
427
+ result_data={"response": "75"}, # Passes requirement of 70
428
+ execution_time_ms=150,
429
+ created_at=datetime.now(timezone.utc),
430
+ ),
431
+ ]
432
+ )
433
+
434
+ # Create use case with configured memory service
435
+ configured_use_case = self._create_configured_use_case(
436
+ document_repo=document_repo,
437
+ knowledge_service_query_repo=knowledge_service_query_repo,
438
+ knowledge_service_config_repo=knowledge_service_config_repo,
439
+ policy_repo=policy_repo,
440
+ document_policy_validation_repo=document_policy_validation_repo,
441
+ memory_service=memory_service,
442
+ )
443
+
444
+ # Act
445
+ result = await configured_use_case.validate_document(
446
+ document_id="doc-123", policy_id="policy-123"
447
+ )
448
+
449
+ # Assert
450
+ assert isinstance(result, DocumentPolicyValidation)
451
+ assert result.status == DocumentPolicyValidationStatus.PASSED
452
+ assert result.passed is True
453
+ assert result.validation_scores == [
454
+ ("quality-query", 85),
455
+ ("clarity-query", 75),
456
+ ]
457
+ assert result.completed_at is not None
458
+ assert result.error_message is None
459
+
460
+ # Verify validation was saved to repository
461
+ saved_validation = await document_policy_validation_repo.get(
462
+ result.validation_id
463
+ )
464
+ assert saved_validation is not None
465
+ assert saved_validation.status == DocumentPolicyValidationStatus.PASSED
466
+ assert saved_validation.passed is True
467
+
468
+ @pytest.mark.asyncio
469
+ async def test_full_validation_workflow_success_fail(
470
+ self,
471
+ use_case: ValidateDocumentUseCase,
472
+ document_repo: MemoryDocumentRepository,
473
+ policy_repo: MemoryPolicyRepository,
474
+ knowledge_service_query_repo: MemoryKnowledgeServiceQueryRepository,
475
+ knowledge_service_config_repo: MemoryKnowledgeServiceConfigRepository,
476
+ document_policy_validation_repo: MemoryDocumentPolicyValidationRepository,
477
+ ) -> None:
478
+ """Test complete validation workflow that fails validation."""
479
+ # Arrange - Create test document
480
+ content_text = "Poor quality document"
481
+ content_bytes = content_text.encode("utf-8")
482
+ document = Document(
483
+ document_id="doc-456",
484
+ original_filename="poor_document.txt",
485
+ content_type="text/plain",
486
+ size_bytes=len(content_bytes),
487
+ content_multihash="test-hash-456",
488
+ status=DocumentStatus.CAPTURED,
489
+ content=ContentStream(io.BytesIO(content_bytes)),
490
+ created_at=datetime.now(timezone.utc),
491
+ updated_at=datetime.now(timezone.utc),
492
+ )
493
+ await document_repo.save(document)
494
+
495
+ # Create policy with high standards
496
+ policy = Policy(
497
+ policy_id="policy-456",
498
+ title="High Standards Policy",
499
+ description="Requires high quality scores",
500
+ status=PolicyStatus.ACTIVE,
501
+ validation_scores=[("quality-query", 90)], # High requirement
502
+ created_at=datetime.now(timezone.utc),
503
+ updated_at=datetime.now(timezone.utc),
504
+ )
505
+ await policy_repo.save(policy)
506
+
507
+ # Create knowledge service config and query
508
+ ks_config = KnowledgeServiceConfig(
509
+ knowledge_service_id="ks-456",
510
+ name="Test Knowledge Service",
511
+ description="Test service",
512
+ service_api=ServiceApi.ANTHROPIC,
513
+ created_at=datetime.now(timezone.utc),
514
+ updated_at=datetime.now(timezone.utc),
515
+ )
516
+ await knowledge_service_config_repo.save(ks_config)
517
+
518
+ quality_query = KnowledgeServiceQuery(
519
+ query_id="quality-query",
520
+ name="Quality Check",
521
+ knowledge_service_id="ks-456",
522
+ prompt="Rate the quality of this document on a scale of 0-100",
523
+ created_at=datetime.now(timezone.utc),
524
+ updated_at=datetime.now(timezone.utc),
525
+ )
526
+ await knowledge_service_query_repo.save(quality_query)
527
+
528
+ # Create memory service with failing score
529
+ memory_service = MemoryKnowledgeService(ks_config)
530
+ memory_service.add_canned_query_result(
531
+ QueryResult(
532
+ query_id="result-1",
533
+ query_text="Rate the quality of this document on a " "scale of 0-100",
534
+ result_data={"response": "60"}, # Fails requirement of 90
535
+ execution_time_ms=100,
536
+ created_at=datetime.now(timezone.utc),
537
+ )
538
+ )
539
+
540
+ # Create use case with configured memory service
541
+ configured_use_case = self._create_configured_use_case(
542
+ document_repo=document_repo,
543
+ knowledge_service_query_repo=knowledge_service_query_repo,
544
+ knowledge_service_config_repo=knowledge_service_config_repo,
545
+ policy_repo=policy_repo,
546
+ document_policy_validation_repo=document_policy_validation_repo,
547
+ memory_service=memory_service,
548
+ )
549
+
550
+ # Act
551
+ await configured_use_case.validate_document(
552
+ document_id="doc-456", policy_id="policy-456"
553
+ )
554
+
555
+ @pytest.mark.asyncio
556
+ async def test_validation_with_transformation_success(
557
+ self,
558
+ use_case: ValidateDocumentUseCase,
559
+ document_repo: MemoryDocumentRepository,
560
+ policy_repo: MemoryPolicyRepository,
561
+ knowledge_service_query_repo: MemoryKnowledgeServiceQueryRepository,
562
+ knowledge_service_config_repo: MemoryKnowledgeServiceConfigRepository,
563
+ document_policy_validation_repo: MemoryDocumentPolicyValidationRepository,
564
+ ) -> None:
565
+ """Test validation with transformation that results in passing."""
566
+ # Arrange - Create test document
567
+ content_text = "Poor quality document that can be improved"
568
+ content_bytes = content_text.encode("utf-8")
569
+ document = Document(
570
+ document_id="doc-transform-1",
571
+ original_filename="transform_test.txt",
572
+ content_type="text/plain",
573
+ size_bytes=len(content_bytes),
574
+ content_multihash="test-hash-transform-1",
575
+ status=DocumentStatus.CAPTURED,
576
+ content=ContentStream(io.BytesIO(content_bytes)),
577
+ created_at=datetime.now(timezone.utc),
578
+ updated_at=datetime.now(timezone.utc),
579
+ )
580
+ await document_repo.save(document)
581
+
582
+ # Create policy with transformation queries
583
+ policy = Policy(
584
+ policy_id="policy-transform-1",
585
+ title="Transformation Policy",
586
+ description="Policy with transformation capabilities",
587
+ status=PolicyStatus.ACTIVE,
588
+ validation_scores=[("quality-query", 80)],
589
+ transformation_queries=["improvement-query"],
590
+ created_at=datetime.now(timezone.utc),
591
+ updated_at=datetime.now(timezone.utc),
592
+ )
593
+ await policy_repo.save(policy)
594
+
595
+ # Create knowledge service config
596
+ ks_config = KnowledgeServiceConfig(
597
+ knowledge_service_id="ks-transform-1",
598
+ name="Transform Knowledge Service",
599
+ description="Service with transformation capability",
600
+ service_api=ServiceApi.ANTHROPIC,
601
+ created_at=datetime.now(timezone.utc),
602
+ updated_at=datetime.now(timezone.utc),
603
+ )
604
+ await knowledge_service_config_repo.save(ks_config)
605
+
606
+ # Create validation and transformation queries
607
+ quality_query = KnowledgeServiceQuery(
608
+ query_id="quality-query",
609
+ name="Quality Check",
610
+ knowledge_service_id="ks-transform-1",
611
+ prompt="Rate the quality of this document on a scale of 0-100",
612
+ created_at=datetime.now(timezone.utc),
613
+ updated_at=datetime.now(timezone.utc),
614
+ )
615
+ improvement_query = KnowledgeServiceQuery(
616
+ query_id="improvement-query",
617
+ name="Document Improvement",
618
+ knowledge_service_id="ks-transform-1",
619
+ prompt="Improve this document to make it higher quality",
620
+ created_at=datetime.now(timezone.utc),
621
+ updated_at=datetime.now(timezone.utc),
622
+ )
623
+ await knowledge_service_query_repo.save(quality_query)
624
+ await knowledge_service_query_repo.save(improvement_query)
625
+
626
+ # Create memory service that simulates transformation workflow
627
+ memory_service = MemoryKnowledgeService(ks_config)
628
+
629
+ # First validation (fails with score 60)
630
+ memory_service.add_canned_query_result(
631
+ QueryResult(
632
+ query_id="initial-validation",
633
+ query_text="Rate the quality of this document on a scale " "of 0-100",
634
+ result_data={"response": "60"}, # Initial score fails
635
+ execution_time_ms=100,
636
+ created_at=datetime.now(timezone.utc),
637
+ )
638
+ )
639
+
640
+ # Transformation query returns improved content
641
+ memory_service.add_canned_query_result(
642
+ QueryResult(
643
+ query_id="transformation",
644
+ query_text="Improve this document to make it higher quality",
645
+ result_data={
646
+ "response": '{"improved_content": "This is a much '
647
+ "higher quality document with better structure and "
648
+ 'clarity."}'
649
+ },
650
+ execution_time_ms=200,
651
+ created_at=datetime.now(timezone.utc),
652
+ )
653
+ )
654
+
655
+ # Post-transformation validation (passes with score 85)
656
+ memory_service.add_canned_query_result(
657
+ QueryResult(
658
+ query_id="post-transform-validation",
659
+ query_text="Rate the quality of this document on a scale " "of 0-100",
660
+ result_data={"response": "85"}, # Post-transform score passes
661
+ execution_time_ms=100,
662
+ created_at=datetime.now(timezone.utc),
663
+ )
664
+ )
665
+
666
+ # Create use case with configured memory service
667
+ configured_use_case = self._create_configured_use_case(
668
+ document_repo=document_repo,
669
+ knowledge_service_query_repo=knowledge_service_query_repo,
670
+ knowledge_service_config_repo=knowledge_service_config_repo,
671
+ policy_repo=policy_repo,
672
+ document_policy_validation_repo=document_policy_validation_repo,
673
+ memory_service=memory_service,
674
+ )
675
+
676
+ # Act
677
+ result = await configured_use_case.validate_document(
678
+ document_id="doc-transform-1", policy_id="policy-transform-1"
679
+ )
680
+
681
+ # Assert
682
+ assert isinstance(result, DocumentPolicyValidation)
683
+ assert result.status == DocumentPolicyValidationStatus.PASSED
684
+ assert result.passed is True
685
+ assert result.validation_scores == [("quality-query", 60)] # Initial scores
686
+ assert result.post_transform_validation_scores == [
687
+ ("quality-query", 85)
688
+ ] # Final scores
689
+ assert result.transformed_document_id is not None
690
+ assert result.completed_at is not None
691
+
692
+ # Verify transformed document was created and saved
693
+ transformed_document = await document_repo.get(result.transformed_document_id)
694
+ assert transformed_document is not None
695
+ assert transformed_document.original_filename.startswith("transformed_")
696
+ assert transformed_document.content_type == "text/plain"
697
+
698
+ # Verify validation was saved to repository
699
+ saved_validation = await document_policy_validation_repo.get(
700
+ result.validation_id
701
+ )
702
+ assert saved_validation is not None
703
+ assert saved_validation.status == DocumentPolicyValidationStatus.PASSED
704
+ assert saved_validation.transformed_document_id is not None
705
+
706
+ @pytest.mark.asyncio
707
+ async def test_validation_with_transformation_still_fails(
708
+ self,
709
+ use_case: ValidateDocumentUseCase,
710
+ document_repo: MemoryDocumentRepository,
711
+ policy_repo: MemoryPolicyRepository,
712
+ knowledge_service_query_repo: MemoryKnowledgeServiceQueryRepository,
713
+ knowledge_service_config_repo: MemoryKnowledgeServiceConfigRepository,
714
+ document_policy_validation_repo: MemoryDocumentPolicyValidationRepository,
715
+ ) -> None:
716
+ """Test validation with transformation that still fails after
717
+ transformation."""
718
+ # Arrange - Create test document
719
+ content_text = "Very poor quality document"
720
+ content_bytes = content_text.encode("utf-8")
721
+ document = Document(
722
+ document_id="doc-transform-2",
723
+ original_filename="poor_transform_test.txt",
724
+ content_type="text/plain",
725
+ size_bytes=len(content_bytes),
726
+ content_multihash="test-hash-transform-2",
727
+ status=DocumentStatus.CAPTURED,
728
+ content=ContentStream(io.BytesIO(content_bytes)),
729
+ created_at=datetime.now(timezone.utc),
730
+ updated_at=datetime.now(timezone.utc),
731
+ )
732
+ await document_repo.save(document)
733
+
734
+ # Create policy with high standards and transformation
735
+ policy = Policy(
736
+ policy_id="policy-transform-2",
737
+ title="High Standards Transform Policy",
738
+ description="Policy with very high standards even after " "transformation",
739
+ status=PolicyStatus.ACTIVE,
740
+ validation_scores=[("quality-query", 95)], # Very high requirement
741
+ transformation_queries=["improvement-query"],
742
+ created_at=datetime.now(timezone.utc),
743
+ updated_at=datetime.now(timezone.utc),
744
+ )
745
+ await policy_repo.save(policy)
746
+
747
+ # Create knowledge service config
748
+ ks_config = KnowledgeServiceConfig(
749
+ knowledge_service_id="ks-transform-2",
750
+ name="Transform Knowledge Service",
751
+ description="Service with transformation capability",
752
+ service_api=ServiceApi.ANTHROPIC,
753
+ created_at=datetime.now(timezone.utc),
754
+ updated_at=datetime.now(timezone.utc),
755
+ )
756
+ await knowledge_service_config_repo.save(ks_config)
757
+
758
+ # Create validation and transformation queries
759
+ quality_query = KnowledgeServiceQuery(
760
+ query_id="quality-query",
761
+ name="Quality Check",
762
+ knowledge_service_id="ks-transform-2",
763
+ prompt="Rate the quality of this document on a scale of 0-100",
764
+ created_at=datetime.now(timezone.utc),
765
+ updated_at=datetime.now(timezone.utc),
766
+ )
767
+ improvement_query = KnowledgeServiceQuery(
768
+ query_id="improvement-query",
769
+ name="Document Improvement",
770
+ knowledge_service_id="ks-transform-2",
771
+ prompt="Try to improve this document",
772
+ created_at=datetime.now(timezone.utc),
773
+ updated_at=datetime.now(timezone.utc),
774
+ )
775
+ await knowledge_service_query_repo.save(quality_query)
776
+ await knowledge_service_query_repo.save(improvement_query)
777
+
778
+ # Create memory service that simulates failed transformation
779
+ memory_service = MemoryKnowledgeService(ks_config)
780
+
781
+ # Initial validation fails
782
+ memory_service.add_canned_query_result(
783
+ QueryResult(
784
+ query_id="initial-validation",
785
+ query_text="Rate the quality of this document on a scale " "of 0-100",
786
+ result_data={"response": "40"}, # Initial score fails
787
+ execution_time_ms=100,
788
+ created_at=datetime.now(timezone.utc),
789
+ )
790
+ )
791
+
792
+ # Transformation attempt
793
+ memory_service.add_canned_query_result(
794
+ QueryResult(
795
+ query_id="transformation",
796
+ query_text="Try to improve this document",
797
+ result_data={
798
+ "response": '{"improved_content": "Slightly improved '
799
+ 'but still poor quality document."}'
800
+ },
801
+ execution_time_ms=200,
802
+ created_at=datetime.now(timezone.utc),
803
+ )
804
+ )
805
+
806
+ # Post-transformation validation still fails
807
+ memory_service.add_canned_query_result(
808
+ QueryResult(
809
+ query_id="post-transform-validation",
810
+ query_text="Rate the quality of this document on a scale " "of 0-100",
811
+ result_data={"response": "70"}, # Still fails requirement of 95
812
+ execution_time_ms=100,
813
+ created_at=datetime.now(timezone.utc),
814
+ )
815
+ )
816
+
817
+ # Create use case with configured memory service
818
+ configured_use_case = self._create_configured_use_case(
819
+ document_repo=document_repo,
820
+ knowledge_service_query_repo=knowledge_service_query_repo,
821
+ knowledge_service_config_repo=knowledge_service_config_repo,
822
+ policy_repo=policy_repo,
823
+ document_policy_validation_repo=document_policy_validation_repo,
824
+ memory_service=memory_service,
825
+ )
826
+
827
+ # Act
828
+ result = await configured_use_case.validate_document(
829
+ document_id="doc-transform-2", policy_id="policy-transform-2"
830
+ )
831
+
832
+ # Assert
833
+ assert isinstance(result, DocumentPolicyValidation)
834
+ assert result.status == DocumentPolicyValidationStatus.FAILED
835
+ assert result.passed is False
836
+ assert result.validation_scores == [("quality-query", 40)] # Initial scores
837
+ assert result.post_transform_validation_scores == [
838
+ ("quality-query", 70)
839
+ ] # Final scores still fail
840
+ assert result.transformed_document_id is not None
841
+ assert result.completed_at is not None
842
+
843
+ @pytest.mark.asyncio
844
+ async def test_validation_no_transformation_when_initially_passes(
845
+ self,
846
+ use_case: ValidateDocumentUseCase,
847
+ document_repo: MemoryDocumentRepository,
848
+ policy_repo: MemoryPolicyRepository,
849
+ knowledge_service_query_repo: MemoryKnowledgeServiceQueryRepository,
850
+ knowledge_service_config_repo: MemoryKnowledgeServiceConfigRepository,
851
+ document_policy_validation_repo: MemoryDocumentPolicyValidationRepository,
852
+ ) -> None:
853
+ """Test that transformation is skipped when initial validation
854
+ passes."""
855
+ # Arrange - Create high quality document
856
+ content_text = "Excellent high quality document"
857
+ content_bytes = content_text.encode("utf-8")
858
+ document = Document(
859
+ document_id="doc-no-transform",
860
+ original_filename="excellent_doc.txt",
861
+ content_type="text/plain",
862
+ size_bytes=len(content_bytes),
863
+ content_multihash="test-hash-no-transform",
864
+ status=DocumentStatus.CAPTURED,
865
+ content=ContentStream(io.BytesIO(content_bytes)),
866
+ created_at=datetime.now(timezone.utc),
867
+ updated_at=datetime.now(timezone.utc),
868
+ )
869
+ await document_repo.save(document)
870
+
871
+ # Create policy with transformation available but not needed
872
+ policy = Policy(
873
+ policy_id="policy-no-transform",
874
+ title="Policy with Unnecessary Transform",
875
+ description="Policy with transformation that won't be used",
876
+ status=PolicyStatus.ACTIVE,
877
+ validation_scores=[("quality-query", 80)],
878
+ transformation_queries=["improvement-query"], # Available but unused
879
+ created_at=datetime.now(timezone.utc),
880
+ updated_at=datetime.now(timezone.utc),
881
+ )
882
+ await policy_repo.save(policy)
883
+
884
+ # Create knowledge service config
885
+ ks_config = KnowledgeServiceConfig(
886
+ knowledge_service_id="ks-no-transform",
887
+ name="Knowledge Service",
888
+ description="Service description",
889
+ service_api=ServiceApi.ANTHROPIC,
890
+ created_at=datetime.now(timezone.utc),
891
+ updated_at=datetime.now(timezone.utc),
892
+ )
893
+ await knowledge_service_config_repo.save(ks_config)
894
+
895
+ # Create queries (transformation query won't be used)
896
+ quality_query = KnowledgeServiceQuery(
897
+ query_id="quality-query",
898
+ name="Quality Check",
899
+ knowledge_service_id="ks-no-transform",
900
+ prompt="Rate the quality of this document on a scale of 0-100",
901
+ created_at=datetime.now(timezone.utc),
902
+ updated_at=datetime.now(timezone.utc),
903
+ )
904
+ improvement_query = KnowledgeServiceQuery(
905
+ query_id="improvement-query",
906
+ name="Document Improvement",
907
+ knowledge_service_id="ks-no-transform",
908
+ prompt="This query should not be called",
909
+ created_at=datetime.now(timezone.utc),
910
+ updated_at=datetime.now(timezone.utc),
911
+ )
912
+ await knowledge_service_query_repo.save(quality_query)
913
+ await knowledge_service_query_repo.save(improvement_query)
914
+
915
+ # Create memory service with only validation result (no
916
+ # transformation)
917
+ memory_service = MemoryKnowledgeService(ks_config)
918
+ memory_service.add_canned_query_result(
919
+ QueryResult(
920
+ query_id="validation-only",
921
+ query_text="Rate the quality of this document on a scale " "of 0-100",
922
+ result_data={"response": "90"}, # Passes initial validation
923
+ execution_time_ms=100,
924
+ created_at=datetime.now(timezone.utc),
925
+ )
926
+ )
927
+
928
+ # Create use case with configured memory service
929
+ configured_use_case = self._create_configured_use_case(
930
+ document_repo=document_repo,
931
+ knowledge_service_query_repo=knowledge_service_query_repo,
932
+ knowledge_service_config_repo=knowledge_service_config_repo,
933
+ policy_repo=policy_repo,
934
+ document_policy_validation_repo=document_policy_validation_repo,
935
+ memory_service=memory_service,
936
+ )
937
+
938
+ # Act
939
+ result = await configured_use_case.validate_document(
940
+ document_id="doc-no-transform", policy_id="policy-no-transform"
941
+ )
942
+
943
+ # Assert
944
+ assert isinstance(result, DocumentPolicyValidation)
945
+ assert result.status == DocumentPolicyValidationStatus.PASSED
946
+ assert result.passed is True
947
+ assert result.validation_scores == [("quality-query", 90)]
948
+ assert result.post_transform_validation_scores is None # No transformation
949
+ assert result.transformed_document_id is None # No transformation
950
+ assert result.completed_at is not None
951
+
952
+ @pytest.mark.asyncio
953
+ async def test_transformation_fails_with_invalid_json(
954
+ self,
955
+ use_case: ValidateDocumentUseCase,
956
+ document_repo: MemoryDocumentRepository,
957
+ policy_repo: MemoryPolicyRepository,
958
+ knowledge_service_query_repo: MemoryKnowledgeServiceQueryRepository,
959
+ knowledge_service_config_repo: MemoryKnowledgeServiceConfigRepository,
960
+ document_policy_validation_repo: MemoryDocumentPolicyValidationRepository,
961
+ ) -> None:
962
+ """Test that transformation fails when result is not valid JSON."""
963
+ # Arrange - Create test document
964
+ content_text = "Document needing transformation"
965
+ content_bytes = content_text.encode("utf-8")
966
+ document = Document(
967
+ document_id="doc-invalid-json",
968
+ original_filename="invalid_json_test.txt",
969
+ content_type="text/plain",
970
+ size_bytes=len(content_bytes),
971
+ content_multihash="test-hash-invalid-json",
972
+ status=DocumentStatus.CAPTURED,
973
+ content=ContentStream(io.BytesIO(content_bytes)),
974
+ created_at=datetime.now(timezone.utc),
975
+ updated_at=datetime.now(timezone.utc),
976
+ )
977
+ await document_repo.save(document)
978
+
979
+ # Create policy with transformation
980
+ policy = Policy(
981
+ policy_id="policy-invalid-json",
982
+ title="Invalid JSON Transform Policy",
983
+ description="Policy that will get invalid JSON from " "transformation",
984
+ status=PolicyStatus.ACTIVE,
985
+ validation_scores=[("quality-query", 80)],
986
+ transformation_queries=["bad-transform-query"],
987
+ created_at=datetime.now(timezone.utc),
988
+ updated_at=datetime.now(timezone.utc),
989
+ )
990
+ await policy_repo.save(policy)
991
+
992
+ # Create knowledge service config
993
+ ks_config = KnowledgeServiceConfig(
994
+ knowledge_service_id="ks-invalid-json",
995
+ name="Invalid JSON Service",
996
+ description="Service that returns invalid JSON",
997
+ service_api=ServiceApi.ANTHROPIC,
998
+ created_at=datetime.now(timezone.utc),
999
+ updated_at=datetime.now(timezone.utc),
1000
+ )
1001
+ await knowledge_service_config_repo.save(ks_config)
1002
+
1003
+ # Create queries
1004
+ quality_query = KnowledgeServiceQuery(
1005
+ query_id="quality-query",
1006
+ name="Quality Check",
1007
+ knowledge_service_id="ks-invalid-json",
1008
+ prompt="Rate the quality of this document on a scale of 0-100",
1009
+ created_at=datetime.now(timezone.utc),
1010
+ updated_at=datetime.now(timezone.utc),
1011
+ )
1012
+ bad_transform_query = KnowledgeServiceQuery(
1013
+ query_id="bad-transform-query",
1014
+ name="Bad Transform Query",
1015
+ knowledge_service_id="ks-invalid-json",
1016
+ prompt="Transform this document",
1017
+ created_at=datetime.now(timezone.utc),
1018
+ updated_at=datetime.now(timezone.utc),
1019
+ )
1020
+ await knowledge_service_query_repo.save(quality_query)
1021
+ await knowledge_service_query_repo.save(bad_transform_query)
1022
+
1023
+ # Create memory service that returns invalid JSON
1024
+ memory_service = MemoryKnowledgeService(ks_config)
1025
+
1026
+ # Initial validation fails
1027
+ memory_service.add_canned_query_result(
1028
+ QueryResult(
1029
+ query_id="initial-validation",
1030
+ query_text="Rate the quality of this document on a scale " "of 0-100",
1031
+ result_data={"response": "50"}, # Fails, triggers transformation
1032
+ execution_time_ms=100,
1033
+ created_at=datetime.now(timezone.utc),
1034
+ )
1035
+ )
1036
+
1037
+ # Transformation returns invalid JSON
1038
+ memory_service.add_canned_query_result(
1039
+ QueryResult(
1040
+ query_id="bad-transformation",
1041
+ query_text="Transform this document",
1042
+ result_data={"response": "This is not valid JSON at all!"},
1043
+ execution_time_ms=200,
1044
+ created_at=datetime.now(timezone.utc),
1045
+ )
1046
+ )
1047
+
1048
+ # Create use case with configured memory service
1049
+ configured_use_case = self._create_configured_use_case(
1050
+ document_repo=document_repo,
1051
+ knowledge_service_query_repo=knowledge_service_query_repo,
1052
+ knowledge_service_config_repo=knowledge_service_config_repo,
1053
+ policy_repo=policy_repo,
1054
+ document_policy_validation_repo=document_policy_validation_repo,
1055
+ memory_service=memory_service,
1056
+ )
1057
+
1058
+ # Act & Assert
1059
+ with pytest.raises(
1060
+ ValueError,
1061
+ match="Transformation result must be valid JSON",
1062
+ ):
1063
+ await configured_use_case.validate_document(
1064
+ document_id="doc-invalid-json",
1065
+ policy_id="policy-invalid-json",
1066
+ )
1067
+
1068
+ @pytest.mark.asyncio
1069
+ async def test_transformation_query_not_found(
1070
+ self,
1071
+ use_case: ValidateDocumentUseCase,
1072
+ document_repo: MemoryDocumentRepository,
1073
+ policy_repo: MemoryPolicyRepository,
1074
+ knowledge_service_query_repo: MemoryKnowledgeServiceQueryRepository,
1075
+ knowledge_service_config_repo: MemoryKnowledgeServiceConfigRepository,
1076
+ ) -> None:
1077
+ """Test that validation fails when transformation query is not
1078
+ found."""
1079
+ # Arrange - Create test document
1080
+ content_text = "Document needing transformation"
1081
+ content_bytes = content_text.encode("utf-8")
1082
+ document = Document(
1083
+ document_id="doc-missing-query",
1084
+ original_filename="missing_query_test.txt",
1085
+ content_type="text/plain",
1086
+ size_bytes=len(content_bytes),
1087
+ content_multihash="test-hash-missing-query",
1088
+ status=DocumentStatus.CAPTURED,
1089
+ content=ContentStream(io.BytesIO(content_bytes)),
1090
+ created_at=datetime.now(timezone.utc),
1091
+ updated_at=datetime.now(timezone.utc),
1092
+ )
1093
+ await document_repo.save(document)
1094
+
1095
+ # Create policy with non-existent transformation query
1096
+ policy = Policy(
1097
+ policy_id="policy-missing-query",
1098
+ title="Missing Query Policy",
1099
+ description="Policy with missing transformation query",
1100
+ status=PolicyStatus.ACTIVE,
1101
+ validation_scores=[("quality-query", 80)],
1102
+ transformation_queries=["nonexistent-transform-query"],
1103
+ created_at=datetime.now(timezone.utc),
1104
+ updated_at=datetime.now(timezone.utc),
1105
+ )
1106
+ await policy_repo.save(policy)
1107
+
1108
+ # Create knowledge service config
1109
+ ks_config = KnowledgeServiceConfig(
1110
+ knowledge_service_id="ks-missing-query",
1111
+ name="Missing Query Service",
1112
+ description="Service config",
1113
+ service_api=ServiceApi.ANTHROPIC,
1114
+ created_at=datetime.now(timezone.utc),
1115
+ updated_at=datetime.now(timezone.utc),
1116
+ )
1117
+ await knowledge_service_config_repo.save(ks_config)
1118
+
1119
+ # Create only the validation query (transformation query is missing)
1120
+ quality_query = KnowledgeServiceQuery(
1121
+ query_id="quality-query",
1122
+ name="Quality Check",
1123
+ knowledge_service_id="ks-missing-query",
1124
+ prompt="Rate the quality of this document on a scale of 0-100",
1125
+ created_at=datetime.now(timezone.utc),
1126
+ updated_at=datetime.now(timezone.utc),
1127
+ )
1128
+ await knowledge_service_query_repo.save(quality_query)
1129
+ # Note: NOT saving the transformation query
1130
+
1131
+ # Act & Assert
1132
+ with pytest.raises(ValueError, match="Transformation query not found"):
1133
+ await use_case.validate_document(
1134
+ document_id="doc-missing-query",
1135
+ policy_id="policy-missing-query",
1136
+ )
1137
+
1138
+ @pytest.mark.asyncio
1139
+ async def test_validation_fails_with_out_of_range_scores(
1140
+ self,
1141
+ use_case: ValidateDocumentUseCase,
1142
+ document_repo: MemoryDocumentRepository,
1143
+ policy_repo: MemoryPolicyRepository,
1144
+ knowledge_service_query_repo: MemoryKnowledgeServiceQueryRepository,
1145
+ knowledge_service_config_repo: MemoryKnowledgeServiceConfigRepository,
1146
+ document_policy_validation_repo: MemoryDocumentPolicyValidationRepository,
1147
+ ) -> None:
1148
+ """Test that validation fails when domain model rejects out-of-range
1149
+ scores."""
1150
+ # Arrange - Create test document
1151
+ content_text = "Test document"
1152
+ content_bytes = content_text.encode("utf-8")
1153
+ document = Document(
1154
+ document_id="doc-789",
1155
+ original_filename="test.txt",
1156
+ content_type="text/plain",
1157
+ size_bytes=len(content_bytes),
1158
+ content_multihash="test-hash-789",
1159
+ status=DocumentStatus.CAPTURED,
1160
+ content=ContentStream(io.BytesIO(content_bytes)),
1161
+ created_at=datetime.now(timezone.utc),
1162
+ updated_at=datetime.now(timezone.utc),
1163
+ )
1164
+ await document_repo.save(document)
1165
+
1166
+ # Create policy
1167
+ policy = Policy(
1168
+ policy_id="policy-789",
1169
+ title="Test Policy",
1170
+ description="Test policy for out-of-range scores",
1171
+ status=PolicyStatus.ACTIVE,
1172
+ validation_scores=[("test-query", 80)],
1173
+ created_at=datetime.now(timezone.utc),
1174
+ updated_at=datetime.now(timezone.utc),
1175
+ )
1176
+ await policy_repo.save(policy)
1177
+
1178
+ # Create knowledge service config and query
1179
+ ks_config = KnowledgeServiceConfig(
1180
+ knowledge_service_id="ks-789",
1181
+ name="Test Knowledge Service",
1182
+ description="Test service",
1183
+ service_api=ServiceApi.ANTHROPIC,
1184
+ created_at=datetime.now(timezone.utc),
1185
+ updated_at=datetime.now(timezone.utc),
1186
+ )
1187
+ await knowledge_service_config_repo.save(ks_config)
1188
+
1189
+ test_query = KnowledgeServiceQuery(
1190
+ query_id="test-query",
1191
+ name="Test Query",
1192
+ knowledge_service_id="ks-789",
1193
+ prompt="Rate this document",
1194
+ created_at=datetime.now(timezone.utc),
1195
+ updated_at=datetime.now(timezone.utc),
1196
+ )
1197
+ await knowledge_service_query_repo.save(test_query)
1198
+
1199
+ # Create memory service with out-of-range score
1200
+ memory_service = MemoryKnowledgeService(ks_config)
1201
+ memory_service.add_canned_query_result(
1202
+ QueryResult(
1203
+ query_id="result-1",
1204
+ query_text="Rate this document",
1205
+ result_data={"response": "150"}, # Out of normal 0-100 range
1206
+ execution_time_ms=100,
1207
+ created_at=datetime.now(timezone.utc),
1208
+ )
1209
+ )
1210
+
1211
+ # Create use case with configured memory service
1212
+ configured_use_case = self._create_configured_use_case(
1213
+ document_repo=document_repo,
1214
+ knowledge_service_query_repo=knowledge_service_query_repo,
1215
+ knowledge_service_config_repo=knowledge_service_config_repo,
1216
+ policy_repo=policy_repo,
1217
+ document_policy_validation_repo=document_policy_validation_repo,
1218
+ memory_service=memory_service,
1219
+ )
1220
+
1221
+ # Act & Assert
1222
+ with pytest.raises(
1223
+ ValidationError,
1224
+ match="must be between 0 and 100",
1225
+ ):
1226
+ await configured_use_case.validate_document(
1227
+ document_id="doc-789", policy_id="policy-789"
1228
+ )