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,443 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Tests for MemoryPolicyRepository implementation.
|
|
3
|
+
|
|
4
|
+
This module provides comprehensive tests for the memory-based policy
|
|
5
|
+
repository implementation, following the testing patterns established in the
|
|
6
|
+
project.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import pytest
|
|
10
|
+
from datetime import datetime, timezone
|
|
11
|
+
|
|
12
|
+
from julee.domain.models.policy import Policy, PolicyStatus
|
|
13
|
+
from julee.repositories.memory.policy import MemoryPolicyRepository
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@pytest.fixture
|
|
17
|
+
def policy_repo() -> MemoryPolicyRepository:
|
|
18
|
+
"""Create a fresh policy repository for each test."""
|
|
19
|
+
return MemoryPolicyRepository()
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@pytest.fixture
|
|
23
|
+
def sample_policy() -> Policy:
|
|
24
|
+
"""Create a sample policy for testing."""
|
|
25
|
+
return Policy(
|
|
26
|
+
policy_id="policy-test-123",
|
|
27
|
+
title="Content Quality Policy",
|
|
28
|
+
description="Validates content meets quality standards",
|
|
29
|
+
status=PolicyStatus.ACTIVE,
|
|
30
|
+
validation_scores=[
|
|
31
|
+
("quality-check-query", 80),
|
|
32
|
+
("completeness-check", 90),
|
|
33
|
+
],
|
|
34
|
+
transformation_queries=["improve-quality", "fix-grammar"],
|
|
35
|
+
version="1.0.0",
|
|
36
|
+
created_at=datetime.now(timezone.utc),
|
|
37
|
+
updated_at=datetime.now(timezone.utc),
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@pytest.fixture
|
|
42
|
+
def validation_only_policy() -> Policy:
|
|
43
|
+
"""Create a validation-only policy (no transformations) for testing."""
|
|
44
|
+
return Policy(
|
|
45
|
+
policy_id="policy-validation-only",
|
|
46
|
+
title="Validation Only Policy",
|
|
47
|
+
description="Only validates content without transformations",
|
|
48
|
+
status=PolicyStatus.ACTIVE,
|
|
49
|
+
validation_scores=[
|
|
50
|
+
("basic-validation", 70),
|
|
51
|
+
],
|
|
52
|
+
transformation_queries=[], # Empty list - validation only
|
|
53
|
+
version="1.0.0",
|
|
54
|
+
created_at=datetime.now(timezone.utc),
|
|
55
|
+
updated_at=datetime.now(timezone.utc),
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class TestMemoryPolicyRepositoryBasicOperations:
|
|
60
|
+
"""Test basic CRUD operations on policies."""
|
|
61
|
+
|
|
62
|
+
@pytest.mark.asyncio
|
|
63
|
+
async def test_save_and_get_policy(
|
|
64
|
+
self,
|
|
65
|
+
policy_repo: MemoryPolicyRepository,
|
|
66
|
+
sample_policy: Policy,
|
|
67
|
+
) -> None:
|
|
68
|
+
"""Test saving and retrieving a policy."""
|
|
69
|
+
# Save policy
|
|
70
|
+
await policy_repo.save(sample_policy)
|
|
71
|
+
|
|
72
|
+
# Retrieve policy
|
|
73
|
+
retrieved = await policy_repo.get(sample_policy.policy_id)
|
|
74
|
+
|
|
75
|
+
assert retrieved is not None
|
|
76
|
+
assert retrieved.policy_id == sample_policy.policy_id
|
|
77
|
+
assert retrieved.title == sample_policy.title
|
|
78
|
+
assert retrieved.description == sample_policy.description
|
|
79
|
+
assert retrieved.status == sample_policy.status
|
|
80
|
+
assert retrieved.validation_scores == sample_policy.validation_scores
|
|
81
|
+
assert retrieved.transformation_queries == sample_policy.transformation_queries
|
|
82
|
+
assert retrieved.version == sample_policy.version
|
|
83
|
+
|
|
84
|
+
@pytest.mark.asyncio
|
|
85
|
+
async def test_get_nonexistent_policy(
|
|
86
|
+
self, policy_repo: MemoryPolicyRepository
|
|
87
|
+
) -> None:
|
|
88
|
+
"""Test retrieving a non-existent policy returns None."""
|
|
89
|
+
result = await policy_repo.get("nonexistent-policy")
|
|
90
|
+
assert result is None
|
|
91
|
+
|
|
92
|
+
@pytest.mark.asyncio
|
|
93
|
+
async def test_generate_id(self, policy_repo: MemoryPolicyRepository) -> None:
|
|
94
|
+
"""Test generating unique policy IDs."""
|
|
95
|
+
id1 = await policy_repo.generate_id()
|
|
96
|
+
id2 = await policy_repo.generate_id()
|
|
97
|
+
|
|
98
|
+
assert isinstance(id1, str)
|
|
99
|
+
assert isinstance(id2, str)
|
|
100
|
+
assert id1 != id2
|
|
101
|
+
assert len(id1) > 0
|
|
102
|
+
assert len(id2) > 0
|
|
103
|
+
assert id1.startswith("policy-")
|
|
104
|
+
assert id2.startswith("policy-")
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
class TestMemoryPolicyRepositoryPolicyTypes:
|
|
108
|
+
"""Test handling of different policy types."""
|
|
109
|
+
|
|
110
|
+
@pytest.mark.asyncio
|
|
111
|
+
async def test_validation_only_policy(
|
|
112
|
+
self,
|
|
113
|
+
policy_repo: MemoryPolicyRepository,
|
|
114
|
+
validation_only_policy: Policy,
|
|
115
|
+
) -> None:
|
|
116
|
+
"""Test storing and retrieving validation-only policy."""
|
|
117
|
+
# Save validation-only policy
|
|
118
|
+
await policy_repo.save(validation_only_policy)
|
|
119
|
+
|
|
120
|
+
# Retrieve and verify
|
|
121
|
+
retrieved = await policy_repo.get(validation_only_policy.policy_id)
|
|
122
|
+
assert retrieved is not None
|
|
123
|
+
assert retrieved.is_validation_only is True
|
|
124
|
+
assert retrieved.has_transformations is False
|
|
125
|
+
assert retrieved.transformation_queries == []
|
|
126
|
+
|
|
127
|
+
@pytest.mark.asyncio
|
|
128
|
+
async def test_transformation_policy(
|
|
129
|
+
self,
|
|
130
|
+
policy_repo: MemoryPolicyRepository,
|
|
131
|
+
sample_policy: Policy,
|
|
132
|
+
) -> None:
|
|
133
|
+
"""Test storing and retrieving policy with transformations."""
|
|
134
|
+
# Save transformation policy
|
|
135
|
+
await policy_repo.save(sample_policy)
|
|
136
|
+
|
|
137
|
+
# Retrieve and verify
|
|
138
|
+
retrieved = await policy_repo.get(sample_policy.policy_id)
|
|
139
|
+
assert retrieved is not None
|
|
140
|
+
assert retrieved.is_validation_only is False
|
|
141
|
+
assert retrieved.has_transformations is True
|
|
142
|
+
assert retrieved.transformation_queries is not None
|
|
143
|
+
assert len(retrieved.transformation_queries) == 2
|
|
144
|
+
assert "improve-quality" in retrieved.transformation_queries
|
|
145
|
+
assert "fix-grammar" in retrieved.transformation_queries
|
|
146
|
+
|
|
147
|
+
@pytest.mark.asyncio
|
|
148
|
+
async def test_policy_with_none_transformations(
|
|
149
|
+
self, policy_repo: MemoryPolicyRepository
|
|
150
|
+
) -> None:
|
|
151
|
+
"""Test policy with None transformation queries."""
|
|
152
|
+
policy = Policy(
|
|
153
|
+
policy_id="policy-none-transforms",
|
|
154
|
+
title="Policy with None Transformations",
|
|
155
|
+
description="Policy where transformation_queries is None",
|
|
156
|
+
validation_scores=[("test-query", 75)],
|
|
157
|
+
transformation_queries=None, # Explicitly None
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
await policy_repo.save(policy)
|
|
161
|
+
retrieved = await policy_repo.get(policy.policy_id)
|
|
162
|
+
|
|
163
|
+
assert retrieved is not None
|
|
164
|
+
assert retrieved.transformation_queries is None
|
|
165
|
+
assert retrieved.is_validation_only is True
|
|
166
|
+
assert retrieved.has_transformations is False
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
class TestMemoryPolicyRepositoryUpdates:
|
|
170
|
+
"""Test policy update operations."""
|
|
171
|
+
|
|
172
|
+
@pytest.mark.asyncio
|
|
173
|
+
async def test_update_policy_status(
|
|
174
|
+
self,
|
|
175
|
+
policy_repo: MemoryPolicyRepository,
|
|
176
|
+
sample_policy: Policy,
|
|
177
|
+
) -> None:
|
|
178
|
+
"""Test updating policy status."""
|
|
179
|
+
# Save initial policy
|
|
180
|
+
await policy_repo.save(sample_policy)
|
|
181
|
+
|
|
182
|
+
# Update status
|
|
183
|
+
sample_policy.status = PolicyStatus.INACTIVE
|
|
184
|
+
await policy_repo.save(sample_policy)
|
|
185
|
+
|
|
186
|
+
# Verify update
|
|
187
|
+
retrieved = await policy_repo.get(sample_policy.policy_id)
|
|
188
|
+
assert retrieved is not None
|
|
189
|
+
assert retrieved.status == PolicyStatus.INACTIVE
|
|
190
|
+
|
|
191
|
+
@pytest.mark.asyncio
|
|
192
|
+
async def test_update_policy_validation_scores(
|
|
193
|
+
self,
|
|
194
|
+
policy_repo: MemoryPolicyRepository,
|
|
195
|
+
sample_policy: Policy,
|
|
196
|
+
) -> None:
|
|
197
|
+
"""Test updating policy validation scores."""
|
|
198
|
+
# Save initial policy
|
|
199
|
+
await policy_repo.save(sample_policy)
|
|
200
|
+
|
|
201
|
+
# Update validation scores
|
|
202
|
+
sample_policy.validation_scores = [
|
|
203
|
+
("new-quality-check", 85),
|
|
204
|
+
("advanced-validation", 95),
|
|
205
|
+
]
|
|
206
|
+
await policy_repo.save(sample_policy)
|
|
207
|
+
|
|
208
|
+
# Verify update
|
|
209
|
+
retrieved = await policy_repo.get(sample_policy.policy_id)
|
|
210
|
+
assert retrieved is not None
|
|
211
|
+
assert len(retrieved.validation_scores) == 2
|
|
212
|
+
assert ("new-quality-check", 85) in retrieved.validation_scores
|
|
213
|
+
assert ("advanced-validation", 95) in retrieved.validation_scores
|
|
214
|
+
|
|
215
|
+
@pytest.mark.asyncio
|
|
216
|
+
async def test_update_transformation_queries(
|
|
217
|
+
self,
|
|
218
|
+
policy_repo: MemoryPolicyRepository,
|
|
219
|
+
sample_policy: Policy,
|
|
220
|
+
) -> None:
|
|
221
|
+
"""Test updating transformation queries."""
|
|
222
|
+
# Save initial policy
|
|
223
|
+
await policy_repo.save(sample_policy)
|
|
224
|
+
|
|
225
|
+
# Update transformation queries
|
|
226
|
+
sample_policy.transformation_queries = ["new-transform"]
|
|
227
|
+
await policy_repo.save(sample_policy)
|
|
228
|
+
|
|
229
|
+
# Verify update
|
|
230
|
+
retrieved = await policy_repo.get(sample_policy.policy_id)
|
|
231
|
+
assert retrieved is not None
|
|
232
|
+
assert retrieved.transformation_queries == ["new-transform"]
|
|
233
|
+
assert retrieved.has_transformations is True
|
|
234
|
+
|
|
235
|
+
@pytest.mark.asyncio
|
|
236
|
+
async def test_save_updates_timestamp(
|
|
237
|
+
self,
|
|
238
|
+
policy_repo: MemoryPolicyRepository,
|
|
239
|
+
sample_policy: Policy,
|
|
240
|
+
) -> None:
|
|
241
|
+
"""Test that save operations update the updated_at timestamp."""
|
|
242
|
+
original_updated_at = sample_policy.updated_at
|
|
243
|
+
|
|
244
|
+
# Save policy
|
|
245
|
+
await policy_repo.save(sample_policy)
|
|
246
|
+
|
|
247
|
+
# Retrieve and check timestamp was updated
|
|
248
|
+
retrieved = await policy_repo.get(sample_policy.policy_id)
|
|
249
|
+
assert retrieved is not None
|
|
250
|
+
assert retrieved.updated_at is not None
|
|
251
|
+
assert original_updated_at is not None
|
|
252
|
+
assert retrieved.updated_at > original_updated_at
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
class TestMemoryPolicyRepositoryEdgeCases:
|
|
256
|
+
"""Test edge cases and complex scenarios."""
|
|
257
|
+
|
|
258
|
+
@pytest.mark.asyncio
|
|
259
|
+
async def test_policy_with_complex_validation_scores(
|
|
260
|
+
self, policy_repo: MemoryPolicyRepository
|
|
261
|
+
) -> None:
|
|
262
|
+
"""Test policy with many validation scores."""
|
|
263
|
+
policy = Policy(
|
|
264
|
+
policy_id="complex-policy",
|
|
265
|
+
title="Complex Validation Policy",
|
|
266
|
+
description="Policy with multiple validation criteria",
|
|
267
|
+
validation_scores=[
|
|
268
|
+
("grammar-check", 80),
|
|
269
|
+
("completeness-check", 85),
|
|
270
|
+
("accuracy-check", 90),
|
|
271
|
+
("style-check", 75),
|
|
272
|
+
("readability-check", 70),
|
|
273
|
+
],
|
|
274
|
+
transformation_queries=["improve-all-aspects"],
|
|
275
|
+
)
|
|
276
|
+
|
|
277
|
+
await policy_repo.save(policy)
|
|
278
|
+
retrieved = await policy_repo.get("complex-policy")
|
|
279
|
+
|
|
280
|
+
assert retrieved is not None
|
|
281
|
+
assert len(retrieved.validation_scores) == 5
|
|
282
|
+
assert retrieved.has_transformations is True
|
|
283
|
+
assert retrieved.transformation_queries is not None
|
|
284
|
+
assert len(retrieved.transformation_queries) == 1
|
|
285
|
+
|
|
286
|
+
@pytest.mark.asyncio
|
|
287
|
+
async def test_policy_lifecycle_management(
|
|
288
|
+
self, policy_repo: MemoryPolicyRepository
|
|
289
|
+
) -> None:
|
|
290
|
+
"""Test complete policy lifecycle from draft to deprecated."""
|
|
291
|
+
# Create draft policy
|
|
292
|
+
policy = Policy(
|
|
293
|
+
policy_id="lifecycle-policy",
|
|
294
|
+
title="Lifecycle Test Policy",
|
|
295
|
+
description="Testing policy lifecycle",
|
|
296
|
+
status=PolicyStatus.DRAFT,
|
|
297
|
+
validation_scores=[("test-check", 80)],
|
|
298
|
+
version="0.1.0",
|
|
299
|
+
)
|
|
300
|
+
|
|
301
|
+
# Save as draft
|
|
302
|
+
await policy_repo.save(policy)
|
|
303
|
+
retrieved = await policy_repo.get("lifecycle-policy")
|
|
304
|
+
assert retrieved is not None
|
|
305
|
+
assert retrieved.status == PolicyStatus.DRAFT
|
|
306
|
+
|
|
307
|
+
# Activate policy
|
|
308
|
+
policy.status = PolicyStatus.ACTIVE
|
|
309
|
+
policy.version = "1.0.0"
|
|
310
|
+
await policy_repo.save(policy)
|
|
311
|
+
|
|
312
|
+
retrieved = await policy_repo.get("lifecycle-policy")
|
|
313
|
+
assert retrieved is not None
|
|
314
|
+
assert retrieved.status == PolicyStatus.ACTIVE
|
|
315
|
+
assert retrieved.version == "1.0.0"
|
|
316
|
+
|
|
317
|
+
# Deprecate policy
|
|
318
|
+
policy.status = PolicyStatus.DEPRECATED
|
|
319
|
+
await policy_repo.save(policy)
|
|
320
|
+
|
|
321
|
+
retrieved = await policy_repo.get("lifecycle-policy")
|
|
322
|
+
assert retrieved is not None
|
|
323
|
+
assert retrieved.status == PolicyStatus.DEPRECATED
|
|
324
|
+
|
|
325
|
+
@pytest.mark.asyncio
|
|
326
|
+
async def test_multiple_policies_independence(
|
|
327
|
+
self, policy_repo: MemoryPolicyRepository
|
|
328
|
+
) -> None:
|
|
329
|
+
"""Test that multiple policies are stored independently."""
|
|
330
|
+
# Create multiple policies
|
|
331
|
+
policy1 = Policy(
|
|
332
|
+
policy_id="policy-1",
|
|
333
|
+
title="First Policy",
|
|
334
|
+
description="First test policy",
|
|
335
|
+
validation_scores=[("check-1", 80)],
|
|
336
|
+
)
|
|
337
|
+
|
|
338
|
+
policy2 = Policy(
|
|
339
|
+
policy_id="policy-2",
|
|
340
|
+
title="Second Policy",
|
|
341
|
+
description="Second test policy",
|
|
342
|
+
validation_scores=[("check-2", 90)],
|
|
343
|
+
transformation_queries=["transform-2"],
|
|
344
|
+
)
|
|
345
|
+
|
|
346
|
+
# Save both policies
|
|
347
|
+
await policy_repo.save(policy1)
|
|
348
|
+
await policy_repo.save(policy2)
|
|
349
|
+
|
|
350
|
+
# Retrieve both and verify independence
|
|
351
|
+
retrieved1 = await policy_repo.get("policy-1")
|
|
352
|
+
retrieved2 = await policy_repo.get("policy-2")
|
|
353
|
+
|
|
354
|
+
assert retrieved1 is not None
|
|
355
|
+
assert retrieved2 is not None
|
|
356
|
+
assert retrieved1.title == "First Policy"
|
|
357
|
+
assert retrieved2.title == "Second Policy"
|
|
358
|
+
assert retrieved1.is_validation_only is True
|
|
359
|
+
assert retrieved2.has_transformations is True
|
|
360
|
+
|
|
361
|
+
# Update one policy and verify the other is unchanged
|
|
362
|
+
policy1.title = "Updated First Policy"
|
|
363
|
+
await policy_repo.save(policy1)
|
|
364
|
+
|
|
365
|
+
retrieved1_updated = await policy_repo.get("policy-1")
|
|
366
|
+
retrieved2_unchanged = await policy_repo.get("policy-2")
|
|
367
|
+
|
|
368
|
+
assert retrieved1_updated is not None
|
|
369
|
+
assert retrieved2_unchanged is not None
|
|
370
|
+
assert retrieved1_updated.title == "Updated First Policy"
|
|
371
|
+
assert retrieved2_unchanged.title == "Second Policy"
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
class TestMemoryPolicyRepositoryIdempotency:
|
|
375
|
+
"""Test idempotent operations."""
|
|
376
|
+
|
|
377
|
+
@pytest.mark.asyncio
|
|
378
|
+
async def test_save_idempotency(
|
|
379
|
+
self,
|
|
380
|
+
policy_repo: MemoryPolicyRepository,
|
|
381
|
+
sample_policy: Policy,
|
|
382
|
+
) -> None:
|
|
383
|
+
"""Test that saving the same policy multiple times is idempotent."""
|
|
384
|
+
# Save policy first time
|
|
385
|
+
await policy_repo.save(sample_policy)
|
|
386
|
+
|
|
387
|
+
# Get the policy to check initial state
|
|
388
|
+
retrieved1 = await policy_repo.get(sample_policy.policy_id)
|
|
389
|
+
assert retrieved1 is not None
|
|
390
|
+
first_updated_at = retrieved1.updated_at
|
|
391
|
+
|
|
392
|
+
# Save same policy again (should update timestamp but maintain data)
|
|
393
|
+
await policy_repo.save(sample_policy)
|
|
394
|
+
|
|
395
|
+
# Verify policy is still there with updated timestamp
|
|
396
|
+
retrieved2 = await policy_repo.get(sample_policy.policy_id)
|
|
397
|
+
assert retrieved2 is not None
|
|
398
|
+
assert retrieved2.policy_id == sample_policy.policy_id
|
|
399
|
+
assert retrieved2.title == sample_policy.title
|
|
400
|
+
assert retrieved2.validation_scores == sample_policy.validation_scores
|
|
401
|
+
assert retrieved2.updated_at is not None
|
|
402
|
+
assert first_updated_at is not None
|
|
403
|
+
assert retrieved2.updated_at > first_updated_at
|
|
404
|
+
|
|
405
|
+
|
|
406
|
+
class TestMemoryPolicyRepositoryRoundtrip:
|
|
407
|
+
"""Test full round-trip scenarios."""
|
|
408
|
+
|
|
409
|
+
@pytest.mark.asyncio
|
|
410
|
+
async def test_full_policy_lifecycle_success(
|
|
411
|
+
self, policy_repo: MemoryPolicyRepository
|
|
412
|
+
) -> None:
|
|
413
|
+
"""Test complete successful policy lifecycle."""
|
|
414
|
+
# Generate new policy ID
|
|
415
|
+
policy_id = await policy_repo.generate_id()
|
|
416
|
+
|
|
417
|
+
# Create and save initial policy
|
|
418
|
+
policy = Policy(
|
|
419
|
+
policy_id=policy_id,
|
|
420
|
+
title="Round-trip Test Policy",
|
|
421
|
+
description="Testing complete policy round-trip",
|
|
422
|
+
status=PolicyStatus.DRAFT,
|
|
423
|
+
validation_scores=[("round-trip-check", 85)],
|
|
424
|
+
transformation_queries=["round-trip-transform"],
|
|
425
|
+
version="0.1.0",
|
|
426
|
+
created_at=datetime.now(timezone.utc),
|
|
427
|
+
updated_at=datetime.now(timezone.utc),
|
|
428
|
+
)
|
|
429
|
+
await policy_repo.save(policy)
|
|
430
|
+
|
|
431
|
+
# Activate policy
|
|
432
|
+
policy.status = PolicyStatus.ACTIVE
|
|
433
|
+
policy.version = "1.0.0"
|
|
434
|
+
await policy_repo.save(policy)
|
|
435
|
+
|
|
436
|
+
# Final verification
|
|
437
|
+
retrieved = await policy_repo.get(policy_id)
|
|
438
|
+
assert retrieved is not None
|
|
439
|
+
assert retrieved.status == PolicyStatus.ACTIVE
|
|
440
|
+
assert retrieved.version == "1.0.0"
|
|
441
|
+
assert retrieved.has_transformations is True
|
|
442
|
+
assert len(retrieved.validation_scores) == 1
|
|
443
|
+
assert retrieved.validation_scores[0] == ("round-trip-check", 85)
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Minio repository implementations for julee domain.
|
|
3
|
+
|
|
4
|
+
This module exports Minio-based implementations of all repository protocols
|
|
5
|
+
for the Capture, Extract, Assemble, Publish workflow. These implementations
|
|
6
|
+
use Minio for object storage and are suitable for production environments
|
|
7
|
+
where persistent, scalable storage is required.
|
|
8
|
+
|
|
9
|
+
All implementations maintain the same async interfaces as their memory
|
|
10
|
+
counterparts while providing durable, distributed storage capabilities.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from .assembly import MinioAssemblyRepository
|
|
14
|
+
from .assembly_specification import MinioAssemblySpecificationRepository
|
|
15
|
+
from .document import MinioDocumentRepository
|
|
16
|
+
from .document_policy_validation import (
|
|
17
|
+
MinioDocumentPolicyValidationRepository,
|
|
18
|
+
)
|
|
19
|
+
from .knowledge_service_config import MinioKnowledgeServiceConfigRepository
|
|
20
|
+
from .knowledge_service_query import MinioKnowledgeServiceQueryRepository
|
|
21
|
+
from .policy import MinioPolicyRepository
|
|
22
|
+
|
|
23
|
+
__all__ = [
|
|
24
|
+
"MinioAssemblyRepository",
|
|
25
|
+
"MinioAssemblySpecificationRepository",
|
|
26
|
+
"MinioDocumentRepository",
|
|
27
|
+
"MinioDocumentPolicyValidationRepository",
|
|
28
|
+
"MinioKnowledgeServiceConfigRepository",
|
|
29
|
+
"MinioKnowledgeServiceQueryRepository",
|
|
30
|
+
"MinioPolicyRepository",
|
|
31
|
+
]
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Minio implementation of AssemblyRepository.
|
|
3
|
+
|
|
4
|
+
This module provides a Minio-based implementation of the AssemblyRepository
|
|
5
|
+
protocol that follows the Clean Architecture patterns defined in the
|
|
6
|
+
Fun-Police Framework. It handles assembly storage, ensuring idempotency and
|
|
7
|
+
proper error handling.
|
|
8
|
+
|
|
9
|
+
The implementation stores assembly data as JSON objects in Minio, following
|
|
10
|
+
the large payload handling pattern from the architectural guidelines.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
import logging
|
|
14
|
+
from typing import Optional, List, Dict
|
|
15
|
+
|
|
16
|
+
from julee.domain.models.assembly import Assembly
|
|
17
|
+
from julee.domain.repositories.assembly import AssemblyRepository
|
|
18
|
+
from .client import MinioClient, MinioRepositoryMixin
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class MinioAssemblyRepository(AssemblyRepository, MinioRepositoryMixin):
|
|
22
|
+
"""
|
|
23
|
+
Minio implementation of AssemblyRepository using Minio for persistence.
|
|
24
|
+
|
|
25
|
+
This implementation stores assembly data as JSON objects in the
|
|
26
|
+
"assemblies" bucket.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
def __init__(self, client: MinioClient) -> None:
|
|
30
|
+
"""Initialize repository with Minio client.
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
client: MinioClient protocol implementation (real or fake)
|
|
34
|
+
"""
|
|
35
|
+
self.client = client
|
|
36
|
+
self.logger = logging.getLogger("MinioAssemblyRepository")
|
|
37
|
+
self.assembly_bucket = "assemblies"
|
|
38
|
+
self.ensure_buckets_exist([self.assembly_bucket])
|
|
39
|
+
|
|
40
|
+
async def get(self, assembly_id: str) -> Optional[Assembly]:
|
|
41
|
+
"""Retrieve an assembly by ID."""
|
|
42
|
+
# Get the assembly using mixin methods
|
|
43
|
+
assembly = self.get_json_object(
|
|
44
|
+
bucket_name=self.assembly_bucket,
|
|
45
|
+
object_name=assembly_id,
|
|
46
|
+
model_class=Assembly,
|
|
47
|
+
not_found_log_message="Assembly not found",
|
|
48
|
+
error_log_message="Error retrieving assembly",
|
|
49
|
+
extra_log_data={"assembly_id": assembly_id},
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
return assembly
|
|
53
|
+
|
|
54
|
+
async def save(self, assembly: Assembly) -> None:
|
|
55
|
+
"""Save assembly metadata (status, updated_at, etc.)."""
|
|
56
|
+
# Update timestamp
|
|
57
|
+
self.update_timestamps(assembly)
|
|
58
|
+
|
|
59
|
+
self.put_json_object(
|
|
60
|
+
bucket_name=self.assembly_bucket,
|
|
61
|
+
object_name=assembly.assembly_id,
|
|
62
|
+
model=assembly,
|
|
63
|
+
success_log_message="Assembly saved successfully",
|
|
64
|
+
error_log_message="Error saving assembly",
|
|
65
|
+
extra_log_data={
|
|
66
|
+
"assembly_id": assembly.assembly_id,
|
|
67
|
+
"status": assembly.status.value,
|
|
68
|
+
"assembled_document_id": assembly.assembled_document_id,
|
|
69
|
+
},
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
async def get_many(self, assembly_ids: List[str]) -> Dict[str, Optional[Assembly]]:
|
|
73
|
+
"""Retrieve multiple assemblies by ID.
|
|
74
|
+
|
|
75
|
+
Args:
|
|
76
|
+
assembly_ids: List of unique assembly identifiers
|
|
77
|
+
|
|
78
|
+
Returns:
|
|
79
|
+
Dict mapping assembly_id to Assembly (or None if not found)
|
|
80
|
+
"""
|
|
81
|
+
# Convert assembly IDs to object names (direct mapping in this case)
|
|
82
|
+
object_names = assembly_ids
|
|
83
|
+
|
|
84
|
+
# Get objects from Minio using batch method
|
|
85
|
+
object_results = self.get_many_json_objects(
|
|
86
|
+
bucket_name=self.assembly_bucket,
|
|
87
|
+
object_names=object_names,
|
|
88
|
+
model_class=Assembly,
|
|
89
|
+
not_found_log_message="Assembly not found",
|
|
90
|
+
error_log_message="Error retrieving assembly",
|
|
91
|
+
extra_log_data={"assembly_ids": assembly_ids},
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
# Convert object names back to assembly IDs for the result
|
|
95
|
+
result: Dict[str, Optional[Assembly]] = {}
|
|
96
|
+
for assembly_id in assembly_ids:
|
|
97
|
+
result[assembly_id] = object_results[assembly_id]
|
|
98
|
+
|
|
99
|
+
return result
|
|
100
|
+
|
|
101
|
+
async def generate_id(self) -> str:
|
|
102
|
+
"""Generate a unique assembly identifier."""
|
|
103
|
+
return self.generate_id_with_prefix("assembly")
|