Skip to content

Commit 770eb4e

Browse files
committed
test: add tests for trigger_cron timezone functionality
Add unit tests for create_next_triggers function to improve coverage: - Test timezone handling (America/New_York, UTC, Europe/London) - Test None timezone defaults to UTC - Test DuplicateKeyError handling - Test datetime object validation - Test multiple trigger creation These tests cover the new timezone-aware trigger scheduling logic. Signed-off-by: Sparsh <sparsh.raj30@gmail.com>
1 parent 93071da commit 770eb4e

2 files changed

Lines changed: 188 additions & 0 deletions

File tree

state-manager/tests/unit/tasks/test_create_crons_coverage.py renamed to state-manager/tests/unit/tasks/test_create_crons.py

File renamed without changes.
Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
"""
2+
Tests for trigger_cron functions to improve code coverage.
3+
These are pure unit tests that mock database operations.
4+
Environment variables are provided by CI (see .github/workflows/test-state-manager.yml).
5+
"""
6+
import pytest
7+
from unittest.mock import MagicMock, AsyncMock, patch
8+
from datetime import datetime
9+
10+
from app.tasks.trigger_cron import create_next_triggers
11+
12+
13+
@pytest.mark.asyncio
14+
async def test_create_next_triggers_with_america_new_york_timezone():
15+
"""Test create_next_triggers processes America/New_York timezone correctly"""
16+
trigger = MagicMock()
17+
trigger.expression = "0 9 * * *"
18+
trigger.timezone = "America/New_York"
19+
trigger.trigger_time = datetime(2025, 10, 4, 13, 0, 0) # Naive UTC time
20+
trigger.graph_name = "test_graph"
21+
trigger.namespace = "test_namespace"
22+
23+
cron_time = datetime(2025, 10, 6, 0, 0, 0)
24+
25+
with patch('app.tasks.trigger_cron.DatabaseTriggers') as mock_db_class:
26+
mock_instance = MagicMock()
27+
mock_instance.insert = AsyncMock()
28+
mock_db_class.return_value = mock_instance
29+
30+
await create_next_triggers(trigger, cron_time)
31+
32+
# Verify DatabaseTriggers was instantiated with timezone
33+
assert mock_db_class.called
34+
call_kwargs = mock_db_class.call_args[1]
35+
assert call_kwargs['timezone'] == "America/New_York"
36+
assert call_kwargs['expression'] == "0 9 * * *"
37+
38+
39+
@pytest.mark.asyncio
40+
async def test_create_next_triggers_with_utc_timezone():
41+
"""Test create_next_triggers with UTC timezone"""
42+
trigger = MagicMock()
43+
trigger.expression = "0 9 * * *"
44+
trigger.timezone = "UTC"
45+
trigger.trigger_time = datetime(2025, 10, 4, 9, 0, 0)
46+
trigger.graph_name = "test_graph"
47+
trigger.namespace = "test_namespace"
48+
49+
cron_time = datetime(2025, 10, 6, 0, 0, 0)
50+
51+
with patch('app.tasks.trigger_cron.DatabaseTriggers') as mock_db_class:
52+
mock_instance = MagicMock()
53+
mock_instance.insert = AsyncMock()
54+
mock_db_class.return_value = mock_instance
55+
56+
await create_next_triggers(trigger, cron_time)
57+
58+
# Verify timezone was passed correctly
59+
call_kwargs = mock_db_class.call_args[1]
60+
assert call_kwargs['timezone'] == "UTC"
61+
62+
63+
@pytest.mark.asyncio
64+
async def test_create_next_triggers_with_none_timezone_defaults_to_utc():
65+
"""Test create_next_triggers with None timezone defaults to UTC"""
66+
trigger = MagicMock()
67+
trigger.expression = "0 9 * * *"
68+
trigger.timezone = None
69+
trigger.trigger_time = datetime(2025, 10, 4, 9, 0, 0)
70+
trigger.graph_name = "test_graph"
71+
trigger.namespace = "test_namespace"
72+
73+
cron_time = datetime(2025, 10, 6, 0, 0, 0)
74+
75+
with patch('app.tasks.trigger_cron.DatabaseTriggers') as mock_db_class:
76+
mock_instance = MagicMock()
77+
mock_instance.insert = AsyncMock()
78+
mock_db_class.return_value = mock_instance
79+
80+
await create_next_triggers(trigger, cron_time)
81+
82+
# Verify None timezone is passed through (will default to UTC in ZoneInfo call)
83+
call_kwargs = mock_db_class.call_args[1]
84+
assert call_kwargs['timezone'] is None
85+
86+
87+
@pytest.mark.asyncio
88+
async def test_create_next_triggers_with_europe_london_timezone():
89+
"""Test create_next_triggers with Europe/London timezone"""
90+
trigger = MagicMock()
91+
trigger.expression = "0 17 * * *"
92+
trigger.timezone = "Europe/London"
93+
trigger.trigger_time = datetime(2025, 10, 4, 16, 0, 0) # UTC time
94+
trigger.graph_name = "test_graph"
95+
trigger.namespace = "test_namespace"
96+
97+
cron_time = datetime(2025, 10, 6, 0, 0, 0)
98+
99+
with patch('app.tasks.trigger_cron.DatabaseTriggers') as mock_db_class:
100+
mock_instance = MagicMock()
101+
mock_instance.insert = AsyncMock()
102+
mock_db_class.return_value = mock_instance
103+
104+
await create_next_triggers(trigger, cron_time)
105+
106+
# Verify Europe/London timezone was used
107+
call_kwargs = mock_db_class.call_args[1]
108+
assert call_kwargs['timezone'] == "Europe/London"
109+
110+
111+
@pytest.mark.asyncio
112+
async def test_create_next_triggers_handles_duplicate_key_error():
113+
"""Test create_next_triggers handles DuplicateKeyError gracefully"""
114+
from pymongo.errors import DuplicateKeyError
115+
116+
trigger = MagicMock()
117+
trigger.expression = "0 9 * * *"
118+
trigger.timezone = "America/New_York"
119+
trigger.trigger_time = datetime(2025, 10, 4, 13, 0, 0)
120+
trigger.graph_name = "test_graph"
121+
trigger.namespace = "test_namespace"
122+
123+
cron_time = datetime(2025, 10, 6, 0, 0, 0)
124+
125+
with patch('app.tasks.trigger_cron.DatabaseTriggers') as mock_db_class:
126+
mock_instance = MagicMock()
127+
# First call raises DuplicateKeyError, second succeeds
128+
mock_instance.insert = AsyncMock(side_effect=[
129+
DuplicateKeyError("Duplicate"),
130+
None
131+
])
132+
mock_db_class.return_value = mock_instance
133+
134+
with patch('app.tasks.trigger_cron.logger') as mock_logger:
135+
# Should not raise exception
136+
await create_next_triggers(trigger, cron_time)
137+
138+
# Verify error was logged
139+
assert mock_logger.error.called
140+
error_msg = mock_logger.error.call_args[0][0]
141+
assert "Duplicate trigger found" in error_msg
142+
143+
144+
@pytest.mark.asyncio
145+
async def test_create_next_triggers_trigger_time_is_datetime():
146+
"""Test that next trigger_time is a datetime object"""
147+
trigger = MagicMock()
148+
trigger.expression = "0 9 * * *"
149+
trigger.timezone = "America/New_York"
150+
trigger.trigger_time = datetime(2025, 10, 4, 13, 0, 0)
151+
trigger.graph_name = "test_graph"
152+
trigger.namespace = "test_namespace"
153+
154+
cron_time = datetime(2025, 10, 6, 0, 0, 0)
155+
156+
with patch('app.tasks.trigger_cron.DatabaseTriggers') as mock_db_class:
157+
mock_instance = MagicMock()
158+
mock_instance.insert = AsyncMock()
159+
mock_db_class.return_value = mock_instance
160+
161+
await create_next_triggers(trigger, cron_time)
162+
163+
# Verify trigger_time is a datetime
164+
call_kwargs = mock_db_class.call_args[1]
165+
assert isinstance(call_kwargs['trigger_time'], datetime)
166+
167+
168+
@pytest.mark.asyncio
169+
async def test_create_next_triggers_creates_multiple_triggers():
170+
"""Test create_next_triggers creates multiple future triggers"""
171+
trigger = MagicMock()
172+
trigger.expression = "0 */6 * * *" # Every 6 hours
173+
trigger.timezone = "UTC"
174+
trigger.trigger_time = datetime(2025, 10, 4, 0, 0, 0)
175+
trigger.graph_name = "test_graph"
176+
trigger.namespace = "test_namespace"
177+
178+
cron_time = datetime(2025, 10, 5, 0, 0, 0) # 24 hours later
179+
180+
with patch('app.tasks.trigger_cron.DatabaseTriggers') as mock_db_class:
181+
mock_instance = MagicMock()
182+
mock_instance.insert = AsyncMock()
183+
mock_db_class.return_value = mock_instance
184+
185+
await create_next_triggers(trigger, cron_time)
186+
187+
# Should create multiple triggers (every 6 hours until past cron_time)
188+
assert mock_db_class.call_count >= 4 # At least 4 triggers in 24 hours

0 commit comments

Comments
 (0)