Skip to content

Commit 39a903e

Browse files
test: cover server version compatibility check in worker._register
- Add 7 unit tests verifying the compatibility branch introduced in 18f1825: accepts major 0 and 2, rejects 1 and 3, tolerates unparseable versions, and falls through when /cluster/info itself errors. - Narrow Client.get_cluster_info return type with an isinstance assert (matches the pattern used by Client.health) so mypy infers dict[str, Any] cleanly. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent e991aa2 commit 39a903e

2 files changed

Lines changed: 54 additions & 1 deletion

File tree

src/durable_workflow/client.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -301,7 +301,9 @@ async def _do_request() -> httpx.Response:
301301

302302
async def get_cluster_info(self) -> dict[str, Any]:
303303
"""Fetch server version and capabilities from /api/cluster/info."""
304-
return await self._request("GET", "/cluster/info", worker=False, context="get_cluster_info")
304+
result = await self._request("GET", "/cluster/info", worker=False, context="get_cluster_info")
305+
assert isinstance(result, dict)
306+
return result
305307

306308
def get_workflow_handle(
307309
self, workflow_id: str, *, run_id: str | None = None, workflow_type: str = ""

tests/test_worker.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ def mock_client() -> AsyncMock:
3838
client.complete_activity_task = AsyncMock(return_value={"outcome": "completed"})
3939
client.fail_workflow_task = AsyncMock(return_value={"outcome": "failed"})
4040
client.fail_activity_task = AsyncMock(return_value={"outcome": "failed"})
41+
client.get_cluster_info = AsyncMock(return_value={"version": "2.0.0"})
4142
return client
4243

4344

@@ -58,6 +59,56 @@ async def test_register(self, mock_client: AsyncMock) -> None:
5859
assert "test-wf" in call_kwargs["supported_workflow_types"]
5960
assert "test-act" in call_kwargs["supported_activity_types"]
6061

62+
@pytest.mark.asyncio
63+
async def test_register_calls_cluster_info(self, mock_client: AsyncMock) -> None:
64+
worker = Worker(mock_client, task_queue="q1", workflows=[TestWorkflow], activities=[])
65+
await worker._register()
66+
mock_client.get_cluster_info.assert_awaited_once()
67+
68+
@pytest.mark.asyncio
69+
async def test_register_accepts_server_major_0(self, mock_client: AsyncMock) -> None:
70+
mock_client.get_cluster_info = AsyncMock(return_value={"version": "0.1.9"})
71+
worker = Worker(mock_client, task_queue="q1", workflows=[TestWorkflow], activities=[])
72+
await worker._register()
73+
mock_client.register_worker.assert_awaited_once()
74+
75+
@pytest.mark.asyncio
76+
async def test_register_accepts_server_major_2(self, mock_client: AsyncMock) -> None:
77+
mock_client.get_cluster_info = AsyncMock(return_value={"version": "2.3.4"})
78+
worker = Worker(mock_client, task_queue="q1", workflows=[TestWorkflow], activities=[])
79+
await worker._register()
80+
mock_client.register_worker.assert_awaited_once()
81+
82+
@pytest.mark.asyncio
83+
async def test_register_rejects_incompatible_major_1(self, mock_client: AsyncMock) -> None:
84+
mock_client.get_cluster_info = AsyncMock(return_value={"version": "1.4.0"})
85+
worker = Worker(mock_client, task_queue="q1", workflows=[TestWorkflow], activities=[])
86+
with pytest.raises(RuntimeError, match="incompatible"):
87+
await worker._register()
88+
mock_client.register_worker.assert_not_called()
89+
90+
@pytest.mark.asyncio
91+
async def test_register_rejects_incompatible_major_3(self, mock_client: AsyncMock) -> None:
92+
mock_client.get_cluster_info = AsyncMock(return_value={"version": "3.0.0"})
93+
worker = Worker(mock_client, task_queue="q1", workflows=[TestWorkflow], activities=[])
94+
with pytest.raises(RuntimeError, match="incompatible"):
95+
await worker._register()
96+
mock_client.register_worker.assert_not_called()
97+
98+
@pytest.mark.asyncio
99+
async def test_register_skips_check_on_unparseable_version(self, mock_client: AsyncMock) -> None:
100+
mock_client.get_cluster_info = AsyncMock(return_value={"version": "unknown"})
101+
worker = Worker(mock_client, task_queue="q1", workflows=[TestWorkflow], activities=[])
102+
await worker._register()
103+
mock_client.register_worker.assert_awaited_once()
104+
105+
@pytest.mark.asyncio
106+
async def test_register_continues_when_cluster_info_fails(self, mock_client: AsyncMock) -> None:
107+
mock_client.get_cluster_info = AsyncMock(side_effect=RuntimeError("network down"))
108+
worker = Worker(mock_client, task_queue="q1", workflows=[TestWorkflow], activities=[])
109+
await worker._register()
110+
mock_client.register_worker.assert_awaited_once()
111+
61112

62113
class TestWorkflowTaskExecution:
63114
@pytest.mark.asyncio

0 commit comments

Comments
 (0)