julee 0.1.3__py3-none-any.whl → 0.1.5__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 (82) hide show
  1. julee/__init__.py +1 -1
  2. julee/api/tests/routers/test_assembly_specifications.py +2 -0
  3. julee/api/tests/routers/test_documents.py +8 -6
  4. julee/api/tests/routers/test_knowledge_service_configs.py +2 -0
  5. julee/api/tests/routers/test_knowledge_service_queries.py +2 -0
  6. julee/api/tests/routers/test_system.py +2 -0
  7. julee/api/tests/routers/test_workflows.py +2 -0
  8. julee/api/tests/test_app.py +2 -0
  9. julee/api/tests/test_dependencies.py +2 -0
  10. julee/api/tests/test_requests.py +2 -0
  11. julee/contrib/polling/__init__.py +22 -19
  12. julee/contrib/polling/apps/__init__.py +17 -0
  13. julee/contrib/polling/apps/worker/__init__.py +17 -0
  14. julee/contrib/polling/apps/worker/pipelines.py +288 -0
  15. julee/contrib/polling/domain/__init__.py +7 -9
  16. julee/contrib/polling/domain/models/__init__.py +6 -7
  17. julee/contrib/polling/domain/models/polling_config.py +18 -1
  18. julee/contrib/polling/domain/services/__init__.py +6 -5
  19. julee/contrib/polling/domain/services/poller.py +1 -1
  20. julee/contrib/polling/infrastructure/__init__.py +9 -8
  21. julee/contrib/polling/infrastructure/services/__init__.py +6 -5
  22. julee/contrib/polling/infrastructure/services/polling/__init__.py +6 -5
  23. julee/contrib/polling/infrastructure/services/polling/http/__init__.py +6 -5
  24. julee/contrib/polling/infrastructure/services/polling/http/http_poller_service.py +5 -2
  25. julee/contrib/polling/infrastructure/temporal/__init__.py +12 -12
  26. julee/contrib/polling/infrastructure/temporal/activities.py +1 -1
  27. julee/contrib/polling/infrastructure/temporal/manager.py +291 -0
  28. julee/contrib/polling/infrastructure/temporal/proxies.py +1 -1
  29. julee/contrib/polling/tests/unit/apps/worker/test_pipelines.py +580 -0
  30. julee/contrib/polling/tests/unit/infrastructure/services/polling/http/test_http_poller_service.py +40 -2
  31. julee/contrib/polling/tests/unit/infrastructure/temporal/__init__.py +7 -0
  32. julee/contrib/polling/tests/unit/infrastructure/temporal/test_manager.py +475 -0
  33. julee/docs/sphinx_hcd/__init__.py +4 -10
  34. julee/docs/sphinx_hcd/accelerators.py +277 -180
  35. julee/docs/sphinx_hcd/apps.py +78 -59
  36. julee/docs/sphinx_hcd/config.py +16 -16
  37. julee/docs/sphinx_hcd/epics.py +47 -42
  38. julee/docs/sphinx_hcd/integrations.py +53 -49
  39. julee/docs/sphinx_hcd/journeys.py +124 -110
  40. julee/docs/sphinx_hcd/personas.py +75 -53
  41. julee/docs/sphinx_hcd/stories.py +99 -71
  42. julee/docs/sphinx_hcd/utils.py +23 -18
  43. julee/domain/models/assembly/tests/test_assembly.py +2 -0
  44. julee/domain/models/assembly_specification/tests/test_assembly_specification.py +2 -0
  45. julee/domain/models/assembly_specification/tests/test_knowledge_service_query.py +2 -0
  46. julee/domain/models/custom_fields/tests/test_custom_fields.py +2 -0
  47. julee/domain/models/document/document.py +12 -21
  48. julee/domain/models/document/tests/test_document.py +16 -34
  49. julee/domain/models/policy/tests/test_document_policy_validation.py +2 -0
  50. julee/domain/models/policy/tests/test_policy.py +2 -0
  51. julee/domain/use_cases/extract_assemble_data.py +1 -1
  52. julee/domain/use_cases/initialize_system_data.py +75 -21
  53. julee/domain/use_cases/tests/test_extract_assemble_data.py +2 -0
  54. julee/domain/use_cases/tests/test_initialize_system_data.py +2 -0
  55. julee/domain/use_cases/tests/test_validate_document.py +2 -0
  56. julee/fixtures/documents.yaml +4 -43
  57. julee/fixtures/knowledge_service_queries.yaml +9 -0
  58. julee/maintenance/release.py +90 -30
  59. julee/repositories/memory/document.py +19 -13
  60. julee/repositories/memory/tests/test_document.py +20 -18
  61. julee/repositories/memory/tests/test_document_policy_validation.py +2 -0
  62. julee/repositories/memory/tests/test_policy.py +2 -0
  63. julee/repositories/minio/document.py +25 -22
  64. julee/repositories/minio/tests/test_assembly.py +2 -0
  65. julee/repositories/minio/tests/test_assembly_specification.py +2 -0
  66. julee/repositories/minio/tests/test_client_protocol.py +3 -0
  67. julee/repositories/minio/tests/test_document.py +18 -16
  68. julee/repositories/minio/tests/test_document_policy_validation.py +2 -0
  69. julee/repositories/minio/tests/test_knowledge_service_config.py +2 -0
  70. julee/repositories/minio/tests/test_knowledge_service_query.py +2 -0
  71. julee/repositories/minio/tests/test_policy.py +2 -0
  72. julee/services/knowledge_service/anthropic/tests/test_knowledge_service.py +2 -0
  73. julee/services/knowledge_service/memory/test_knowledge_service.py +2 -0
  74. julee/services/knowledge_service/test_factory.py +2 -0
  75. julee/util/tests/test_decorators.py +2 -0
  76. julee-0.1.5.dist-info/METADATA +103 -0
  77. {julee-0.1.3.dist-info → julee-0.1.5.dist-info}/RECORD +80 -74
  78. julee/fixtures/assembly_specifications.yaml +0 -70
  79. julee-0.1.3.dist-info/METADATA +0 -198
  80. {julee-0.1.3.dist-info → julee-0.1.5.dist-info}/WHEEL +0 -0
  81. {julee-0.1.3.dist-info → julee-0.1.5.dist-info}/licenses/LICENSE +0 -0
  82. {julee-0.1.3.dist-info → julee-0.1.5.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,475 @@
1
+ """
2
+ Unit tests for PollingManager.
3
+
4
+ This module tests the PollingManager class using mocked Temporal client,
5
+ as the test environment doesn't support schedule operations. We mock the
6
+ Temporal client to test the manager's business logic and error handling
7
+ without requiring actual Temporal infrastructure.
8
+ """
9
+
10
+ from unittest.mock import AsyncMock, MagicMock
11
+
12
+ import pytest
13
+ from temporalio.client import Client, ScheduleAlreadyRunningError
14
+
15
+ from julee.contrib.polling.domain.models.polling_config import (
16
+ PollingConfig,
17
+ PollingProtocol,
18
+ )
19
+ from julee.contrib.polling.infrastructure.temporal.manager import PollingManager
20
+
21
+
22
+ @pytest.fixture
23
+ def mock_temporal_client():
24
+ """Provide a mocked Temporal client."""
25
+ client = AsyncMock(spec=Client)
26
+
27
+ # Mock schedule handle for operations with stateful pause behavior
28
+ mock_schedule_handle = AsyncMock()
29
+ client.get_schedule_handle.return_value = mock_schedule_handle
30
+
31
+ # Create a stateful mock for pause/resume behavior
32
+ paused_state = {"paused": False}
33
+
34
+ async def mock_pause():
35
+ paused_state["paused"] = True
36
+
37
+ async def mock_unpause():
38
+ paused_state["paused"] = False
39
+
40
+ def mock_describe():
41
+ mock_description = MagicMock()
42
+ mock_description.schedule.state.paused = paused_state["paused"]
43
+ return mock_description
44
+
45
+ mock_schedule_handle.pause.side_effect = mock_pause
46
+ mock_schedule_handle.unpause.side_effect = mock_unpause
47
+ mock_schedule_handle.describe.side_effect = mock_describe
48
+
49
+ return client
50
+
51
+
52
+ @pytest.fixture
53
+ def polling_manager(mock_temporal_client):
54
+ """Provide a PollingManager with mocked client."""
55
+ return PollingManager(mock_temporal_client)
56
+
57
+
58
+ @pytest.fixture
59
+ def sample_config():
60
+ """Provide a sample PollingConfig for testing."""
61
+ return PollingConfig(
62
+ endpoint_identifier="test-api",
63
+ polling_protocol=PollingProtocol.HTTP,
64
+ connection_params={"url": "https://api.example.com/data"},
65
+ timeout_seconds=30,
66
+ )
67
+
68
+
69
+ class TestPollingManagerInitialization:
70
+ """Test PollingManager initialization."""
71
+
72
+ def test_init_with_client(self, mock_temporal_client):
73
+ """Test initialization with temporal client."""
74
+ manager = PollingManager(mock_temporal_client)
75
+ assert manager._temporal_client is not None
76
+ assert manager._active_polls == {}
77
+
78
+ def test_init_without_client(self):
79
+ """Test initialization without temporal client."""
80
+ manager = PollingManager()
81
+ assert manager._temporal_client is None
82
+ assert manager._active_polls == {}
83
+
84
+ def test_init_with_custom_task_queue(self, mock_temporal_client):
85
+ """Test initialization with custom task queue."""
86
+ custom_queue = "custom-task-queue"
87
+ manager = PollingManager(mock_temporal_client, task_queue=custom_queue)
88
+ assert manager._temporal_client is not None
89
+ assert manager._task_queue == custom_queue
90
+ assert manager._active_polls == {}
91
+
92
+ def test_init_with_default_task_queue(self, mock_temporal_client):
93
+ """Test initialization with default task queue."""
94
+ manager = PollingManager(mock_temporal_client)
95
+ assert manager._temporal_client is not None
96
+ assert manager._task_queue == "julee-polling-queue"
97
+ assert manager._active_polls == {}
98
+
99
+ @pytest.mark.asyncio
100
+ async def test_start_polling_with_custom_task_queue(
101
+ self, mock_temporal_client, sample_config
102
+ ):
103
+ """Test that custom task queue is used in schedule creation."""
104
+ custom_queue = "custom-polling-queue"
105
+ manager = PollingManager(mock_temporal_client, task_queue=custom_queue)
106
+
107
+ # Capture the schedule created
108
+ schedule_id = await manager.start_polling("test-endpoint", sample_config, 60)
109
+
110
+ # Verify create_schedule was called
111
+ mock_temporal_client.create_schedule.assert_called_once()
112
+ call_args = mock_temporal_client.create_schedule.call_args
113
+
114
+ # Verify the schedule uses the custom task queue
115
+ schedule_obj = call_args[1]["schedule"] # kwargs['schedule']
116
+ assert schedule_obj.action.task_queue == custom_queue
117
+ assert schedule_id == "poll-test-endpoint"
118
+
119
+ @pytest.mark.asyncio
120
+ async def test_update_existing_schedule(self, mock_temporal_client, sample_config):
121
+ """Test updating existing schedule when one already exists."""
122
+ manager = PollingManager(mock_temporal_client)
123
+
124
+ # Mock create_schedule to raise ScheduleAlreadyRunningError
125
+ mock_temporal_client.create_schedule.side_effect = ScheduleAlreadyRunningError()
126
+
127
+ # Mock schedule handle for update
128
+ mock_schedule_handle = AsyncMock()
129
+ mock_temporal_client.get_schedule_handle.return_value = mock_schedule_handle
130
+
131
+ schedule_id = await manager.start_polling("test-endpoint", sample_config, 60)
132
+
133
+ assert schedule_id == "poll-test-endpoint"
134
+ assert "test-endpoint" in manager._active_polls
135
+
136
+ # Verify update was called on the existing schedule
137
+ mock_schedule_handle.update.assert_called_once()
138
+ # Verify create_schedule was called once (and failed)
139
+ mock_temporal_client.create_schedule.assert_called_once()
140
+
141
+
142
+ class TestPollingManagerStartPolling:
143
+ """Test PollingManager start_polling method."""
144
+
145
+ @pytest.mark.asyncio
146
+ async def test_start_polling_success(self, polling_manager, sample_config):
147
+ """Test successful polling start creates schedule and tracks state."""
148
+ schedule_id = await polling_manager.start_polling(
149
+ "test-endpoint", sample_config, 60
150
+ )
151
+
152
+ # Verify return value
153
+ assert schedule_id == "poll-test-endpoint"
154
+
155
+ # Verify internal tracking
156
+ assert "test-endpoint" in polling_manager._active_polls
157
+ poll_info = polling_manager._active_polls["test-endpoint"]
158
+ assert poll_info["schedule_id"] == "poll-test-endpoint"
159
+ assert poll_info["config"] == sample_config
160
+ assert poll_info["interval_seconds"] == 60
161
+ assert poll_info["downstream_pipeline"] is None
162
+
163
+ @pytest.mark.asyncio
164
+ async def test_start_polling_with_downstream_pipeline(
165
+ self, polling_manager, sample_config
166
+ ):
167
+ """Test polling start with downstream pipeline."""
168
+ schedule_id = await polling_manager.start_polling(
169
+ "test-endpoint", sample_config, 30, "custom-pipeline"
170
+ )
171
+
172
+ assert schedule_id == "poll-test-endpoint"
173
+ poll_info = polling_manager._active_polls["test-endpoint"]
174
+ assert poll_info["downstream_pipeline"] == "custom-pipeline"
175
+
176
+ @pytest.mark.asyncio
177
+ async def test_start_polling_duplicate_endpoint_raises_error(
178
+ self, polling_manager, sample_config
179
+ ):
180
+ """Test starting polling for duplicate endpoint raises ValueError."""
181
+ # Start polling first time
182
+ await polling_manager.start_polling("test-endpoint", sample_config, 60)
183
+
184
+ # Attempt to start again should raise error
185
+ with pytest.raises(
186
+ ValueError, match="Endpoint test-endpoint is already being polled"
187
+ ):
188
+ await polling_manager.start_polling("test-endpoint", sample_config, 60)
189
+
190
+ @pytest.mark.asyncio
191
+ async def test_start_polling_no_client_raises_error(self, sample_config):
192
+ """Test starting polling without client raises RuntimeError."""
193
+ manager = PollingManager() # No client
194
+
195
+ with pytest.raises(RuntimeError, match="Temporal client not available"):
196
+ await manager.start_polling("test-endpoint", sample_config, 60)
197
+
198
+ @pytest.mark.asyncio
199
+ async def test_start_polling_multiple_endpoints(
200
+ self, polling_manager, sample_config
201
+ ):
202
+ """Test starting polling for multiple endpoints."""
203
+ # Create different configs for different endpoints
204
+ config2 = PollingConfig(
205
+ endpoint_identifier="test-api-2",
206
+ polling_protocol=PollingProtocol.HTTP,
207
+ connection_params={"url": "https://api2.example.com/data"},
208
+ )
209
+
210
+ # Start polling for multiple endpoints
211
+ schedule_id1 = await polling_manager.start_polling(
212
+ "endpoint-1", sample_config, 60
213
+ )
214
+ schedule_id2 = await polling_manager.start_polling("endpoint-2", config2, 30)
215
+
216
+ # Verify both are tracked
217
+ assert schedule_id1 == "poll-endpoint-1"
218
+ assert schedule_id2 == "poll-endpoint-2"
219
+ assert "endpoint-1" in polling_manager._active_polls
220
+ assert "endpoint-2" in polling_manager._active_polls
221
+ assert len(polling_manager._active_polls) == 2
222
+
223
+
224
+ class TestPollingManagerStopPolling:
225
+ """Test PollingManager stop_polling method."""
226
+
227
+ @pytest.mark.asyncio
228
+ async def test_stop_polling_success(self, polling_manager, sample_config):
229
+ """Test successful polling stop removes schedule and tracking."""
230
+ # Start polling first
231
+ await polling_manager.start_polling("test-endpoint", sample_config, 60)
232
+ assert "test-endpoint" in polling_manager._active_polls
233
+
234
+ # Stop polling
235
+ result = await polling_manager.stop_polling("test-endpoint")
236
+
237
+ # Verify success and cleanup
238
+ assert result is True
239
+ assert "test-endpoint" not in polling_manager._active_polls
240
+
241
+ @pytest.mark.asyncio
242
+ async def test_stop_polling_nonexistent_endpoint(self, polling_manager):
243
+ """Test stopping polling for non-existent endpoint returns False."""
244
+ result = await polling_manager.stop_polling("nonexistent-endpoint")
245
+ assert result is False
246
+
247
+ @pytest.mark.asyncio
248
+ async def test_stop_polling_no_client_raises_error(self, sample_config):
249
+ """Test stopping polling without client raises RuntimeError."""
250
+ manager = PollingManager() # No client
251
+ # Manually add to tracking to test the error condition
252
+ manager._active_polls["test-endpoint"] = {
253
+ "schedule_id": "poll-test-endpoint",
254
+ "config": sample_config,
255
+ "interval_seconds": 60,
256
+ "downstream_pipeline": None,
257
+ }
258
+
259
+ with pytest.raises(RuntimeError, match="Temporal client not available"):
260
+ await manager.stop_polling("test-endpoint")
261
+
262
+
263
+ class TestPollingManagerListActivePolling:
264
+ """Test PollingManager list_active_polling method."""
265
+
266
+ @pytest.mark.asyncio
267
+ async def test_list_active_polling_empty(self, polling_manager):
268
+ """Test listing active polls when none exist."""
269
+ active_polls = await polling_manager.list_active_polling()
270
+ assert active_polls == []
271
+
272
+ @pytest.mark.asyncio
273
+ async def test_list_active_polling_with_data(self, polling_manager, sample_config):
274
+ """Test listing active polls with existing data."""
275
+ # Start some polling operations
276
+ await polling_manager.start_polling("endpoint-1", sample_config, 60)
277
+ await polling_manager.start_polling(
278
+ "endpoint-2", sample_config, 30, "pipeline-2"
279
+ )
280
+
281
+ # List active polling
282
+ active_polls = await polling_manager.list_active_polling()
283
+
284
+ # Verify results
285
+ assert len(active_polls) == 2
286
+
287
+ # Find polls by endpoint_id (order not guaranteed)
288
+ endpoint1_poll = next(
289
+ p for p in active_polls if p["endpoint_id"] == "endpoint-1"
290
+ )
291
+ endpoint2_poll = next(
292
+ p for p in active_polls if p["endpoint_id"] == "endpoint-2"
293
+ )
294
+
295
+ # Verify endpoint-1 details
296
+ assert endpoint1_poll["schedule_id"] == "poll-endpoint-1"
297
+ assert endpoint1_poll["interval_seconds"] == 60
298
+ assert endpoint1_poll["endpoint_identifier"] == "test-api"
299
+ assert endpoint1_poll["polling_protocol"] == "http"
300
+ assert endpoint1_poll["downstream_pipeline"] is None
301
+
302
+ # Verify endpoint-2 details
303
+ assert endpoint2_poll["schedule_id"] == "poll-endpoint-2"
304
+ assert endpoint2_poll["interval_seconds"] == 30
305
+ assert endpoint2_poll["downstream_pipeline"] == "pipeline-2"
306
+
307
+
308
+ class TestPollingManagerGetPollingStatus:
309
+ """Test PollingManager get_polling_status method."""
310
+
311
+ @pytest.mark.asyncio
312
+ async def test_get_polling_status_nonexistent_endpoint(self, polling_manager):
313
+ """Test getting status for non-existent endpoint returns None."""
314
+ status = await polling_manager.get_polling_status("nonexistent-endpoint")
315
+ assert status is None
316
+
317
+ @pytest.mark.asyncio
318
+ async def test_get_polling_status_no_client_raises_error(self, sample_config):
319
+ """Test getting status without client raises RuntimeError."""
320
+ manager = PollingManager() # No client
321
+ # Manually add to tracking to test the error condition
322
+ manager._active_polls["test-endpoint"] = {
323
+ "schedule_id": "poll-test-endpoint",
324
+ "config": sample_config,
325
+ "interval_seconds": 60,
326
+ "downstream_pipeline": None,
327
+ }
328
+
329
+ with pytest.raises(RuntimeError, match="Temporal client not available"):
330
+ await manager.get_polling_status("test-endpoint")
331
+
332
+ @pytest.mark.asyncio
333
+ async def test_get_polling_status_success(self, polling_manager, sample_config):
334
+ """Test getting status for existing endpoint."""
335
+ # Start polling
336
+ await polling_manager.start_polling(
337
+ "test-endpoint", sample_config, 60, "test-pipeline"
338
+ )
339
+
340
+ # Get status
341
+ status = await polling_manager.get_polling_status("test-endpoint")
342
+
343
+ # Verify status details
344
+ assert status is not None
345
+ assert status["endpoint_id"] == "test-endpoint"
346
+ assert status["schedule_id"] == "poll-test-endpoint"
347
+ assert status["interval_seconds"] == 60
348
+ assert status["downstream_pipeline"] == "test-pipeline"
349
+ # Should not be paused initially
350
+ assert status["is_paused"] is False
351
+
352
+
353
+ class TestPollingManagerPauseResumePolling:
354
+ """Test PollingManager pause_polling and resume_polling methods."""
355
+
356
+ @pytest.mark.asyncio
357
+ async def test_pause_polling_success(self, polling_manager, sample_config):
358
+ """Test successful polling pause."""
359
+ # Start polling
360
+ await polling_manager.start_polling("test-endpoint", sample_config, 60)
361
+
362
+ # Pause polling
363
+ result = await polling_manager.pause_polling("test-endpoint")
364
+ assert result is True
365
+
366
+ @pytest.mark.asyncio
367
+ async def test_pause_polling_nonexistent_endpoint(self, polling_manager):
368
+ """Test pausing non-existent endpoint returns False."""
369
+ result = await polling_manager.pause_polling("nonexistent-endpoint")
370
+ assert result is False
371
+
372
+ @pytest.mark.asyncio
373
+ async def test_pause_polling_no_client_raises_error(self, sample_config):
374
+ """Test pausing polling without client raises RuntimeError."""
375
+ manager = PollingManager() # No client
376
+ # Manually add to tracking to test the error condition
377
+ manager._active_polls["test-endpoint"] = {
378
+ "schedule_id": "poll-test-endpoint",
379
+ "config": sample_config,
380
+ "interval_seconds": 60,
381
+ "downstream_pipeline": None,
382
+ }
383
+
384
+ with pytest.raises(RuntimeError, match="Temporal client not available"):
385
+ await manager.pause_polling("test-endpoint")
386
+
387
+ @pytest.mark.asyncio
388
+ async def test_resume_polling_success(self, polling_manager, sample_config):
389
+ """Test successful polling resume."""
390
+ # Start and pause polling
391
+ await polling_manager.start_polling("test-endpoint", sample_config, 60)
392
+ await polling_manager.pause_polling("test-endpoint")
393
+
394
+ # Resume polling
395
+ result = await polling_manager.resume_polling("test-endpoint")
396
+ assert result is True
397
+
398
+ @pytest.mark.asyncio
399
+ async def test_resume_polling_nonexistent_endpoint(self, polling_manager):
400
+ """Test resuming non-existent endpoint returns False."""
401
+ result = await polling_manager.resume_polling("nonexistent-endpoint")
402
+ assert result is False
403
+
404
+ @pytest.mark.asyncio
405
+ async def test_resume_polling_no_client_raises_error(self, sample_config):
406
+ """Test resuming polling without client raises RuntimeError."""
407
+ manager = PollingManager() # No client
408
+ # Manually add to tracking to test the error condition
409
+ manager._active_polls["test-endpoint"] = {
410
+ "schedule_id": "poll-test-endpoint",
411
+ "config": sample_config,
412
+ "interval_seconds": 60,
413
+ "downstream_pipeline": None,
414
+ }
415
+
416
+ with pytest.raises(RuntimeError, match="Temporal client not available"):
417
+ await manager.resume_polling("test-endpoint")
418
+
419
+ @pytest.mark.asyncio
420
+ async def test_pause_resume_workflow(self, polling_manager, sample_config):
421
+ """Test complete pause/resume workflow."""
422
+ # Start polling
423
+ await polling_manager.start_polling("test-endpoint", sample_config, 60)
424
+
425
+ # Pause polling
426
+ await polling_manager.pause_polling("test-endpoint")
427
+
428
+ # Verify paused status
429
+ status = await polling_manager.get_polling_status("test-endpoint")
430
+ assert status["is_paused"] is True
431
+
432
+ # Resume polling
433
+ await polling_manager.resume_polling("test-endpoint")
434
+
435
+ # Verify resumed status
436
+ status = await polling_manager.get_polling_status("test-endpoint")
437
+ assert status["is_paused"] is False
438
+
439
+
440
+ class TestPollingManagerIntegration:
441
+ """Integration tests for PollingManager full workflows."""
442
+
443
+ @pytest.mark.asyncio
444
+ async def test_full_polling_lifecycle(self, polling_manager, sample_config):
445
+ """Test complete polling lifecycle: start -> pause -> resume -> stop."""
446
+ endpoint_id = "lifecycle-test"
447
+
448
+ # Start polling
449
+ schedule_id = await polling_manager.start_polling(
450
+ endpoint_id, sample_config, 45
451
+ )
452
+ assert schedule_id == f"poll-{endpoint_id}"
453
+
454
+ # Verify it's in active polls
455
+ active_polls = await polling_manager.list_active_polling()
456
+ assert len(active_polls) == 1
457
+ assert active_polls[0]["endpoint_id"] == endpoint_id
458
+
459
+ # Pause polling
460
+ assert await polling_manager.pause_polling(endpoint_id) is True
461
+ status = await polling_manager.get_polling_status(endpoint_id)
462
+ assert status["is_paused"] is True
463
+
464
+ # Resume polling
465
+ assert await polling_manager.resume_polling(endpoint_id) is True
466
+ status = await polling_manager.get_polling_status(endpoint_id)
467
+ assert status["is_paused"] is False
468
+
469
+ # Stop polling
470
+ assert await polling_manager.stop_polling(endpoint_id) is True
471
+
472
+ # Verify cleanup
473
+ assert await polling_manager.get_polling_status(endpoint_id) is None
474
+ active_polls = await polling_manager.list_active_polling()
475
+ assert len(active_polls) == 0
@@ -37,7 +37,7 @@ Usage in conf.py::
37
37
 
38
38
  from sphinx.util import logging
39
39
 
40
- from .config import init_config, config_factory
40
+ from .config import config_factory, init_config
41
41
 
42
42
  logger = logging.getLogger(__name__)
43
43
 
@@ -45,19 +45,13 @@ logger = logging.getLogger(__name__)
45
45
  def setup(app):
46
46
  """Set up all HCD extensions for Sphinx."""
47
47
  # Register configuration value first
48
- app.add_config_value('sphinx_hcd', {}, 'env')
48
+ app.add_config_value("sphinx_hcd", {}, "env")
49
49
 
50
50
  # Initialize config when builder starts (after conf.py is loaded)
51
- app.connect('builder-inited', _init_config_handler, priority=0)
51
+ app.connect("builder-inited", _init_config_handler, priority=0)
52
52
 
53
53
  # Import and setup each extension module
54
- from . import stories
55
- from . import journeys
56
- from . import epics
57
- from . import apps
58
- from . import accelerators
59
- from . import integrations
60
- from . import personas
54
+ from . import accelerators, apps, epics, integrations, journeys, personas, stories
61
55
 
62
56
  # Call setup on each module
63
57
  stories.setup(app)