Skip to content

Commit 8d7af58

Browse files
committed
add unit test
1 parent 668f7c3 commit 8d7af58

File tree

3 files changed

+861
-0
lines changed

3 files changed

+861
-0
lines changed
Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
#!/usr/bin/env python3
2+
"""
3+
Unit tests for BaseTaskHandler class.
4+
"""
5+
6+
import pytest
7+
from unittest.mock import Mock, AsyncMock, patch
8+
from typing import Any
9+
10+
from cadence.worker._base_task_handler import BaseTaskHandler
11+
12+
13+
class ConcreteTaskHandler(BaseTaskHandler[str]):
14+
"""Concrete implementation of BaseTaskHandler for testing."""
15+
16+
def __init__(self, client, task_list: str, identity: str, **options):
17+
super().__init__(client, task_list, identity, **options)
18+
self._handle_task_implementation_called = False
19+
self._handle_task_failure_called = False
20+
self._propagate_context_called = False
21+
self._unset_current_context_called = False
22+
self._last_task: str = ""
23+
self._last_error: Exception | None = None
24+
25+
async def _handle_task_implementation(self, task: str) -> None:
26+
"""Test implementation of task handling."""
27+
self._handle_task_implementation_called = True
28+
self._last_task = task
29+
if task == "raise_error":
30+
raise ValueError("Test error")
31+
32+
async def handle_task_failure(self, task: str, error: Exception) -> None:
33+
"""Test implementation of task failure handling."""
34+
self._handle_task_failure_called = True
35+
self._last_task = task
36+
self._last_error = error
37+
38+
async def _propagate_context(self, task: str) -> None:
39+
"""Test implementation of context propagation."""
40+
self._propagate_context_called = True
41+
self._last_task = task
42+
43+
async def _unset_current_context(self) -> None:
44+
"""Test implementation of context cleanup."""
45+
self._unset_current_context_called = True
46+
47+
48+
class TestBaseTaskHandler:
49+
"""Test cases for BaseTaskHandler."""
50+
51+
def test_initialization(self):
52+
"""Test BaseTaskHandler initialization."""
53+
client = Mock()
54+
handler = ConcreteTaskHandler(
55+
client=client,
56+
task_list="test_task_list",
57+
identity="test_identity",
58+
option1="value1",
59+
option2="value2"
60+
)
61+
62+
assert handler._client == client
63+
assert handler._task_list == "test_task_list"
64+
assert handler._identity == "test_identity"
65+
assert handler._options == {"option1": "value1", "option2": "value2"}
66+
67+
@pytest.mark.asyncio
68+
async def test_handle_task_success(self):
69+
"""Test successful task handling."""
70+
client = Mock()
71+
handler = ConcreteTaskHandler(client, "test_task_list", "test_identity")
72+
73+
await handler.handle_task("test_task")
74+
75+
# Verify all methods were called in correct order
76+
assert handler._propagate_context_called
77+
assert handler._handle_task_implementation_called
78+
assert handler._unset_current_context_called
79+
assert not handler._handle_task_failure_called
80+
assert handler._last_task == "test_task"
81+
assert handler._last_error is None
82+
83+
@pytest.mark.asyncio
84+
async def test_handle_task_failure(self):
85+
"""Test task handling with error."""
86+
client = Mock()
87+
handler = ConcreteTaskHandler(client, "test_task_list", "test_identity")
88+
89+
await handler.handle_task("raise_error")
90+
91+
# Verify error handling was called
92+
assert handler._propagate_context_called
93+
assert handler._handle_task_implementation_called
94+
assert handler._handle_task_failure_called
95+
assert handler._unset_current_context_called
96+
assert handler._last_task == "raise_error"
97+
assert isinstance(handler._last_error, ValueError)
98+
assert str(handler._last_error) == "Test error"
99+
100+
@pytest.mark.asyncio
101+
async def test_handle_task_with_context_propagation_error(self):
102+
"""Test task handling when context propagation fails."""
103+
client = Mock()
104+
handler = ConcreteTaskHandler(client, "test_task_list", "test_identity")
105+
106+
# Override _propagate_context to raise an error
107+
async def failing_propagate_context(task):
108+
raise RuntimeError("Context propagation failed")
109+
110+
# Use setattr to avoid mypy error about method assignment
111+
setattr(handler, '_propagate_context', failing_propagate_context)
112+
113+
await handler.handle_task("test_task")
114+
115+
# Verify error handling was called
116+
assert handler._handle_task_failure_called
117+
assert handler._unset_current_context_called
118+
assert isinstance(handler._last_error, RuntimeError)
119+
assert str(handler._last_error) == "Context propagation failed"
120+
121+
@pytest.mark.asyncio
122+
async def test_handle_task_with_cleanup_error(self):
123+
"""Test task handling when cleanup fails."""
124+
client = Mock()
125+
handler = ConcreteTaskHandler(client, "test_task_list", "test_identity")
126+
127+
# Override _unset_current_context to raise an error
128+
async def failing_unset_context():
129+
raise RuntimeError("Cleanup failed")
130+
131+
# Use setattr to avoid mypy error about method assignment
132+
setattr(handler, '_unset_current_context', failing_unset_context)
133+
134+
# Cleanup errors in finally block will propagate
135+
with pytest.raises(RuntimeError, match="Cleanup failed"):
136+
await handler.handle_task("test_task")
137+
138+
@pytest.mark.asyncio
139+
async def test_handle_task_with_implementation_and_cleanup_errors(self):
140+
"""Test task handling when both implementation and cleanup fail."""
141+
client = Mock()
142+
handler = ConcreteTaskHandler(client, "test_task_list", "test_identity")
143+
144+
# Override _unset_current_context to raise an error
145+
async def failing_unset_context():
146+
raise RuntimeError("Cleanup failed")
147+
148+
# Use setattr to avoid mypy error about method assignment
149+
setattr(handler, '_unset_current_context', failing_unset_context)
150+
151+
# The implementation error should be handled, but cleanup error will propagate
152+
with pytest.raises(RuntimeError, match="Cleanup failed"):
153+
await handler.handle_task("raise_error")
154+
155+
# Verify the implementation error was handled before cleanup error
156+
assert handler._handle_task_failure_called
157+
assert isinstance(handler._last_error, ValueError)
158+
159+
@pytest.mark.asyncio
160+
async def test_abstract_methods_not_implemented(self):
161+
"""Test that abstract methods raise NotImplementedError when not implemented."""
162+
client = Mock()
163+
164+
class IncompleteHandler(BaseTaskHandler[str]):
165+
async def _handle_task_implementation(self, task: str) -> None:
166+
raise NotImplementedError()
167+
168+
async def handle_task_failure(self, task: str, error: Exception) -> None:
169+
raise NotImplementedError()
170+
171+
handler = IncompleteHandler(client, "test_task_list", "test_identity")
172+
173+
with pytest.raises(NotImplementedError):
174+
await handler._handle_task_implementation("test")
175+
176+
with pytest.raises(NotImplementedError):
177+
await handler.handle_task_failure("test", Exception("test"))
178+
179+
@pytest.mark.asyncio
180+
async def test_default_context_methods(self):
181+
"""Test default implementations of context methods."""
182+
client = Mock()
183+
handler = ConcreteTaskHandler(client, "test_task_list", "test_identity")
184+
185+
# Test default _propagate_context (should not raise)
186+
await handler._propagate_context("test_task")
187+
188+
# Test default _unset_current_context (should not raise)
189+
await handler._unset_current_context()
190+
191+
@pytest.mark.asyncio
192+
async def test_generic_type_parameter(self):
193+
"""Test that the generic type parameter works correctly."""
194+
client = Mock()
195+
196+
class IntHandler(BaseTaskHandler[int]):
197+
async def _handle_task_implementation(self, task: int) -> None:
198+
pass
199+
200+
async def handle_task_failure(self, task: int, error: Exception) -> None:
201+
pass
202+
203+
handler = IntHandler(client, "test_task_list", "test_identity")
204+
205+
# Should accept int tasks
206+
await handler.handle_task(42)
207+
208+
# Type checker should catch type mismatches (this is more of a static analysis test)
209+
# In runtime, Python won't enforce the type, but the type hints are there for static analysis

0 commit comments

Comments
 (0)