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,738 @@
1
+ """
2
+ Tests for the knowledge service queries API router.
3
+
4
+ This module provides comprehensive tests for the knowledge service queries
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.knowledge_service_queries import router
16
+ from julee.api.dependencies import (
17
+ get_knowledge_service_query_repository,
18
+ )
19
+ from julee.domain.models import KnowledgeServiceQuery
20
+ from julee.repositories.memory import (
21
+ MemoryKnowledgeServiceQueryRepository,
22
+ )
23
+
24
+
25
+ @pytest.fixture
26
+ def memory_repo() -> MemoryKnowledgeServiceQueryRepository:
27
+ """Create a memory knowledge service query repository for testing."""
28
+ return MemoryKnowledgeServiceQueryRepository()
29
+
30
+
31
+ @pytest.fixture
32
+ def app_with_router(
33
+ memory_repo: MemoryKnowledgeServiceQueryRepository,
34
+ ) -> FastAPI:
35
+ """Create a FastAPI app with just the knowledge service queries router."""
36
+ app = FastAPI()
37
+
38
+ # Override the dependency with our memory repository
39
+ app.dependency_overrides[get_knowledge_service_query_repository] = (
40
+ lambda: memory_repo
41
+ )
42
+
43
+ # Add pagination support (required for the paginate function)
44
+ add_pagination(app)
45
+
46
+ # Include the router with the prefix
47
+ app.include_router(
48
+ router,
49
+ prefix="/knowledge_service_queries",
50
+ tags=["Knowledge Service Queries"],
51
+ )
52
+
53
+ return app
54
+
55
+
56
+ @pytest.fixture
57
+ def client(
58
+ app_with_router: FastAPI,
59
+ ) -> Generator[TestClient, None, None]:
60
+ """Create a test client with the router app."""
61
+ with TestClient(app_with_router) as test_client:
62
+ yield test_client
63
+
64
+
65
+ @pytest.fixture
66
+ def sample_knowledge_service_query() -> KnowledgeServiceQuery:
67
+ """Create a sample knowledge service query for testing."""
68
+ return KnowledgeServiceQuery(
69
+ query_id="test-query-123",
70
+ name="Extract Meeting Summary",
71
+ knowledge_service_id="anthropic-claude",
72
+ prompt="Extract the main summary from this meeting transcript",
73
+ query_metadata={"model": "claude-3", "temperature": 0.2},
74
+ assistant_prompt="Please format as JSON",
75
+ )
76
+
77
+
78
+ class TestGetKnowledgeServiceQueries:
79
+ """Test the GET / endpoint for knowledge service queries."""
80
+
81
+ def test_get_knowledge_service_queries_empty_list(
82
+ self,
83
+ client: TestClient,
84
+ memory_repo: MemoryKnowledgeServiceQueryRepository,
85
+ ) -> None:
86
+ """Test getting queries when repository is empty."""
87
+ response = client.get("/knowledge_service_queries/")
88
+
89
+ assert response.status_code == 200
90
+ data = response.json()
91
+
92
+ # Verify pagination structure
93
+ assert "items" in data
94
+ assert "total" in data
95
+ assert "page" in data
96
+ assert "size" in data
97
+ assert "pages" in data
98
+
99
+ # Should return empty list when repository is empty
100
+ assert data["items"] == []
101
+ assert data["total"] == 0
102
+
103
+ def test_get_knowledge_service_queries_with_pagination_params(
104
+ self,
105
+ client: TestClient,
106
+ memory_repo: MemoryKnowledgeServiceQueryRepository,
107
+ ) -> None:
108
+ """Test getting queries with pagination parameters."""
109
+ response = client.get("/knowledge_service_queries/?page=2&size=10")
110
+
111
+ assert response.status_code == 200
112
+ data = response.json()
113
+
114
+ # Verify pagination parameters are handled
115
+ assert "items" in data
116
+ assert "page" in data
117
+ assert "size" in data
118
+
119
+ # Even with pagination params, should work with empty repository
120
+ assert data["items"] == []
121
+
122
+ async def test_get_knowledge_service_queries_with_data(
123
+ self,
124
+ client: TestClient,
125
+ memory_repo: MemoryKnowledgeServiceQueryRepository,
126
+ sample_knowledge_service_query: KnowledgeServiceQuery,
127
+ ) -> None:
128
+ """Test getting queries when repository contains data."""
129
+ # Create a second query for testing
130
+ query2 = KnowledgeServiceQuery(
131
+ query_id="test-query-456",
132
+ name="Extract Attendees",
133
+ knowledge_service_id="openai-service",
134
+ prompt="Extract all attendees from this meeting",
135
+ query_metadata={"model": "gpt-4", "temperature": 0.1},
136
+ assistant_prompt="Format as JSON array",
137
+ )
138
+
139
+ # Save queries to the repository
140
+ await memory_repo.save(sample_knowledge_service_query)
141
+ await memory_repo.save(query2)
142
+
143
+ response = client.get("/knowledge_service_queries/")
144
+
145
+ assert response.status_code == 200
146
+ data = response.json()
147
+
148
+ # Verify pagination structure
149
+ assert "items" in data
150
+ assert "total" in data
151
+ assert "page" in data
152
+ assert "size" in data
153
+
154
+ # Should return both queries
155
+ assert data["total"] == 2
156
+ assert len(data["items"]) == 2
157
+
158
+ # Verify the queries are returned (order may vary)
159
+ returned_ids = {item["query_id"] for item in data["items"]}
160
+ expected_ids = {
161
+ sample_knowledge_service_query.query_id,
162
+ query2.query_id,
163
+ }
164
+ assert returned_ids == expected_ids
165
+
166
+ # Verify query data structure
167
+ for item in data["items"]:
168
+ assert "query_id" in item
169
+ assert "name" in item
170
+ assert "knowledge_service_id" in item
171
+ assert "prompt" in item
172
+
173
+ async def test_get_knowledge_service_queries_pagination(
174
+ self,
175
+ client: TestClient,
176
+ memory_repo: MemoryKnowledgeServiceQueryRepository,
177
+ ) -> None:
178
+ """Test pagination with multiple queries."""
179
+ # Create several queries
180
+ queries = []
181
+ for i in range(5):
182
+ query = KnowledgeServiceQuery(
183
+ query_id=f"query-{i:03d}",
184
+ name=f"Query {i}",
185
+ knowledge_service_id="test-service",
186
+ prompt=f"Test prompt {i}",
187
+ )
188
+ queries.append(query)
189
+ await memory_repo.save(query)
190
+
191
+ # Test first page with size 2
192
+ response = client.get("/knowledge_service_queries/?page=1&size=2")
193
+ assert response.status_code == 200
194
+ data = response.json()
195
+
196
+ assert data["total"] == 5
197
+ assert data["page"] == 1
198
+ assert data["size"] == 2
199
+ assert len(data["items"]) == 2
200
+
201
+ # Test second page
202
+ response = client.get("/knowledge_service_queries/?page=2&size=2")
203
+ assert response.status_code == 200
204
+ data = response.json()
205
+
206
+ assert data["total"] == 5
207
+ assert data["page"] == 2
208
+ assert data["size"] == 2
209
+ assert len(data["items"]) == 2
210
+
211
+
212
+ class TestCreateKnowledgeServiceQuery:
213
+ """Test the POST / endpoint for creating knowledge service queries."""
214
+
215
+ def test_create_knowledge_service_query_success(
216
+ self,
217
+ client: TestClient,
218
+ memory_repo: MemoryKnowledgeServiceQueryRepository,
219
+ ) -> None:
220
+ """Test successful creation of a knowledge service query."""
221
+ request_data = {
222
+ "name": "Extract Meeting Summary",
223
+ "knowledge_service_id": "anthropic-claude",
224
+ "prompt": "Extract the main summary from this meeting transcript",
225
+ "query_metadata": {"model": "claude-3", "temperature": 0.2},
226
+ "assistant_prompt": "Please format as JSON",
227
+ }
228
+
229
+ response = client.post("/knowledge_service_queries/", json=request_data)
230
+
231
+ assert response.status_code == 200
232
+ data = response.json()
233
+
234
+ # Verify response structure
235
+ assert "query_id" in data
236
+ assert data["name"] == request_data["name"]
237
+ assert data["knowledge_service_id"] == request_data["knowledge_service_id"]
238
+ assert data["prompt"] == request_data["prompt"]
239
+ assert data["query_metadata"] == request_data["query_metadata"]
240
+ assert data["assistant_prompt"] == request_data["assistant_prompt"]
241
+ assert "created_at" in data
242
+ assert "updated_at" in data
243
+
244
+ # Verify the query was saved to repository
245
+ query_id = data["query_id"]
246
+ assert query_id is not None
247
+ assert query_id != ""
248
+
249
+ async def test_create_knowledge_service_query_persisted(
250
+ self,
251
+ client: TestClient,
252
+ memory_repo: MemoryKnowledgeServiceQueryRepository,
253
+ ) -> None:
254
+ """Test that created query is persisted in repository."""
255
+ request_data = {
256
+ "name": "Extract Action Items",
257
+ "knowledge_service_id": "openai-gpt4",
258
+ "prompt": "List all action items from this meeting",
259
+ }
260
+
261
+ response = client.post("/knowledge_service_queries/", json=request_data)
262
+ assert response.status_code == 200
263
+
264
+ query_id = response.json()["query_id"]
265
+
266
+ # Verify query was saved by retrieving it
267
+ saved_query = await memory_repo.get(query_id)
268
+ assert saved_query is not None
269
+ assert saved_query.name == request_data["name"]
270
+ assert saved_query.knowledge_service_id == request_data["knowledge_service_id"]
271
+ assert saved_query.prompt == request_data["prompt"]
272
+
273
+ def test_create_knowledge_service_query_minimal_fields(
274
+ self,
275
+ client: TestClient,
276
+ memory_repo: MemoryKnowledgeServiceQueryRepository,
277
+ ) -> None:
278
+ """Test creation with only required fields."""
279
+ request_data = {
280
+ "name": "Minimal Query",
281
+ "knowledge_service_id": "test-service",
282
+ "prompt": "Test prompt",
283
+ }
284
+
285
+ response = client.post("/knowledge_service_queries/", json=request_data)
286
+
287
+ assert response.status_code == 200
288
+ data = response.json()
289
+
290
+ assert data["name"] == request_data["name"]
291
+ assert data["knowledge_service_id"] == request_data["knowledge_service_id"]
292
+ assert data["prompt"] == request_data["prompt"]
293
+ assert data["query_metadata"] == {}
294
+ assert data["assistant_prompt"] is None
295
+
296
+ def test_create_knowledge_service_query_validation_errors(
297
+ self,
298
+ client: TestClient,
299
+ memory_repo: MemoryKnowledgeServiceQueryRepository,
300
+ ) -> None:
301
+ """Test validation error handling."""
302
+ # Test empty name
303
+ request_data = {
304
+ "name": "",
305
+ "knowledge_service_id": "test-service",
306
+ "prompt": "Test prompt",
307
+ }
308
+
309
+ response = client.post("/knowledge_service_queries/", json=request_data)
310
+ assert response.status_code == 422
311
+
312
+ # Test empty knowledge_service_id
313
+ request_data = {
314
+ "name": "Test Query",
315
+ "knowledge_service_id": "",
316
+ "prompt": "Test prompt",
317
+ }
318
+
319
+ response = client.post("/knowledge_service_queries/", json=request_data)
320
+ assert response.status_code == 422
321
+
322
+ # Test empty prompt
323
+ request_data = {
324
+ "name": "Test Query",
325
+ "knowledge_service_id": "test-service",
326
+ "prompt": "",
327
+ }
328
+
329
+ response = client.post("/knowledge_service_queries/", json=request_data)
330
+ assert response.status_code == 422
331
+
332
+ def test_create_knowledge_service_query_missing_required_fields(
333
+ self,
334
+ client: TestClient,
335
+ memory_repo: MemoryKnowledgeServiceQueryRepository,
336
+ ) -> None:
337
+ """Test handling of missing required fields."""
338
+ # Missing name
339
+ request_data = {
340
+ "knowledge_service_id": "test-service",
341
+ "prompt": "Test prompt",
342
+ }
343
+
344
+ response = client.post("/knowledge_service_queries/", json=request_data)
345
+ assert response.status_code == 422
346
+
347
+ # Missing knowledge_service_id
348
+ request_data = {
349
+ "name": "Test Query",
350
+ "prompt": "Test prompt",
351
+ }
352
+
353
+ response = client.post("/knowledge_service_queries/", json=request_data)
354
+ assert response.status_code == 422
355
+
356
+ # Missing prompt
357
+ request_data = {
358
+ "name": "Test Query",
359
+ "knowledge_service_id": "test-service",
360
+ }
361
+
362
+ response = client.post("/knowledge_service_queries/", json=request_data)
363
+ assert response.status_code == 422
364
+
365
+ def test_post_and_get_integration(
366
+ self,
367
+ client: TestClient,
368
+ memory_repo: MemoryKnowledgeServiceQueryRepository,
369
+ ) -> None:
370
+ """Test that POST and GET endpoints work together."""
371
+ # Create a query via POST
372
+ request_data = {
373
+ "name": "Integration Test Query",
374
+ "knowledge_service_id": "test-integration-service",
375
+ "prompt": "This is an integration test prompt",
376
+ "query_metadata": {"test": True, "integration": "yes"},
377
+ "assistant_prompt": "Integration test response format",
378
+ }
379
+
380
+ post_response = client.post("/knowledge_service_queries/", json=request_data)
381
+ assert post_response.status_code == 200
382
+ created_query = post_response.json()
383
+
384
+ # Verify the query appears in GET response
385
+ get_response = client.get("/knowledge_service_queries/")
386
+ assert get_response.status_code == 200
387
+ get_data = get_response.json()
388
+
389
+ # Should find our created query in the list
390
+ assert get_data["total"] == 1
391
+ assert len(get_data["items"]) == 1
392
+
393
+ returned_query = get_data["items"][0]
394
+ assert returned_query["query_id"] == created_query["query_id"]
395
+ assert returned_query["name"] == request_data["name"]
396
+ assert (
397
+ returned_query["knowledge_service_id"]
398
+ == request_data["knowledge_service_id"]
399
+ )
400
+ assert returned_query["prompt"] == request_data["prompt"]
401
+ assert returned_query["query_metadata"] == request_data["query_metadata"]
402
+ assert returned_query["assistant_prompt"] == request_data["assistant_prompt"]
403
+
404
+ # Create another query to test multiple items
405
+ request_data2 = {
406
+ "name": "Second Integration Query",
407
+ "knowledge_service_id": "another-service",
408
+ "prompt": "Another test prompt",
409
+ }
410
+
411
+ post_response2 = client.post("/knowledge_service_queries/", json=request_data2)
412
+ assert post_response2.status_code == 200
413
+
414
+ # Verify both queries appear in GET response
415
+ get_response2 = client.get("/knowledge_service_queries/")
416
+ assert get_response2.status_code == 200
417
+ get_data2 = get_response2.json()
418
+
419
+ assert get_data2["total"] == 2
420
+ assert len(get_data2["items"]) == 2
421
+
422
+ # Verify both query IDs are present
423
+ returned_ids = {item["query_id"] for item in get_data2["items"]}
424
+ expected_ids = {
425
+ created_query["query_id"],
426
+ post_response2.json()["query_id"],
427
+ }
428
+ assert returned_ids == expected_ids
429
+
430
+
431
+ class TestBulkGetKnowledgeServiceQueries:
432
+ """Test the bulk GET functionality with IDs parameter."""
433
+
434
+ async def test_bulk_get_queries_success(
435
+ self,
436
+ client: TestClient,
437
+ memory_repo: MemoryKnowledgeServiceQueryRepository,
438
+ ) -> None:
439
+ """Test successful bulk retrieval of queries by IDs."""
440
+ # Create test queries
441
+ queries = []
442
+ for i in range(3):
443
+ query = KnowledgeServiceQuery(
444
+ query_id=f"bulk-query-{i}",
445
+ name=f"Bulk Query {i}",
446
+ knowledge_service_id="test-service",
447
+ prompt=f"Test prompt {i}",
448
+ )
449
+ queries.append(query)
450
+ await memory_repo.save(query)
451
+
452
+ # Test bulk get with all IDs
453
+ ids_param = ",".join([q.query_id for q in queries])
454
+ response = client.get(f"/knowledge_service_queries/?ids={ids_param}")
455
+
456
+ assert response.status_code == 200
457
+ data = response.json()
458
+
459
+ # Verify pagination structure
460
+ assert "items" in data
461
+ assert "total" in data
462
+ assert data["total"] == 3
463
+ assert len(data["items"]) == 3
464
+
465
+ # Verify all queries are returned
466
+ returned_ids = {item["query_id"] for item in data["items"]}
467
+ expected_ids = {query.query_id for query in queries}
468
+ assert returned_ids == expected_ids
469
+
470
+ async def test_bulk_get_queries_partial_found(
471
+ self,
472
+ client: TestClient,
473
+ memory_repo: MemoryKnowledgeServiceQueryRepository,
474
+ ) -> None:
475
+ """Test bulk retrieval when only some IDs are found."""
476
+ # Create one query
477
+ query = KnowledgeServiceQuery(
478
+ query_id="existing-query",
479
+ name="Existing Query",
480
+ knowledge_service_id="test-service",
481
+ prompt="Test prompt",
482
+ )
483
+ await memory_repo.save(query)
484
+
485
+ # Request both existing and non-existing IDs
486
+ ids_param = "existing-query,non-existing-1,non-existing-2"
487
+ response = client.get(f"/knowledge_service_queries/?ids={ids_param}")
488
+
489
+ assert response.status_code == 200
490
+ data = response.json()
491
+
492
+ # Should return only the found query
493
+ assert data["total"] == 1
494
+ assert len(data["items"]) == 1
495
+ assert data["items"][0]["query_id"] == "existing-query"
496
+
497
+ def test_bulk_get_queries_empty_ids(
498
+ self,
499
+ client: TestClient,
500
+ memory_repo: MemoryKnowledgeServiceQueryRepository,
501
+ ) -> None:
502
+ """Test bulk retrieval with empty IDs parameter."""
503
+ response = client.get("/knowledge_service_queries/?ids=")
504
+
505
+ assert response.status_code == 400
506
+ data = response.json()
507
+ assert "Invalid ids parameter" in data["detail"]
508
+
509
+ def test_bulk_get_queries_whitespace_only_ids(
510
+ self,
511
+ client: TestClient,
512
+ memory_repo: MemoryKnowledgeServiceQueryRepository,
513
+ ) -> None:
514
+ """Test bulk retrieval with whitespace-only IDs."""
515
+ response = client.get("/knowledge_service_queries/?ids= , , ")
516
+
517
+ assert response.status_code == 400
518
+ data = response.json()
519
+ assert "Invalid ids parameter" in data["detail"]
520
+
521
+ def test_bulk_get_queries_too_many_ids(
522
+ self,
523
+ client: TestClient,
524
+ memory_repo: MemoryKnowledgeServiceQueryRepository,
525
+ ) -> None:
526
+ """Test bulk retrieval with too many IDs."""
527
+ # Create 101 IDs (exceeds limit of 100)
528
+ ids = [f"query-{i}" for i in range(101)]
529
+ ids_param = ",".join(ids)
530
+
531
+ response = client.get(f"/knowledge_service_queries/?ids={ids_param}")
532
+
533
+ assert response.status_code == 400
534
+ data = response.json()
535
+ assert "Too many IDs requested" in data["detail"]
536
+ assert "maximum 100" in data["detail"]
537
+
538
+ async def test_bulk_get_queries_with_spaces_and_commas(
539
+ self,
540
+ client: TestClient,
541
+ memory_repo: MemoryKnowledgeServiceQueryRepository,
542
+ ) -> None:
543
+ """Test bulk retrieval with various comma and space combinations."""
544
+ # Create test queries
545
+ queries = []
546
+ for i in range(2):
547
+ query = KnowledgeServiceQuery(
548
+ query_id=f"space-query-{i}",
549
+ name=f"Space Query {i}",
550
+ knowledge_service_id="test-service",
551
+ prompt=f"Test prompt {i}",
552
+ )
553
+ queries.append(query)
554
+ await memory_repo.save(query)
555
+
556
+ # Test with various spacing and comma patterns
557
+ test_cases = [
558
+ "space-query-0,space-query-1",
559
+ "space-query-0, space-query-1",
560
+ " space-query-0 , space-query-1 ",
561
+ "space-query-0, space-query-1 ,",
562
+ ]
563
+
564
+ for ids_param in test_cases:
565
+ response = client.get(f"/knowledge_service_queries/?ids={ids_param}")
566
+ assert response.status_code == 200
567
+ data = response.json()
568
+ assert data["total"] == 2
569
+ returned_ids = {item["query_id"] for item in data["items"]}
570
+ expected_ids = {q.query_id for q in queries}
571
+ assert returned_ids == expected_ids
572
+
573
+ async def test_bulk_get_queries_single_id(
574
+ self,
575
+ client: TestClient,
576
+ memory_repo: MemoryKnowledgeServiceQueryRepository,
577
+ ) -> None:
578
+ """Test bulk retrieval with a single ID."""
579
+ query = KnowledgeServiceQuery(
580
+ query_id="single-query",
581
+ name="Single Query",
582
+ knowledge_service_id="test-service",
583
+ prompt="Single test prompt",
584
+ )
585
+ await memory_repo.save(query)
586
+
587
+ response = client.get("/knowledge_service_queries/?ids=single-query")
588
+
589
+ assert response.status_code == 200
590
+ data = response.json()
591
+ assert data["total"] == 1
592
+ assert data["items"][0]["query_id"] == "single-query"
593
+ assert data["items"][0]["name"] == "Single Query"
594
+
595
+ def test_bulk_get_queries_no_ids_falls_back_to_list_all(
596
+ self,
597
+ client: TestClient,
598
+ memory_repo: MemoryKnowledgeServiceQueryRepository,
599
+ ) -> None:
600
+ """Test that without IDs parameter, it falls back to list all."""
601
+ response = client.get("/knowledge_service_queries/")
602
+
603
+ assert response.status_code == 200
604
+ data = response.json()
605
+
606
+ # Should have pagination structure from list all
607
+ assert "items" in data
608
+ assert "total" in data
609
+ assert "page" in data
610
+ assert "size" in data
611
+ assert "pages" in data
612
+
613
+ async def test_bulk_get_queries_integration_with_assembly_spec_use_case(
614
+ self,
615
+ client: TestClient,
616
+ memory_repo: MemoryKnowledgeServiceQueryRepository,
617
+ ) -> None:
618
+ """Test the typical use case: getting queries referenced by spec."""
619
+ # Create queries that would be referenced by an assembly spec
620
+ query_mappings = {
621
+ "/properties/attendees": "attendee-extractor",
622
+ "/properties/summary": "summary-extractor",
623
+ "/properties/action_items": "action-extractor",
624
+ }
625
+
626
+ queries = []
627
+ for json_pointer, query_id in query_mappings.items():
628
+ query = KnowledgeServiceQuery(
629
+ query_id=query_id,
630
+ name=f"Query for {json_pointer}",
631
+ knowledge_service_id="test-service",
632
+ prompt=f"Extract data for {json_pointer}",
633
+ )
634
+ queries.append(query)
635
+ await memory_repo.save(query)
636
+
637
+ # Simulate getting all queries referenced by an assembly spec
638
+ query_ids = list(query_mappings.values())
639
+ ids_param = ",".join(query_ids)
640
+
641
+ response = client.get(f"/knowledge_service_queries/?ids={ids_param}")
642
+
643
+ assert response.status_code == 200
644
+ data = response.json()
645
+
646
+ # Should get all referenced queries
647
+ assert data["total"] == 3
648
+ returned_ids = {item["query_id"] for item in data["items"]}
649
+ assert returned_ids == set(query_ids)
650
+
651
+ # Verify query details are complete
652
+ for item in data["items"]:
653
+ assert "query_id" in item
654
+ assert "name" in item
655
+ assert "knowledge_service_id" in item
656
+ assert "prompt" in item
657
+ assert "query_metadata" in item
658
+
659
+
660
+ class TestGetIndividualKnowledgeServiceQuery:
661
+ """Tests for the GET /knowledge_service_queries/{query_id} endpoint."""
662
+
663
+ async def test_get_query_success(
664
+ self,
665
+ client: TestClient,
666
+ memory_repo: MemoryKnowledgeServiceQueryRepository,
667
+ ) -> None:
668
+ """Test successfully retrieving an individual query."""
669
+ # Create a test query
670
+ query = KnowledgeServiceQuery(
671
+ query_id="test-query-123",
672
+ name="Test Query",
673
+ knowledge_service_id="test-service",
674
+ prompt="Extract test data",
675
+ assistant_prompt="Assistant instructions",
676
+ query_metadata={"max_tokens": 100, "temperature": 0.7},
677
+ )
678
+ await memory_repo.save(query)
679
+
680
+ # Get the query
681
+ response = client.get("/knowledge_service_queries/test-query-123")
682
+
683
+ assert response.status_code == 200
684
+ data = response.json()
685
+
686
+ assert data["query_id"] == "test-query-123"
687
+ assert data["name"] == "Test Query"
688
+ assert data["knowledge_service_id"] == "test-service"
689
+ assert data["prompt"] == "Extract test data"
690
+ assert data["assistant_prompt"] == "Assistant instructions"
691
+ assert data["query_metadata"] == {
692
+ "max_tokens": 100,
693
+ "temperature": 0.7,
694
+ }
695
+ assert "created_at" in data
696
+ assert "updated_at" in data
697
+
698
+ def test_get_query_not_found(self, client: TestClient) -> None:
699
+ """Test retrieving a non-existent query returns 404."""
700
+ response = client.get("/knowledge_service_queries/nonexistent-query")
701
+
702
+ assert response.status_code == 404
703
+ data = response.json()
704
+ assert "not found" in data["detail"].lower()
705
+ assert "nonexistent-query" in data["detail"]
706
+
707
+ def test_get_query_empty_id(self, client: TestClient) -> None:
708
+ """Test that empty query ID in URL is handled properly."""
709
+ # FastAPI will treat this as a different route, test edge case
710
+ response = client.get("/knowledge_service_queries/")
711
+ # This should hit the list endpoint instead
712
+ assert response.status_code == 200
713
+
714
+ async def test_get_query_without_optional_fields(
715
+ self,
716
+ client: TestClient,
717
+ memory_repo: MemoryKnowledgeServiceQueryRepository,
718
+ ) -> None:
719
+ """Test retrieving a query that doesn't have optional fields."""
720
+ # Create a minimal query without assistant_prompt
721
+ query = KnowledgeServiceQuery(
722
+ query_id="minimal-query",
723
+ name="Minimal Query",
724
+ knowledge_service_id="test-service",
725
+ prompt="Basic prompt",
726
+ query_metadata={},
727
+ )
728
+ await memory_repo.save(query)
729
+
730
+ response = client.get("/knowledge_service_queries/minimal-query")
731
+
732
+ assert response.status_code == 200
733
+ data = response.json()
734
+
735
+ assert data["query_id"] == "minimal-query"
736
+ assert data["name"] == "Minimal Query"
737
+ assert data["assistant_prompt"] is None
738
+ assert data["query_metadata"] == {}