Skip to content

Commit 2cc14e8

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 <[email protected]>
1 parent 93071da commit 2cc14e8

2 files changed

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

0 commit comments

Comments
 (0)