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,749 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Tests for the assembly specifications API router.
|
|
3
|
+
|
|
4
|
+
This module provides comprehensive tests for the assembly specifications
|
|
5
|
+
endpoints, focusing on testing the router behavior with proper dependency
|
|
6
|
+
injection and mocking patterns.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import pytest
|
|
10
|
+
from typing import Generator
|
|
11
|
+
from fastapi.testclient import TestClient
|
|
12
|
+
from fastapi import FastAPI
|
|
13
|
+
from fastapi_pagination import add_pagination
|
|
14
|
+
|
|
15
|
+
from julee.api.routers.assembly_specifications import router
|
|
16
|
+
from julee.api.dependencies import (
|
|
17
|
+
get_assembly_specification_repository,
|
|
18
|
+
)
|
|
19
|
+
from julee.domain.models import (
|
|
20
|
+
AssemblySpecification,
|
|
21
|
+
AssemblySpecificationStatus,
|
|
22
|
+
)
|
|
23
|
+
from julee.repositories.memory import (
|
|
24
|
+
MemoryAssemblySpecificationRepository,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@pytest.fixture
|
|
29
|
+
def memory_repo() -> MemoryAssemblySpecificationRepository:
|
|
30
|
+
"""Create a memory assembly specification repository for testing."""
|
|
31
|
+
return MemoryAssemblySpecificationRepository()
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@pytest.fixture
|
|
35
|
+
def app_with_router(
|
|
36
|
+
memory_repo: MemoryAssemblySpecificationRepository,
|
|
37
|
+
) -> FastAPI:
|
|
38
|
+
"""Create a FastAPI app with just the assembly specifications router."""
|
|
39
|
+
app = FastAPI()
|
|
40
|
+
|
|
41
|
+
# Override the dependency with our memory repository
|
|
42
|
+
app.dependency_overrides[get_assembly_specification_repository] = (
|
|
43
|
+
lambda: memory_repo
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
# Add pagination support (required for the paginate function)
|
|
47
|
+
add_pagination(app)
|
|
48
|
+
|
|
49
|
+
# Include the router with the prefix
|
|
50
|
+
app.include_router(
|
|
51
|
+
router,
|
|
52
|
+
prefix="/assembly_specifications",
|
|
53
|
+
tags=["Assembly Specifications"],
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
return app
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
@pytest.fixture
|
|
60
|
+
def client(
|
|
61
|
+
app_with_router: FastAPI,
|
|
62
|
+
) -> Generator[TestClient, None, None]:
|
|
63
|
+
"""Create a test client with the router app."""
|
|
64
|
+
with TestClient(app_with_router) as test_client:
|
|
65
|
+
yield test_client
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
@pytest.fixture
|
|
69
|
+
def sample_assembly_specification() -> AssemblySpecification:
|
|
70
|
+
"""Create a sample assembly specification for testing."""
|
|
71
|
+
return AssemblySpecification(
|
|
72
|
+
assembly_specification_id="test-spec-123",
|
|
73
|
+
name="Meeting Minutes",
|
|
74
|
+
applicability="Online video meeting transcripts",
|
|
75
|
+
jsonschema={
|
|
76
|
+
"type": "object",
|
|
77
|
+
"properties": {
|
|
78
|
+
"attendees": {"type": "array", "items": {"type": "string"}},
|
|
79
|
+
"summary": {"type": "string"},
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
knowledge_service_queries={
|
|
83
|
+
"/properties/attendees": "query-123",
|
|
84
|
+
"/properties/summary": "query-456",
|
|
85
|
+
},
|
|
86
|
+
status=AssemblySpecificationStatus.ACTIVE,
|
|
87
|
+
version="1.0.0",
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
class TestGetAssemblySpecifications:
|
|
92
|
+
"""Test the GET / endpoint for assembly specifications."""
|
|
93
|
+
|
|
94
|
+
def test_get_assembly_specifications_empty_list(
|
|
95
|
+
self,
|
|
96
|
+
client: TestClient,
|
|
97
|
+
memory_repo: MemoryAssemblySpecificationRepository,
|
|
98
|
+
) -> None:
|
|
99
|
+
"""Test getting specifications when repository is empty."""
|
|
100
|
+
response = client.get("/assembly_specifications/")
|
|
101
|
+
|
|
102
|
+
assert response.status_code == 200
|
|
103
|
+
data = response.json()
|
|
104
|
+
|
|
105
|
+
# Verify pagination structure
|
|
106
|
+
assert "items" in data
|
|
107
|
+
assert "total" in data
|
|
108
|
+
assert "page" in data
|
|
109
|
+
assert "size" in data
|
|
110
|
+
assert "pages" in data
|
|
111
|
+
|
|
112
|
+
# Should return empty list when repository is empty
|
|
113
|
+
assert data["items"] == []
|
|
114
|
+
assert data["total"] == 0
|
|
115
|
+
|
|
116
|
+
def test_get_assembly_specifications_with_pagination_params(
|
|
117
|
+
self,
|
|
118
|
+
client: TestClient,
|
|
119
|
+
memory_repo: MemoryAssemblySpecificationRepository,
|
|
120
|
+
) -> None:
|
|
121
|
+
"""Test getting specifications with pagination parameters."""
|
|
122
|
+
response = client.get("/assembly_specifications/?page=2&size=10")
|
|
123
|
+
|
|
124
|
+
assert response.status_code == 200
|
|
125
|
+
data = response.json()
|
|
126
|
+
|
|
127
|
+
# Verify pagination parameters are handled
|
|
128
|
+
assert "items" in data
|
|
129
|
+
assert "page" in data
|
|
130
|
+
assert "size" in data
|
|
131
|
+
|
|
132
|
+
# Even with pagination params, should work with empty repository
|
|
133
|
+
assert data["items"] == []
|
|
134
|
+
|
|
135
|
+
async def test_get_assembly_specifications_with_data(
|
|
136
|
+
self,
|
|
137
|
+
client: TestClient,
|
|
138
|
+
memory_repo: MemoryAssemblySpecificationRepository,
|
|
139
|
+
sample_assembly_specification: AssemblySpecification,
|
|
140
|
+
) -> None:
|
|
141
|
+
"""Test getting specifications when repository contains data."""
|
|
142
|
+
# Create a second specification for testing
|
|
143
|
+
spec2 = AssemblySpecification(
|
|
144
|
+
assembly_specification_id="test-spec-456",
|
|
145
|
+
name="Project Report",
|
|
146
|
+
applicability="Project documentation and status updates",
|
|
147
|
+
jsonschema={
|
|
148
|
+
"type": "object",
|
|
149
|
+
"properties": {
|
|
150
|
+
"project_name": {"type": "string"},
|
|
151
|
+
"status": {"type": "string"},
|
|
152
|
+
},
|
|
153
|
+
},
|
|
154
|
+
knowledge_service_queries={
|
|
155
|
+
"/properties/project_name": "query-789",
|
|
156
|
+
"/properties/status": "query-101",
|
|
157
|
+
},
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
# Save specifications to the repository
|
|
161
|
+
await memory_repo.save(sample_assembly_specification)
|
|
162
|
+
await memory_repo.save(spec2)
|
|
163
|
+
|
|
164
|
+
response = client.get("/assembly_specifications/")
|
|
165
|
+
|
|
166
|
+
assert response.status_code == 200
|
|
167
|
+
data = response.json()
|
|
168
|
+
|
|
169
|
+
# Verify pagination structure
|
|
170
|
+
assert "items" in data
|
|
171
|
+
assert "total" in data
|
|
172
|
+
assert "page" in data
|
|
173
|
+
assert "size" in data
|
|
174
|
+
|
|
175
|
+
# Should return both specifications
|
|
176
|
+
assert data["total"] == 2
|
|
177
|
+
assert len(data["items"]) == 2
|
|
178
|
+
|
|
179
|
+
# Verify the specifications are returned (order may vary)
|
|
180
|
+
returned_ids = {item["assembly_specification_id"] for item in data["items"]}
|
|
181
|
+
expected_ids = {
|
|
182
|
+
sample_assembly_specification.assembly_specification_id,
|
|
183
|
+
spec2.assembly_specification_id,
|
|
184
|
+
}
|
|
185
|
+
assert returned_ids == expected_ids
|
|
186
|
+
|
|
187
|
+
# Verify specification data structure
|
|
188
|
+
for item in data["items"]:
|
|
189
|
+
assert "assembly_specification_id" in item
|
|
190
|
+
assert "name" in item
|
|
191
|
+
assert "applicability" in item
|
|
192
|
+
assert "jsonschema" in item
|
|
193
|
+
assert "status" in item
|
|
194
|
+
|
|
195
|
+
async def test_get_assembly_specifications_pagination(
|
|
196
|
+
self,
|
|
197
|
+
client: TestClient,
|
|
198
|
+
memory_repo: MemoryAssemblySpecificationRepository,
|
|
199
|
+
) -> None:
|
|
200
|
+
"""Test pagination with multiple specifications."""
|
|
201
|
+
# Create several specifications
|
|
202
|
+
specifications = []
|
|
203
|
+
for i in range(5):
|
|
204
|
+
spec = AssemblySpecification(
|
|
205
|
+
assembly_specification_id=f"spec-{i:03d}",
|
|
206
|
+
name=f"Specification {i}",
|
|
207
|
+
applicability=f"Test applicability {i}",
|
|
208
|
+
jsonschema={"type": "object", "properties": {}},
|
|
209
|
+
)
|
|
210
|
+
specifications.append(spec)
|
|
211
|
+
await memory_repo.save(spec)
|
|
212
|
+
|
|
213
|
+
# Test first page with size 2
|
|
214
|
+
response = client.get("/assembly_specifications/?page=1&size=2")
|
|
215
|
+
assert response.status_code == 200
|
|
216
|
+
data = response.json()
|
|
217
|
+
|
|
218
|
+
assert data["total"] == 5
|
|
219
|
+
assert data["page"] == 1
|
|
220
|
+
assert data["size"] == 2
|
|
221
|
+
assert len(data["items"]) == 2
|
|
222
|
+
|
|
223
|
+
# Test second page
|
|
224
|
+
response = client.get("/assembly_specifications/?page=2&size=2")
|
|
225
|
+
assert response.status_code == 200
|
|
226
|
+
data = response.json()
|
|
227
|
+
|
|
228
|
+
assert data["total"] == 5
|
|
229
|
+
assert data["page"] == 2
|
|
230
|
+
assert data["size"] == 2
|
|
231
|
+
assert len(data["items"]) == 2
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
class TestGetAssemblySpecification:
|
|
235
|
+
"""Test the GET /{id} endpoint for getting a specific specification."""
|
|
236
|
+
|
|
237
|
+
async def test_get_assembly_specification_success(
|
|
238
|
+
self,
|
|
239
|
+
client: TestClient,
|
|
240
|
+
memory_repo: MemoryAssemblySpecificationRepository,
|
|
241
|
+
sample_assembly_specification: AssemblySpecification,
|
|
242
|
+
) -> None:
|
|
243
|
+
"""Test successfully getting a specific assembly specification."""
|
|
244
|
+
# Save the specification to the repository
|
|
245
|
+
await memory_repo.save(sample_assembly_specification)
|
|
246
|
+
|
|
247
|
+
response = client.get(
|
|
248
|
+
f"/assembly_specifications/{sample_assembly_specification.assembly_specification_id}"
|
|
249
|
+
)
|
|
250
|
+
|
|
251
|
+
assert response.status_code == 200
|
|
252
|
+
data = response.json()
|
|
253
|
+
|
|
254
|
+
# Verify response structure and content
|
|
255
|
+
assert (
|
|
256
|
+
data["assembly_specification_id"]
|
|
257
|
+
== sample_assembly_specification.assembly_specification_id
|
|
258
|
+
)
|
|
259
|
+
assert data["name"] == sample_assembly_specification.name
|
|
260
|
+
assert data["applicability"] == sample_assembly_specification.applicability
|
|
261
|
+
assert data["jsonschema"] == sample_assembly_specification.jsonschema
|
|
262
|
+
assert (
|
|
263
|
+
data["knowledge_service_queries"]
|
|
264
|
+
== sample_assembly_specification.knowledge_service_queries
|
|
265
|
+
)
|
|
266
|
+
assert data["status"] == sample_assembly_specification.status.value
|
|
267
|
+
assert data["version"] == sample_assembly_specification.version
|
|
268
|
+
assert "created_at" in data
|
|
269
|
+
assert "updated_at" in data
|
|
270
|
+
|
|
271
|
+
def test_get_assembly_specification_not_found(
|
|
272
|
+
self,
|
|
273
|
+
client: TestClient,
|
|
274
|
+
memory_repo: MemoryAssemblySpecificationRepository,
|
|
275
|
+
) -> None:
|
|
276
|
+
"""Test getting a non-existent assembly specification."""
|
|
277
|
+
nonexistent_id = "nonexistent-spec-123"
|
|
278
|
+
response = client.get(f"/assembly_specifications/{nonexistent_id}")
|
|
279
|
+
|
|
280
|
+
assert response.status_code == 404
|
|
281
|
+
data = response.json()
|
|
282
|
+
assert "not found" in data["detail"].lower()
|
|
283
|
+
assert nonexistent_id in data["detail"]
|
|
284
|
+
|
|
285
|
+
async def test_get_assembly_specification_with_complex_schema(
|
|
286
|
+
self,
|
|
287
|
+
client: TestClient,
|
|
288
|
+
memory_repo: MemoryAssemblySpecificationRepository,
|
|
289
|
+
) -> None:
|
|
290
|
+
"""Test getting specification with complex JSON schema."""
|
|
291
|
+
complex_spec = AssemblySpecification(
|
|
292
|
+
assembly_specification_id="complex-spec-123",
|
|
293
|
+
name="Complex Meeting Minutes",
|
|
294
|
+
applicability="Detailed meeting transcripts with metadata",
|
|
295
|
+
jsonschema={
|
|
296
|
+
"type": "object",
|
|
297
|
+
"properties": {
|
|
298
|
+
"metadata": {
|
|
299
|
+
"type": "object",
|
|
300
|
+
"properties": {
|
|
301
|
+
"date": {"type": "string", "format": "date"},
|
|
302
|
+
"duration": {"type": "integer"},
|
|
303
|
+
},
|
|
304
|
+
},
|
|
305
|
+
"attendees": {
|
|
306
|
+
"type": "array",
|
|
307
|
+
"items": {
|
|
308
|
+
"type": "object",
|
|
309
|
+
"properties": {
|
|
310
|
+
"name": {"type": "string"},
|
|
311
|
+
"role": {"type": "string"},
|
|
312
|
+
},
|
|
313
|
+
},
|
|
314
|
+
},
|
|
315
|
+
"agenda": {
|
|
316
|
+
"type": "array",
|
|
317
|
+
"items": {"type": "string"},
|
|
318
|
+
},
|
|
319
|
+
},
|
|
320
|
+
"required": ["metadata", "attendees"],
|
|
321
|
+
},
|
|
322
|
+
knowledge_service_queries={
|
|
323
|
+
"/properties/metadata/properties/date": "date-query",
|
|
324
|
+
"/properties/attendees": "attendees-query",
|
|
325
|
+
"/properties/agenda": "agenda-query",
|
|
326
|
+
},
|
|
327
|
+
)
|
|
328
|
+
|
|
329
|
+
await memory_repo.save(complex_spec)
|
|
330
|
+
|
|
331
|
+
response = client.get(
|
|
332
|
+
f"/assembly_specifications/{complex_spec.assembly_specification_id}"
|
|
333
|
+
)
|
|
334
|
+
|
|
335
|
+
assert response.status_code == 200
|
|
336
|
+
data = response.json()
|
|
337
|
+
|
|
338
|
+
# Verify complex schema is preserved
|
|
339
|
+
assert data["jsonschema"]["properties"]["metadata"]["properties"]
|
|
340
|
+
assert data["jsonschema"]["required"] == ["metadata", "attendees"]
|
|
341
|
+
assert len(data["knowledge_service_queries"]) == 3
|
|
342
|
+
|
|
343
|
+
def test_get_assembly_specification_invalid_id_format(
|
|
344
|
+
self,
|
|
345
|
+
client: TestClient,
|
|
346
|
+
memory_repo: MemoryAssemblySpecificationRepository,
|
|
347
|
+
) -> None:
|
|
348
|
+
"""Test getting specification with various ID formats."""
|
|
349
|
+
# Test with empty string (should be handled by FastAPI routing)
|
|
350
|
+
response = client.get("/assembly_specifications/")
|
|
351
|
+
assert response.status_code == 200 # This hits the list endpoint
|
|
352
|
+
|
|
353
|
+
# Test with special characters
|
|
354
|
+
response = client.get("/assembly_specifications/test@spec#123")
|
|
355
|
+
assert response.status_code == 404 # Not found is expected
|
|
356
|
+
|
|
357
|
+
async def test_get_assembly_specification_different_statuses(
|
|
358
|
+
self,
|
|
359
|
+
client: TestClient,
|
|
360
|
+
memory_repo: MemoryAssemblySpecificationRepository,
|
|
361
|
+
) -> None:
|
|
362
|
+
"""Test getting specifications with different status values."""
|
|
363
|
+
for status in AssemblySpecificationStatus:
|
|
364
|
+
spec = AssemblySpecification(
|
|
365
|
+
assembly_specification_id=f"spec-{status.value}",
|
|
366
|
+
name=f"Spec {status.value}",
|
|
367
|
+
applicability="Test applicability",
|
|
368
|
+
jsonschema={"type": "object", "properties": {}},
|
|
369
|
+
status=status,
|
|
370
|
+
)
|
|
371
|
+
await memory_repo.save(spec)
|
|
372
|
+
|
|
373
|
+
response = client.get(
|
|
374
|
+
f"/assembly_specifications/{spec.assembly_specification_id}"
|
|
375
|
+
)
|
|
376
|
+
assert response.status_code == 200
|
|
377
|
+
data = response.json()
|
|
378
|
+
assert data["status"] == status.value
|
|
379
|
+
|
|
380
|
+
|
|
381
|
+
class TestCreateAssemblySpecification:
|
|
382
|
+
"""Test the POST / endpoint for creating assembly specifications."""
|
|
383
|
+
|
|
384
|
+
def test_create_assembly_specification_success(
|
|
385
|
+
self,
|
|
386
|
+
client: TestClient,
|
|
387
|
+
memory_repo: MemoryAssemblySpecificationRepository,
|
|
388
|
+
) -> None:
|
|
389
|
+
"""Test successful creation of an assembly specification."""
|
|
390
|
+
request_data = {
|
|
391
|
+
"name": "Meeting Minutes Template",
|
|
392
|
+
"applicability": "Online video meeting transcripts",
|
|
393
|
+
"jsonschema": {
|
|
394
|
+
"type": "object",
|
|
395
|
+
"properties": {
|
|
396
|
+
"attendees": {
|
|
397
|
+
"type": "array",
|
|
398
|
+
"items": {"type": "string"},
|
|
399
|
+
},
|
|
400
|
+
"summary": {"type": "string"},
|
|
401
|
+
"action_items": {
|
|
402
|
+
"type": "array",
|
|
403
|
+
"items": {"type": "string"},
|
|
404
|
+
},
|
|
405
|
+
},
|
|
406
|
+
},
|
|
407
|
+
"knowledge_service_queries": {
|
|
408
|
+
"/properties/attendees": "attendee-extractor-query",
|
|
409
|
+
"/properties/summary": "summary-extractor-query",
|
|
410
|
+
},
|
|
411
|
+
"version": "1.0.0",
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
response = client.post("/assembly_specifications/", json=request_data)
|
|
415
|
+
|
|
416
|
+
assert response.status_code == 200
|
|
417
|
+
data = response.json()
|
|
418
|
+
|
|
419
|
+
# Verify response structure
|
|
420
|
+
assert "assembly_specification_id" in data
|
|
421
|
+
assert data["name"] == request_data["name"]
|
|
422
|
+
assert data["applicability"] == request_data["applicability"]
|
|
423
|
+
assert data["jsonschema"] == request_data["jsonschema"]
|
|
424
|
+
assert (
|
|
425
|
+
data["knowledge_service_queries"]
|
|
426
|
+
== request_data["knowledge_service_queries"]
|
|
427
|
+
)
|
|
428
|
+
assert data["version"] == request_data["version"]
|
|
429
|
+
assert data["status"] == "draft" # Default status
|
|
430
|
+
assert "created_at" in data
|
|
431
|
+
assert "updated_at" in data
|
|
432
|
+
|
|
433
|
+
# Verify the specification was saved to repository
|
|
434
|
+
spec_id = data["assembly_specification_id"]
|
|
435
|
+
assert spec_id is not None
|
|
436
|
+
assert spec_id != ""
|
|
437
|
+
|
|
438
|
+
async def test_create_assembly_specification_persisted(
|
|
439
|
+
self,
|
|
440
|
+
client: TestClient,
|
|
441
|
+
memory_repo: MemoryAssemblySpecificationRepository,
|
|
442
|
+
) -> None:
|
|
443
|
+
"""Test that created specification is persisted in repository."""
|
|
444
|
+
request_data = {
|
|
445
|
+
"name": "Project Status Report",
|
|
446
|
+
"applicability": "Weekly project status documents",
|
|
447
|
+
"jsonschema": {
|
|
448
|
+
"type": "object",
|
|
449
|
+
"properties": {
|
|
450
|
+
"project_name": {"type": "string"},
|
|
451
|
+
"status": {"type": "string"},
|
|
452
|
+
"milestones": {
|
|
453
|
+
"type": "array",
|
|
454
|
+
"items": {"type": "string"},
|
|
455
|
+
},
|
|
456
|
+
},
|
|
457
|
+
},
|
|
458
|
+
"knowledge_service_queries": {
|
|
459
|
+
"/properties/project_name": "project-name-query",
|
|
460
|
+
"/properties/status": "status-query",
|
|
461
|
+
},
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
response = client.post("/assembly_specifications/", json=request_data)
|
|
465
|
+
assert response.status_code == 200
|
|
466
|
+
|
|
467
|
+
spec_id = response.json()["assembly_specification_id"]
|
|
468
|
+
|
|
469
|
+
# Verify specification was saved by retrieving it
|
|
470
|
+
saved_spec = await memory_repo.get(spec_id)
|
|
471
|
+
assert saved_spec is not None
|
|
472
|
+
assert saved_spec.name == request_data["name"]
|
|
473
|
+
assert saved_spec.applicability == request_data["applicability"]
|
|
474
|
+
assert saved_spec.jsonschema == request_data["jsonschema"]
|
|
475
|
+
assert (
|
|
476
|
+
saved_spec.knowledge_service_queries
|
|
477
|
+
== request_data["knowledge_service_queries"]
|
|
478
|
+
)
|
|
479
|
+
|
|
480
|
+
def test_create_assembly_specification_minimal_fields(
|
|
481
|
+
self,
|
|
482
|
+
client: TestClient,
|
|
483
|
+
memory_repo: MemoryAssemblySpecificationRepository,
|
|
484
|
+
) -> None:
|
|
485
|
+
"""Test creation with only required fields."""
|
|
486
|
+
request_data = {
|
|
487
|
+
"name": "Minimal Spec",
|
|
488
|
+
"applicability": "Test applicability",
|
|
489
|
+
"jsonschema": {"type": "object", "properties": {}},
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
response = client.post("/assembly_specifications/", json=request_data)
|
|
493
|
+
|
|
494
|
+
assert response.status_code == 200
|
|
495
|
+
data = response.json()
|
|
496
|
+
|
|
497
|
+
assert data["name"] == request_data["name"]
|
|
498
|
+
assert data["applicability"] == request_data["applicability"]
|
|
499
|
+
assert data["jsonschema"] == request_data["jsonschema"]
|
|
500
|
+
assert data["knowledge_service_queries"] == {}
|
|
501
|
+
assert data["version"] == "0.1.0" # Default version
|
|
502
|
+
assert data["status"] == "draft" # Default status
|
|
503
|
+
|
|
504
|
+
def test_create_assembly_specification_validation_errors(
|
|
505
|
+
self,
|
|
506
|
+
client: TestClient,
|
|
507
|
+
memory_repo: MemoryAssemblySpecificationRepository,
|
|
508
|
+
) -> None:
|
|
509
|
+
"""Test validation error handling."""
|
|
510
|
+
# Test empty name
|
|
511
|
+
request_data = {
|
|
512
|
+
"name": "",
|
|
513
|
+
"applicability": "Test applicability",
|
|
514
|
+
"jsonschema": {"type": "object", "properties": {}},
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
response = client.post("/assembly_specifications/", json=request_data)
|
|
518
|
+
assert response.status_code == 422
|
|
519
|
+
|
|
520
|
+
# Test empty applicability
|
|
521
|
+
request_data = {
|
|
522
|
+
"name": "Test Spec",
|
|
523
|
+
"applicability": "",
|
|
524
|
+
"jsonschema": {"type": "object", "properties": {}},
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
response = client.post("/assembly_specifications/", json=request_data)
|
|
528
|
+
assert response.status_code == 422
|
|
529
|
+
|
|
530
|
+
# Test invalid JSON schema
|
|
531
|
+
request_data = {
|
|
532
|
+
"name": "Test Spec",
|
|
533
|
+
"applicability": "Test applicability",
|
|
534
|
+
"jsonschema": {
|
|
535
|
+
"invalid": "schema"
|
|
536
|
+
}, # Invalid JSON schema: contains an unrecognized keyword
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
response = client.post("/assembly_specifications/", json=request_data)
|
|
540
|
+
assert response.status_code == 422
|
|
541
|
+
|
|
542
|
+
def test_create_assembly_specification_missing_required_fields(
|
|
543
|
+
self,
|
|
544
|
+
client: TestClient,
|
|
545
|
+
memory_repo: MemoryAssemblySpecificationRepository,
|
|
546
|
+
) -> None:
|
|
547
|
+
"""Test handling of missing required fields."""
|
|
548
|
+
# Missing name
|
|
549
|
+
request_data = {
|
|
550
|
+
"applicability": "Test applicability",
|
|
551
|
+
"jsonschema": {"type": "object", "properties": {}},
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
response = client.post("/assembly_specifications/", json=request_data)
|
|
555
|
+
assert response.status_code == 422
|
|
556
|
+
|
|
557
|
+
# Missing applicability
|
|
558
|
+
request_data = {
|
|
559
|
+
"name": "Test Spec",
|
|
560
|
+
"jsonschema": {"type": "object", "properties": {}},
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
response = client.post("/assembly_specifications/", json=request_data)
|
|
564
|
+
assert response.status_code == 422
|
|
565
|
+
|
|
566
|
+
# Missing jsonschema
|
|
567
|
+
request_data = {
|
|
568
|
+
"name": "Test Spec",
|
|
569
|
+
"applicability": "Test applicability",
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
response = client.post("/assembly_specifications/", json=request_data)
|
|
573
|
+
assert response.status_code == 422
|
|
574
|
+
|
|
575
|
+
def test_create_assembly_specification_invalid_json_pointers(
|
|
576
|
+
self,
|
|
577
|
+
client: TestClient,
|
|
578
|
+
memory_repo: MemoryAssemblySpecificationRepository,
|
|
579
|
+
) -> None:
|
|
580
|
+
"""Test validation of JSON pointer paths in knowledge queries."""
|
|
581
|
+
# Invalid JSON pointer (doesn't exist in schema)
|
|
582
|
+
request_data = {
|
|
583
|
+
"name": "Test Spec",
|
|
584
|
+
"applicability": "Test applicability",
|
|
585
|
+
"jsonschema": {
|
|
586
|
+
"type": "object",
|
|
587
|
+
"properties": {"summary": {"type": "string"}},
|
|
588
|
+
},
|
|
589
|
+
"knowledge_service_queries": {
|
|
590
|
+
"/properties/nonexistent": "some-query-id", # Path not found
|
|
591
|
+
},
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
response = client.post("/assembly_specifications/", json=request_data)
|
|
595
|
+
assert response.status_code == 422
|
|
596
|
+
|
|
597
|
+
# Invalid JSON pointer format
|
|
598
|
+
request_data = {
|
|
599
|
+
"name": "Test Spec",
|
|
600
|
+
"applicability": "Test applicability",
|
|
601
|
+
"jsonschema": {
|
|
602
|
+
"type": "object",
|
|
603
|
+
"properties": {"summary": {"type": "string"}},
|
|
604
|
+
},
|
|
605
|
+
"knowledge_service_queries": {
|
|
606
|
+
"invalid-pointer": "some-query-id", # Invalid format
|
|
607
|
+
},
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
response = client.post("/assembly_specifications/", json=request_data)
|
|
611
|
+
assert response.status_code == 422
|
|
612
|
+
|
|
613
|
+
def test_create_assembly_specification_complex_schema(
|
|
614
|
+
self,
|
|
615
|
+
client: TestClient,
|
|
616
|
+
memory_repo: MemoryAssemblySpecificationRepository,
|
|
617
|
+
) -> None:
|
|
618
|
+
"""Test creation with complex JSON schema and query mappings."""
|
|
619
|
+
request_data = {
|
|
620
|
+
"name": "Complex Meeting Minutes",
|
|
621
|
+
"applicability": "Detailed enterprise meeting transcripts",
|
|
622
|
+
"jsonschema": {
|
|
623
|
+
"type": "object",
|
|
624
|
+
"properties": {
|
|
625
|
+
"metadata": {
|
|
626
|
+
"type": "object",
|
|
627
|
+
"properties": {
|
|
628
|
+
"meeting_date": {
|
|
629
|
+
"type": "string",
|
|
630
|
+
"format": "date",
|
|
631
|
+
},
|
|
632
|
+
"duration_minutes": {"type": "integer"},
|
|
633
|
+
"location": {"type": "string"},
|
|
634
|
+
},
|
|
635
|
+
"required": ["meeting_date"],
|
|
636
|
+
},
|
|
637
|
+
"attendees": {
|
|
638
|
+
"type": "array",
|
|
639
|
+
"items": {
|
|
640
|
+
"type": "object",
|
|
641
|
+
"properties": {
|
|
642
|
+
"name": {"type": "string"},
|
|
643
|
+
"role": {"type": "string"},
|
|
644
|
+
"department": {"type": "string"},
|
|
645
|
+
},
|
|
646
|
+
"required": ["name"],
|
|
647
|
+
},
|
|
648
|
+
},
|
|
649
|
+
"agenda_items": {
|
|
650
|
+
"type": "array",
|
|
651
|
+
"items": {"type": "string"},
|
|
652
|
+
},
|
|
653
|
+
"decisions": {
|
|
654
|
+
"type": "array",
|
|
655
|
+
"items": {
|
|
656
|
+
"type": "object",
|
|
657
|
+
"properties": {
|
|
658
|
+
"description": {"type": "string"},
|
|
659
|
+
"owner": {"type": "string"},
|
|
660
|
+
"due_date": {
|
|
661
|
+
"type": "string",
|
|
662
|
+
"format": "date",
|
|
663
|
+
},
|
|
664
|
+
},
|
|
665
|
+
},
|
|
666
|
+
},
|
|
667
|
+
},
|
|
668
|
+
"required": ["metadata", "attendees"],
|
|
669
|
+
},
|
|
670
|
+
"knowledge_service_queries": {
|
|
671
|
+
"/properties/metadata/properties/meeting_date": ("date-extractor"),
|
|
672
|
+
"/properties/metadata/properties/location": ("location-extractor"),
|
|
673
|
+
"/properties/attendees": "attendee-extractor",
|
|
674
|
+
"/properties/agenda_items": "agenda-extractor",
|
|
675
|
+
"/properties/decisions": "decision-extractor",
|
|
676
|
+
},
|
|
677
|
+
"version": "2.1.0",
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
response = client.post("/assembly_specifications/", json=request_data)
|
|
681
|
+
|
|
682
|
+
assert response.status_code == 200
|
|
683
|
+
data = response.json()
|
|
684
|
+
|
|
685
|
+
# Verify complex schema is preserved
|
|
686
|
+
assert data["jsonschema"]["properties"]["metadata"]["properties"]
|
|
687
|
+
assert data["jsonschema"]["required"] == ["metadata", "attendees"]
|
|
688
|
+
assert len(data["knowledge_service_queries"]) == 5
|
|
689
|
+
assert data["version"] == "2.1.0"
|
|
690
|
+
|
|
691
|
+
def test_post_and_get_integration(
|
|
692
|
+
self,
|
|
693
|
+
client: TestClient,
|
|
694
|
+
memory_repo: MemoryAssemblySpecificationRepository,
|
|
695
|
+
) -> None:
|
|
696
|
+
"""Test that POST and GET endpoints work together."""
|
|
697
|
+
# Create a specification via POST
|
|
698
|
+
request_data = {
|
|
699
|
+
"name": "Integration Test Specification",
|
|
700
|
+
"applicability": "Test integration between endpoints",
|
|
701
|
+
"jsonschema": {
|
|
702
|
+
"type": "object",
|
|
703
|
+
"properties": {
|
|
704
|
+
"title": {"type": "string"},
|
|
705
|
+
"content": {"type": "string"},
|
|
706
|
+
},
|
|
707
|
+
},
|
|
708
|
+
"knowledge_service_queries": {
|
|
709
|
+
"/properties/title": "title-query",
|
|
710
|
+
"/properties/content": "content-query",
|
|
711
|
+
},
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
post_response = client.post("/assembly_specifications/", json=request_data)
|
|
715
|
+
assert post_response.status_code == 200
|
|
716
|
+
created_spec = post_response.json()
|
|
717
|
+
|
|
718
|
+
# Verify the specification appears in GET list response
|
|
719
|
+
list_response = client.get("/assembly_specifications/")
|
|
720
|
+
assert list_response.status_code == 200
|
|
721
|
+
list_data = list_response.json()
|
|
722
|
+
|
|
723
|
+
# Should find our created specification in the list
|
|
724
|
+
assert list_data["total"] == 1
|
|
725
|
+
assert len(list_data["items"]) == 1
|
|
726
|
+
|
|
727
|
+
returned_spec = list_data["items"][0]
|
|
728
|
+
assert (
|
|
729
|
+
returned_spec["assembly_specification_id"]
|
|
730
|
+
== created_spec["assembly_specification_id"]
|
|
731
|
+
)
|
|
732
|
+
assert returned_spec["name"] == request_data["name"]
|
|
733
|
+
|
|
734
|
+
# Verify the specification can be retrieved by ID
|
|
735
|
+
spec_id = created_spec["assembly_specification_id"]
|
|
736
|
+
get_response = client.get(f"/assembly_specifications/{spec_id}")
|
|
737
|
+
assert get_response.status_code == 200
|
|
738
|
+
retrieved_spec = get_response.json()
|
|
739
|
+
|
|
740
|
+
assert (
|
|
741
|
+
retrieved_spec["assembly_specification_id"]
|
|
742
|
+
== created_spec["assembly_specification_id"]
|
|
743
|
+
)
|
|
744
|
+
assert retrieved_spec["name"] == request_data["name"]
|
|
745
|
+
assert retrieved_spec["applicability"] == request_data["applicability"]
|
|
746
|
+
assert (
|
|
747
|
+
retrieved_spec["knowledge_service_queries"]
|
|
748
|
+
== request_data["knowledge_service_queries"]
|
|
749
|
+
)
|