julee 0.1.4__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.
- julee/__init__.py +1 -1
- julee/api/tests/routers/test_assembly_specifications.py +2 -0
- julee/api/tests/routers/test_documents.py +2 -0
- julee/api/tests/routers/test_knowledge_service_configs.py +2 -0
- julee/api/tests/routers/test_knowledge_service_queries.py +2 -0
- julee/api/tests/routers/test_system.py +2 -0
- julee/api/tests/routers/test_workflows.py +2 -0
- julee/api/tests/test_app.py +2 -0
- julee/api/tests/test_dependencies.py +2 -0
- julee/api/tests/test_requests.py +2 -0
- julee/contrib/polling/__init__.py +22 -19
- julee/contrib/polling/apps/__init__.py +17 -0
- julee/contrib/polling/apps/worker/__init__.py +17 -0
- julee/contrib/polling/apps/worker/pipelines.py +288 -0
- julee/contrib/polling/domain/__init__.py +7 -9
- julee/contrib/polling/domain/models/__init__.py +6 -7
- julee/contrib/polling/domain/models/polling_config.py +18 -1
- julee/contrib/polling/domain/services/__init__.py +6 -5
- julee/contrib/polling/domain/services/poller.py +1 -1
- julee/contrib/polling/infrastructure/__init__.py +9 -8
- julee/contrib/polling/infrastructure/services/__init__.py +6 -5
- julee/contrib/polling/infrastructure/services/polling/__init__.py +6 -5
- julee/contrib/polling/infrastructure/services/polling/http/__init__.py +6 -5
- julee/contrib/polling/infrastructure/services/polling/http/http_poller_service.py +5 -2
- julee/contrib/polling/infrastructure/temporal/__init__.py +12 -12
- julee/contrib/polling/infrastructure/temporal/activities.py +1 -1
- julee/contrib/polling/infrastructure/temporal/manager.py +291 -0
- julee/contrib/polling/infrastructure/temporal/proxies.py +1 -1
- julee/contrib/polling/tests/unit/apps/worker/test_pipelines.py +580 -0
- julee/contrib/polling/tests/unit/infrastructure/services/polling/http/test_http_poller_service.py +40 -2
- julee/contrib/polling/tests/unit/infrastructure/temporal/__init__.py +7 -0
- julee/contrib/polling/tests/unit/infrastructure/temporal/test_manager.py +475 -0
- julee/domain/models/assembly/tests/test_assembly.py +2 -0
- julee/domain/models/assembly_specification/tests/test_assembly_specification.py +2 -0
- julee/domain/models/assembly_specification/tests/test_knowledge_service_query.py +2 -0
- julee/domain/models/custom_fields/tests/test_custom_fields.py +2 -0
- julee/domain/models/document/tests/test_document.py +2 -0
- julee/domain/models/policy/tests/test_document_policy_validation.py +2 -0
- julee/domain/models/policy/tests/test_policy.py +2 -0
- julee/domain/use_cases/tests/test_extract_assemble_data.py +2 -0
- julee/domain/use_cases/tests/test_initialize_system_data.py +2 -0
- julee/domain/use_cases/tests/test_validate_document.py +2 -0
- julee/maintenance/release.py +10 -5
- julee/repositories/memory/tests/test_document.py +2 -0
- julee/repositories/memory/tests/test_document_policy_validation.py +2 -0
- julee/repositories/memory/tests/test_policy.py +2 -0
- julee/repositories/minio/tests/test_assembly.py +2 -0
- julee/repositories/minio/tests/test_assembly_specification.py +2 -0
- julee/repositories/minio/tests/test_client_protocol.py +3 -0
- julee/repositories/minio/tests/test_document.py +2 -0
- julee/repositories/minio/tests/test_document_policy_validation.py +2 -0
- julee/repositories/minio/tests/test_knowledge_service_config.py +2 -0
- julee/repositories/minio/tests/test_knowledge_service_query.py +2 -0
- julee/repositories/minio/tests/test_policy.py +2 -0
- julee/services/knowledge_service/anthropic/tests/test_knowledge_service.py +2 -0
- julee/services/knowledge_service/memory/test_knowledge_service.py +2 -0
- julee/services/knowledge_service/test_factory.py +2 -0
- julee/util/tests/test_decorators.py +2 -0
- julee-0.1.5.dist-info/METADATA +103 -0
- {julee-0.1.4.dist-info → julee-0.1.5.dist-info}/RECORD +63 -56
- julee-0.1.4.dist-info/METADATA +0 -197
- {julee-0.1.4.dist-info → julee-0.1.5.dist-info}/WHEEL +0 -0
- {julee-0.1.4.dist-info → julee-0.1.5.dist-info}/licenses/LICENSE +0 -0
- {julee-0.1.4.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
|
|
@@ -27,6 +27,8 @@ from julee.domain.models.assembly_specification import (
|
|
|
27
27
|
|
|
28
28
|
from .factories import KnowledgeServiceQueryFactory
|
|
29
29
|
|
|
30
|
+
pytestmark = pytest.mark.unit
|
|
31
|
+
|
|
30
32
|
|
|
31
33
|
class TestKnowledgeServiceQueryInstantiation:
|
|
32
34
|
"""Test KnowledgeServiceQuery creation with various field combinations."""
|
julee/maintenance/release.py
CHANGED
|
@@ -151,7 +151,7 @@ def prepare(version: str, message_file: Path | None = None) -> None:
|
|
|
151
151
|
commit_msg = f"release: bump version to {version}"
|
|
152
152
|
|
|
153
153
|
# Use a temp file for the commit message to handle multiline properly
|
|
154
|
-
with tempfile.NamedTemporaryFile(mode=
|
|
154
|
+
with tempfile.NamedTemporaryFile(mode="w", suffix=".txt", delete=False) as f:
|
|
155
155
|
f.write(commit_msg)
|
|
156
156
|
commit_msg_file = f.name
|
|
157
157
|
try:
|
|
@@ -167,7 +167,7 @@ def prepare(version: str, message_file: Path | None = None) -> None:
|
|
|
167
167
|
print("Creating pull request...")
|
|
168
168
|
pr_body = release_notes if release_notes else f"Bump version to {version}"
|
|
169
169
|
|
|
170
|
-
with tempfile.NamedTemporaryFile(mode=
|
|
170
|
+
with tempfile.NamedTemporaryFile(mode="w", suffix=".txt", delete=False) as f:
|
|
171
171
|
f.write(pr_body)
|
|
172
172
|
pr_body_file = f.name
|
|
173
173
|
try:
|
|
@@ -215,14 +215,19 @@ def tag(version: str) -> None:
|
|
|
215
215
|
|
|
216
216
|
|
|
217
217
|
def main() -> None:
|
|
218
|
-
parser = argparse.ArgumentParser(
|
|
218
|
+
parser = argparse.ArgumentParser(
|
|
219
|
+
description="Release preparation and tagging script"
|
|
220
|
+
)
|
|
219
221
|
subparsers = parser.add_subparsers(dest="command", required=True)
|
|
220
222
|
|
|
221
223
|
# prepare subcommand
|
|
222
|
-
prepare_parser = subparsers.add_parser(
|
|
224
|
+
prepare_parser = subparsers.add_parser(
|
|
225
|
+
"prepare", help="Create release branch and PR"
|
|
226
|
+
)
|
|
223
227
|
prepare_parser.add_argument("version", help="Version number (X.Y.Z)")
|
|
224
228
|
prepare_parser.add_argument(
|
|
225
|
-
"--message-file",
|
|
229
|
+
"--message-file",
|
|
230
|
+
"-m",
|
|
226
231
|
type=Path,
|
|
227
232
|
help="File containing release notes for commit message and PR body",
|
|
228
233
|
)
|
|
@@ -6,10 +6,13 @@ MinioClient protocol, ensuring that our protocol definition matches the
|
|
|
6
6
|
actual interface.
|
|
7
7
|
"""
|
|
8
8
|
|
|
9
|
+
import pytest
|
|
9
10
|
from minio import Minio
|
|
10
11
|
|
|
11
12
|
from ..client import MinioClient
|
|
12
13
|
|
|
14
|
+
pytestmark = pytest.mark.unit
|
|
15
|
+
|
|
13
16
|
|
|
14
17
|
class TestMinioClientProtocol:
|
|
15
18
|
"""Test that the real Minio client implements our protocol."""
|