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,192 @@
1
+ """
2
+ Tests for MinioDocumentPolicyValidationRepository implementation.
3
+
4
+ This module provides tests for the Minio-based document policy validation
5
+ repository implementation, focusing on functionality specific to this
6
+ repository that differs from the inherited mixins. Uses the fake client to
7
+ avoid external dependencies during testing.
8
+ """
9
+
10
+ import pytest
11
+ from datetime import datetime, timezone
12
+
13
+ from julee.domain.models.policy import (
14
+ DocumentPolicyValidation,
15
+ DocumentPolicyValidationStatus,
16
+ )
17
+ from julee.repositories.minio.document_policy_validation import (
18
+ MinioDocumentPolicyValidationRepository,
19
+ )
20
+ from .fake_client import FakeMinioClient
21
+
22
+
23
+ @pytest.fixture
24
+ def fake_client() -> FakeMinioClient:
25
+ """Create a fresh fake Minio client for each test."""
26
+ return FakeMinioClient()
27
+
28
+
29
+ @pytest.fixture
30
+ def validation_repo(
31
+ fake_client: FakeMinioClient,
32
+ ) -> MinioDocumentPolicyValidationRepository:
33
+ """Create validation repository with fake client."""
34
+ return MinioDocumentPolicyValidationRepository(fake_client)
35
+
36
+
37
+ @pytest.fixture
38
+ def sample_validation() -> DocumentPolicyValidation:
39
+ """Create a sample document policy validation for testing."""
40
+ return DocumentPolicyValidation(
41
+ validation_id="validation-test-123",
42
+ input_document_id="doc-123",
43
+ policy_id="policy-456",
44
+ status=DocumentPolicyValidationStatus.PASSED,
45
+ validation_scores=[
46
+ ("quality-check-query", 85),
47
+ ("completeness-check", 92),
48
+ ],
49
+ transformed_document_id="doc-123-transformed",
50
+ post_transform_validation_scores=[
51
+ ("quality-check-query", 95),
52
+ ("completeness-check", 88),
53
+ ],
54
+ started_at=datetime.now(timezone.utc),
55
+ completed_at=datetime.now(timezone.utc),
56
+ passed=True,
57
+ )
58
+
59
+
60
+ class TestMinioDocumentPolicyValidationRepositorySpecific:
61
+ """Test functionality specific to DocumentPolicyValidation Minio
62
+ repository."""
63
+
64
+ @pytest.mark.asyncio
65
+ async def test_initialization_creates_correct_bucket(
66
+ self, fake_client: FakeMinioClient
67
+ ) -> None:
68
+ """Test that repository initialization creates the correct bucket."""
69
+ # Check bucket doesn't exist initially
70
+ assert "document-policy-validations" not in fake_client._buckets
71
+
72
+ # Create repository - should create bucket
73
+ repo = MinioDocumentPolicyValidationRepository(fake_client)
74
+
75
+ # Check bucket was created
76
+ assert "document-policy-validations" in fake_client._buckets
77
+ assert repo.validations_bucket == "document-policy-validations"
78
+
79
+ @pytest.mark.asyncio
80
+ async def test_generate_id_prefix(
81
+ self, validation_repo: MinioDocumentPolicyValidationRepository
82
+ ) -> None:
83
+ """Test that generated IDs use the correct prefix for validations."""
84
+ id1 = await validation_repo.generate_id()
85
+ id2 = await validation_repo.generate_id()
86
+
87
+ assert isinstance(id1, str)
88
+ assert isinstance(id2, str)
89
+ assert id1 != id2
90
+ assert len(id1) > 0
91
+ assert len(id2) > 0
92
+ assert id1.startswith("validation-")
93
+ assert id2.startswith("validation-")
94
+
95
+ @pytest.mark.asyncio
96
+ async def test_save_and_retrieve_validation(
97
+ self,
98
+ validation_repo: MinioDocumentPolicyValidationRepository,
99
+ sample_validation: DocumentPolicyValidation,
100
+ ) -> None:
101
+ """Test that save and retrieve operations work correctly."""
102
+ # Save the validation
103
+ await validation_repo.save(sample_validation)
104
+
105
+ # Retrieve and verify all fields are preserved
106
+ retrieved = await validation_repo.get(sample_validation.validation_id)
107
+
108
+ assert retrieved is not None
109
+ assert retrieved.validation_id == sample_validation.validation_id
110
+ assert retrieved.input_document_id == sample_validation.input_document_id
111
+ assert retrieved.policy_id == sample_validation.policy_id
112
+ assert retrieved.status == sample_validation.status
113
+ assert retrieved.validation_scores == sample_validation.validation_scores
114
+ assert (
115
+ retrieved.transformed_document_id
116
+ == sample_validation.transformed_document_id
117
+ )
118
+ assert (
119
+ retrieved.post_transform_validation_scores
120
+ == sample_validation.post_transform_validation_scores
121
+ )
122
+ assert retrieved.passed == sample_validation.passed
123
+
124
+ @pytest.mark.asyncio
125
+ async def test_get_nonexistent_validation_returns_none(
126
+ self, validation_repo: MinioDocumentPolicyValidationRepository
127
+ ) -> None:
128
+ """Test retrieving a non-existent validation returns None."""
129
+ result = await validation_repo.get("nonexistent-validation")
130
+ assert result is None
131
+
132
+ @pytest.mark.asyncio
133
+ async def test_save_preserves_timestamps(
134
+ self, validation_repo: MinioDocumentPolicyValidationRepository
135
+ ) -> None:
136
+ """Test that save operation preserves existing timestamps."""
137
+ # Create validation with specific timestamps
138
+ original_time = datetime(2024, 1, 1, 12, 0, 0, tzinfo=timezone.utc)
139
+ validation = DocumentPolicyValidation(
140
+ validation_id="validation-timestamp-test",
141
+ input_document_id="doc-timestamp",
142
+ policy_id="policy-timestamp",
143
+ status=DocumentPolicyValidationStatus.PENDING,
144
+ validation_scores=[],
145
+ started_at=original_time,
146
+ completed_at=None,
147
+ )
148
+
149
+ # Save and retrieve the validation
150
+ await validation_repo.save(validation)
151
+ retrieved = await validation_repo.get("validation-timestamp-test")
152
+
153
+ # Check that timestamps are preserved correctly
154
+ assert retrieved is not None
155
+ assert retrieved.started_at == original_time
156
+ assert retrieved.completed_at is None
157
+
158
+ @pytest.mark.asyncio
159
+ async def test_save_with_transformation_data(
160
+ self, validation_repo: MinioDocumentPolicyValidationRepository
161
+ ) -> None:
162
+ """Test saving and retrieving validation with transformation data."""
163
+ validation_with_transforms = DocumentPolicyValidation(
164
+ validation_id="validation-transform-test",
165
+ input_document_id="doc-original",
166
+ policy_id="policy-transform",
167
+ status=DocumentPolicyValidationStatus.TRANSFORMATION_COMPLETE,
168
+ validation_scores=[("initial-check", 70)],
169
+ transformed_document_id="doc-transformed",
170
+ post_transform_validation_scores=[("final-check", 85)],
171
+ passed=True,
172
+ )
173
+
174
+ # Save and retrieve the validation
175
+ await validation_repo.save(validation_with_transforms)
176
+ retrieved = await validation_repo.get("validation-transform-test")
177
+
178
+ # Verify transformation data is preserved
179
+ assert retrieved is not None
180
+ assert retrieved.transformed_document_id == "doc-transformed"
181
+ assert retrieved.post_transform_validation_scores == [("final-check", 85)]
182
+ assert retrieved.passed is True
183
+ assert retrieved.validation_scores == [("initial-check", 70)]
184
+
185
+ @pytest.mark.asyncio
186
+ async def test_repository_logger_configuration(
187
+ self, validation_repo: MinioDocumentPolicyValidationRepository
188
+ ) -> None:
189
+ """Test that repository logger is configured correctly."""
190
+ assert validation_repo.logger is not None
191
+ # Logger name should reflect the repository class
192
+ assert "MinioDocumentPolicyValidationRepository" in validation_repo.logger.name
@@ -0,0 +1,374 @@
1
+ """
2
+ Tests for MinioKnowledgeServiceConfigRepository implementation.
3
+
4
+ This module provides comprehensive tests for the Minio-based knowledge service
5
+ configuration repository implementation, using the fake client to avoid
6
+ external dependencies during testing.
7
+ """
8
+
9
+ import pytest
10
+ from datetime import datetime, timezone
11
+
12
+ from julee.domain.models.knowledge_service_config import (
13
+ KnowledgeServiceConfig,
14
+ )
15
+ from julee.domain.models.knowledge_service_config import ServiceApi
16
+ from julee.repositories.minio.knowledge_service_config import (
17
+ MinioKnowledgeServiceConfigRepository,
18
+ )
19
+ from .fake_client import FakeMinioClient
20
+
21
+
22
+ @pytest.fixture
23
+ def fake_client() -> FakeMinioClient:
24
+ """Create a fresh fake Minio client for each test."""
25
+ return FakeMinioClient()
26
+
27
+
28
+ @pytest.fixture
29
+ def knowledge_service_config_repo(
30
+ fake_client: FakeMinioClient,
31
+ ) -> MinioKnowledgeServiceConfigRepository:
32
+ """Create knowledge service config repository with fake client."""
33
+ return MinioKnowledgeServiceConfigRepository(fake_client)
34
+
35
+
36
+ @pytest.fixture
37
+ def sample_knowledge_service_config() -> KnowledgeServiceConfig:
38
+ """Create a sample knowledge service config for testing."""
39
+ return KnowledgeServiceConfig(
40
+ knowledge_service_id="ks-test-123",
41
+ name="Test Anthropic Service",
42
+ description="A test knowledge service using Anthropic API",
43
+ service_api=ServiceApi.ANTHROPIC,
44
+ created_at=datetime.now(timezone.utc),
45
+ updated_at=datetime.now(timezone.utc),
46
+ )
47
+
48
+
49
+ class TestMinioKnowledgeServiceConfigRepositoryBasicOperations:
50
+ """Test basic CRUD operations on knowledge service configurations."""
51
+
52
+ @pytest.mark.asyncio
53
+ async def test_save_and_get_knowledge_service_config(
54
+ self,
55
+ knowledge_service_config_repo: MinioKnowledgeServiceConfigRepository,
56
+ sample_knowledge_service_config: KnowledgeServiceConfig,
57
+ ) -> None:
58
+ """Test saving and retrieving a knowledge service configuration."""
59
+ # Save knowledge service config
60
+ await knowledge_service_config_repo.save(sample_knowledge_service_config)
61
+
62
+ # Retrieve knowledge service config
63
+ retrieved = await knowledge_service_config_repo.get(
64
+ sample_knowledge_service_config.knowledge_service_id
65
+ )
66
+
67
+ assert retrieved is not None
68
+ assert (
69
+ retrieved.knowledge_service_id
70
+ == sample_knowledge_service_config.knowledge_service_id
71
+ )
72
+ assert retrieved.name == sample_knowledge_service_config.name
73
+ assert retrieved.description == sample_knowledge_service_config.description
74
+ assert retrieved.service_api == sample_knowledge_service_config.service_api
75
+ assert retrieved.created_at is not None
76
+ assert retrieved.updated_at is not None
77
+
78
+ @pytest.mark.asyncio
79
+ async def test_get_nonexistent_knowledge_service_config(
80
+ self,
81
+ knowledge_service_config_repo: MinioKnowledgeServiceConfigRepository,
82
+ ) -> None:
83
+ """Test retrieving a non-existent knowledge service config returns
84
+ None."""
85
+ result = await knowledge_service_config_repo.get("nonexistent-ks-config")
86
+ assert result is None
87
+
88
+ @pytest.mark.asyncio
89
+ async def test_generate_id(
90
+ self,
91
+ knowledge_service_config_repo: MinioKnowledgeServiceConfigRepository,
92
+ ) -> None:
93
+ """Test generating unique knowledge service configuration IDs."""
94
+ id1 = await knowledge_service_config_repo.generate_id()
95
+ id2 = await knowledge_service_config_repo.generate_id()
96
+
97
+ assert isinstance(id1, str)
98
+ assert isinstance(id2, str)
99
+ assert id1 != id2
100
+ assert len(id1) > 0
101
+ assert len(id2) > 0
102
+ assert id1.startswith("ks-")
103
+ assert id2.startswith("ks-")
104
+
105
+
106
+ class TestMinioKnowledgeServiceConfigRepositoryUpdates:
107
+ """Test knowledge service configuration update operations."""
108
+
109
+ @pytest.mark.asyncio
110
+ async def test_update_knowledge_service_config(
111
+ self,
112
+ knowledge_service_config_repo: MinioKnowledgeServiceConfigRepository,
113
+ sample_knowledge_service_config: KnowledgeServiceConfig,
114
+ ) -> None:
115
+ """Test updating a knowledge service configuration."""
116
+ # Save initial config
117
+ await knowledge_service_config_repo.save(sample_knowledge_service_config)
118
+
119
+ # Update the config
120
+ sample_knowledge_service_config.name = "Updated Test Service"
121
+ sample_knowledge_service_config.description = (
122
+ "Updated description for the test service"
123
+ )
124
+ await knowledge_service_config_repo.save(sample_knowledge_service_config)
125
+
126
+ # Verify update
127
+ retrieved = await knowledge_service_config_repo.get(
128
+ sample_knowledge_service_config.knowledge_service_id
129
+ )
130
+ assert retrieved is not None
131
+ assert retrieved.name == "Updated Test Service"
132
+ assert retrieved.description == "Updated description for the test service"
133
+ assert retrieved.service_api == sample_knowledge_service_config.service_api
134
+
135
+ @pytest.mark.asyncio
136
+ async def test_save_updates_timestamp(
137
+ self,
138
+ knowledge_service_config_repo: MinioKnowledgeServiceConfigRepository,
139
+ sample_knowledge_service_config: KnowledgeServiceConfig,
140
+ ) -> None:
141
+ """Test that save operations update the updated_at timestamp."""
142
+ original_updated_at = sample_knowledge_service_config.updated_at
143
+
144
+ # Save config
145
+ await knowledge_service_config_repo.save(sample_knowledge_service_config)
146
+
147
+ # Retrieve and check timestamp was updated
148
+ retrieved = await knowledge_service_config_repo.get(
149
+ sample_knowledge_service_config.knowledge_service_id
150
+ )
151
+ assert retrieved is not None
152
+ assert retrieved.updated_at is not None
153
+ assert original_updated_at is not None
154
+ assert retrieved.updated_at > original_updated_at
155
+
156
+ @pytest.mark.asyncio
157
+ async def test_save_sets_created_at_for_new_config(
158
+ self,
159
+ knowledge_service_config_repo: MinioKnowledgeServiceConfigRepository,
160
+ ) -> None:
161
+ """Test that save operations set created_at for new configurations."""
162
+ # Create config without created_at
163
+ config = KnowledgeServiceConfig(
164
+ knowledge_service_id="ks-new-test",
165
+ name="New Test Service",
166
+ description="A new test service",
167
+ service_api=ServiceApi.ANTHROPIC,
168
+ created_at=None, # Explicitly set to None
169
+ updated_at=None, # Explicitly set to None
170
+ )
171
+
172
+ # Save config
173
+ await knowledge_service_config_repo.save(config)
174
+
175
+ # Retrieve and check timestamps were set
176
+ retrieved = await knowledge_service_config_repo.get("ks-new-test")
177
+ assert retrieved is not None
178
+ assert retrieved.created_at is not None
179
+ assert retrieved.updated_at is not None
180
+
181
+
182
+ class TestMinioKnowledgeServiceConfigRepositoryIdempotency:
183
+ """Test idempotent operations on knowledge service configurations."""
184
+
185
+ @pytest.mark.asyncio
186
+ async def test_save_idempotency(
187
+ self,
188
+ knowledge_service_config_repo: MinioKnowledgeServiceConfigRepository,
189
+ sample_knowledge_service_config: KnowledgeServiceConfig,
190
+ ) -> None:
191
+ """Test that saving the same config multiple times is idempotent."""
192
+ # Save config first time
193
+ await knowledge_service_config_repo.save(sample_knowledge_service_config)
194
+
195
+ # Get the config to check initial state
196
+ retrieved1 = await knowledge_service_config_repo.get(
197
+ sample_knowledge_service_config.knowledge_service_id
198
+ )
199
+ assert retrieved1 is not None
200
+ first_updated_at = retrieved1.updated_at
201
+
202
+ # Save same config again (should update timestamp but maintain other
203
+ # data)
204
+ await knowledge_service_config_repo.save(sample_knowledge_service_config)
205
+
206
+ # Verify config is still there with updated timestamp
207
+ retrieved2 = await knowledge_service_config_repo.get(
208
+ sample_knowledge_service_config.knowledge_service_id
209
+ )
210
+ assert retrieved2 is not None
211
+ assert (
212
+ retrieved2.knowledge_service_id
213
+ == sample_knowledge_service_config.knowledge_service_id
214
+ )
215
+ assert retrieved2.name == sample_knowledge_service_config.name
216
+ assert retrieved2.description == sample_knowledge_service_config.description
217
+ assert retrieved2.service_api == sample_knowledge_service_config.service_api
218
+ assert retrieved2.updated_at is not None
219
+ assert first_updated_at is not None
220
+ assert retrieved2.updated_at > first_updated_at
221
+
222
+
223
+ class TestMinioKnowledgeServiceConfigRepositoryServiceApiTypes:
224
+ """Test handling of different service API types."""
225
+
226
+ @pytest.mark.asyncio
227
+ async def test_anthropic_service_api(
228
+ self,
229
+ knowledge_service_config_repo: MinioKnowledgeServiceConfigRepository,
230
+ ) -> None:
231
+ """Test saving and retrieving config with Anthropic service API."""
232
+ config = KnowledgeServiceConfig(
233
+ knowledge_service_id="ks-anthropic-test",
234
+ name="Anthropic Service",
235
+ description="Service using Anthropic API",
236
+ service_api=ServiceApi.ANTHROPIC,
237
+ )
238
+
239
+ await knowledge_service_config_repo.save(config)
240
+ retrieved = await knowledge_service_config_repo.get("ks-anthropic-test")
241
+
242
+ assert retrieved is not None
243
+ assert retrieved.service_api == ServiceApi.ANTHROPIC
244
+ assert retrieved.service_api.value == "anthropic"
245
+
246
+
247
+ class TestMinioKnowledgeServiceConfigRepositoryRoundtrip:
248
+ """Test full round-trip scenarios."""
249
+
250
+ @pytest.mark.asyncio
251
+ async def test_full_knowledge_service_config_lifecycle(
252
+ self,
253
+ knowledge_service_config_repo: MinioKnowledgeServiceConfigRepository,
254
+ ) -> None:
255
+ """Test complete knowledge service config lifecycle."""
256
+ # Generate new config ID
257
+ config_id = await knowledge_service_config_repo.generate_id()
258
+
259
+ # Create and save initial config
260
+ config = KnowledgeServiceConfig(
261
+ knowledge_service_id=config_id,
262
+ name="Lifecycle Test Service",
263
+ description="Testing full lifecycle",
264
+ service_api=ServiceApi.ANTHROPIC,
265
+ )
266
+ await knowledge_service_config_repo.save(config)
267
+
268
+ # Retrieve and verify initial state
269
+ retrieved = await knowledge_service_config_repo.get(config_id)
270
+ assert retrieved is not None
271
+ assert retrieved.name == "Lifecycle Test Service"
272
+ assert retrieved.service_api == ServiceApi.ANTHROPIC
273
+
274
+ # Update the configuration
275
+ config.name = "Updated Lifecycle Service"
276
+ config.description = "Updated description after lifecycle test"
277
+ await knowledge_service_config_repo.save(config)
278
+
279
+ # Final verification
280
+ final_config = await knowledge_service_config_repo.get(config_id)
281
+ assert final_config is not None
282
+ assert final_config.name == "Updated Lifecycle Service"
283
+ assert final_config.description == "Updated description after lifecycle test"
284
+ assert final_config.service_api == ServiceApi.ANTHROPIC
285
+ assert final_config.created_at is not None
286
+ assert final_config.updated_at is not None
287
+
288
+ @pytest.mark.asyncio
289
+ async def test_multiple_configs_independence(
290
+ self,
291
+ knowledge_service_config_repo: MinioKnowledgeServiceConfigRepository,
292
+ ) -> None:
293
+ """Test that multiple configs are stored and retrieved
294
+ independently."""
295
+ # Create multiple configs
296
+ config1 = KnowledgeServiceConfig(
297
+ knowledge_service_id="ks-test-1",
298
+ name="Service One",
299
+ description="First test service",
300
+ service_api=ServiceApi.ANTHROPIC,
301
+ )
302
+
303
+ config2 = KnowledgeServiceConfig(
304
+ knowledge_service_id="ks-test-2",
305
+ name="Service Two",
306
+ description="Second test service",
307
+ service_api=ServiceApi.ANTHROPIC,
308
+ )
309
+
310
+ # Save both configs
311
+ await knowledge_service_config_repo.save(config1)
312
+ await knowledge_service_config_repo.save(config2)
313
+
314
+ # Retrieve both and verify independence
315
+ retrieved1 = await knowledge_service_config_repo.get("ks-test-1")
316
+ retrieved2 = await knowledge_service_config_repo.get("ks-test-2")
317
+
318
+ assert retrieved1 is not None
319
+ assert retrieved2 is not None
320
+ assert retrieved1.knowledge_service_id == "ks-test-1"
321
+ assert retrieved2.knowledge_service_id == "ks-test-2"
322
+ assert retrieved1.name == "Service One"
323
+ assert retrieved2.name == "Service Two"
324
+ assert retrieved1.description == "First test service"
325
+ assert retrieved2.description == "Second test service"
326
+
327
+ # Update one config and verify the other is unchanged
328
+ config1.name = "Updated Service One"
329
+ await knowledge_service_config_repo.save(config1)
330
+
331
+ retrieved1_updated = await knowledge_service_config_repo.get("ks-test-1")
332
+ retrieved2_unchanged = await knowledge_service_config_repo.get("ks-test-2")
333
+
334
+ assert retrieved1_updated is not None
335
+ assert retrieved2_unchanged is not None
336
+ assert retrieved1_updated.name == "Updated Service One"
337
+ assert retrieved2_unchanged.name == "Service Two" # Should be unchanged
338
+
339
+
340
+ class TestMinioKnowledgeServiceConfigRepositoryEdgeCases:
341
+ """Test edge cases and error conditions."""
342
+
343
+ @pytest.mark.asyncio
344
+ async def test_empty_string_id_handling(
345
+ self,
346
+ knowledge_service_config_repo: MinioKnowledgeServiceConfigRepository,
347
+ ) -> None:
348
+ """Test handling of empty string knowledge service ID."""
349
+ result = await knowledge_service_config_repo.get("")
350
+ assert result is None
351
+
352
+ @pytest.mark.asyncio
353
+ async def test_special_characters_in_id(
354
+ self,
355
+ knowledge_service_config_repo: MinioKnowledgeServiceConfigRepository,
356
+ ) -> None:
357
+ """Test handling knowledge service IDs with special characters."""
358
+ # Note: This test ensures the repository can handle various ID formats
359
+ # that might be generated by different ID generation strategies
360
+ special_id = "ks-test-with-dashes-and-numbers-123"
361
+
362
+ config = KnowledgeServiceConfig(
363
+ knowledge_service_id=special_id,
364
+ name="Special ID Service",
365
+ description="Service with special characters in ID",
366
+ service_api=ServiceApi.ANTHROPIC,
367
+ )
368
+
369
+ await knowledge_service_config_repo.save(config)
370
+ retrieved = await knowledge_service_config_repo.get(special_id)
371
+
372
+ assert retrieved is not None
373
+ assert retrieved.knowledge_service_id == special_id
374
+ assert retrieved.name == "Special ID Service"