Skip to content

Commit 93071da

Browse files
committed
test: add comprehensive tests for timezone functionality
Add unit tests to improve code coverage for timezone support: - Tests for CronTrigger model timezone validation - Tests for create_crons function with various timezone scenarios - Tests for timezone deduplication logic - Tests for default UTC timezone behavior All tests use mocks to avoid database dependencies and rely on environment variables set by CI workflow. Signed-off-by: Sparsh <sparsh.raj30@gmail.com>
1 parent db9e0a0 commit 93071da

1 file changed

Lines changed: 236 additions & 0 deletions

File tree

Lines changed: 236 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,236 @@
1+
"""
2+
Tests for create_crons function to improve code coverage.
3+
These are pure unit tests that mock DatabaseTriggers to avoid database dependency.
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+
# Environment variables are set by CI workflow
11+
# For local testing, these should be set before running tests
12+
# See: .github/workflows/test-state-manager.yml
13+
14+
from app.tasks.verify_graph import create_crons
15+
from app.models.trigger_models import Trigger, TriggerTypeEnum
16+
17+
18+
@pytest.mark.asyncio
19+
async def test_create_crons_with_america_new_york_timezone():
20+
"""Test create_crons processes America/New_York timezone correctly"""
21+
graph_template = MagicMock()
22+
graph_template.name = "test_graph"
23+
graph_template.namespace = "test_ns"
24+
graph_template.triggers = [
25+
Trigger(
26+
type=TriggerTypeEnum.CRON,
27+
value={"expression": "0 9 * * *", "timezone": "America/New_York"}
28+
)
29+
]
30+
31+
# Mock DatabaseTriggers class and insert_many method
32+
with patch('app.tasks.verify_graph.DatabaseTriggers') as mock_db_class:
33+
# Mock instances that will be created
34+
mock_instance = MagicMock()
35+
mock_db_class.return_value = mock_instance
36+
mock_db_class.insert_many = AsyncMock()
37+
38+
await create_crons(graph_template)
39+
40+
# Verify DatabaseTriggers was instantiated with correct parameters
41+
assert mock_db_class.called
42+
call_kwargs = mock_db_class.call_args[1]
43+
assert call_kwargs['timezone'] == "America/New_York"
44+
assert call_kwargs['expression'] == "0 9 * * *"
45+
assert call_kwargs['graph_name'] == "test_graph"
46+
assert call_kwargs['namespace'] == "test_ns"
47+
48+
# Verify insert_many was called with the list of triggers
49+
assert mock_db_class.insert_many.called
50+
51+
52+
@pytest.mark.asyncio
53+
async def test_create_crons_with_default_utc_timezone():
54+
"""Test create_crons uses UTC as default when timezone not specified"""
55+
graph_template = MagicMock()
56+
graph_template.name = "test_graph"
57+
graph_template.namespace = "test_ns"
58+
graph_template.triggers = [
59+
Trigger(
60+
type=TriggerTypeEnum.CRON,
61+
value={"expression": "0 9 * * *"} # No timezone specified
62+
)
63+
]
64+
65+
with patch('app.tasks.verify_graph.DatabaseTriggers') as mock_db_class:
66+
mock_db_class.return_value = MagicMock()
67+
mock_db_class.insert_many = AsyncMock()
68+
69+
await create_crons(graph_template)
70+
71+
# Verify default UTC timezone was used
72+
call_kwargs = mock_db_class.call_args[1]
73+
assert call_kwargs['timezone'] == "UTC"
74+
assert mock_db_class.insert_many.called
75+
76+
77+
@pytest.mark.asyncio
78+
async def test_create_crons_with_europe_london_timezone():
79+
"""Test create_crons handles Europe/London timezone"""
80+
graph_template = MagicMock()
81+
graph_template.name = "test_graph"
82+
graph_template.namespace = "test_ns"
83+
graph_template.triggers = [
84+
Trigger(
85+
type=TriggerTypeEnum.CRON,
86+
value={"expression": "0 17 * * *", "timezone": "Europe/London"}
87+
)
88+
]
89+
90+
with patch('app.tasks.verify_graph.DatabaseTriggers') as mock_db_class:
91+
mock_db_class.return_value = MagicMock()
92+
mock_db_class.insert_many = AsyncMock()
93+
94+
await create_crons(graph_template)
95+
96+
call_kwargs = mock_db_class.call_args[1]
97+
assert call_kwargs['timezone'] == "Europe/London"
98+
assert call_kwargs['expression'] == "0 17 * * *"
99+
assert mock_db_class.insert_many.called
100+
101+
102+
@pytest.mark.asyncio
103+
async def test_create_crons_with_multiple_different_timezones():
104+
"""Test create_crons handles multiple triggers with different timezones"""
105+
graph_template = MagicMock()
106+
graph_template.name = "test_graph"
107+
graph_template.namespace = "test_ns"
108+
graph_template.triggers = [
109+
Trigger(
110+
type=TriggerTypeEnum.CRON,
111+
value={"expression": "0 9 * * *", "timezone": "America/New_York"}
112+
),
113+
Trigger(
114+
type=TriggerTypeEnum.CRON,
115+
value={"expression": "0 17 * * *", "timezone": "Europe/London"}
116+
)
117+
]
118+
119+
with patch('app.tasks.verify_graph.DatabaseTriggers') as mock_db_class:
120+
mock_db_class.return_value = MagicMock()
121+
mock_db_class.insert_many = AsyncMock()
122+
123+
await create_crons(graph_template)
124+
125+
# Should be called twice (one for each trigger)
126+
assert mock_db_class.call_count == 2
127+
128+
# Verify insert_many was called once with list of 2 triggers
129+
assert mock_db_class.insert_many.call_count == 1
130+
insert_call_args = mock_db_class.insert_many.call_args[0][0]
131+
assert len(insert_call_args) == 2
132+
133+
134+
@pytest.mark.asyncio
135+
async def test_create_crons_skips_insert_when_no_triggers():
136+
"""Test create_crons doesn't call insert_many when no CRON triggers exist"""
137+
graph_template = MagicMock()
138+
graph_template.name = "test_graph"
139+
graph_template.namespace = "test_ns"
140+
graph_template.triggers = []
141+
142+
with patch('app.tasks.verify_graph.DatabaseTriggers') as mock_db_class:
143+
mock_db_class.insert_many = AsyncMock()
144+
145+
await create_crons(graph_template)
146+
147+
# Verify insert_many was NOT called (no triggers to insert)
148+
assert not mock_db_class.insert_many.called
149+
# DatabaseTriggers should not have been instantiated
150+
assert mock_db_class.call_count == 0
151+
152+
153+
@pytest.mark.asyncio
154+
async def test_create_crons_deduplicates_same_expression_and_timezone():
155+
"""Test create_crons deduplicates triggers with same expression and timezone"""
156+
graph_template = MagicMock()
157+
graph_template.name = "test_graph"
158+
graph_template.namespace = "test_ns"
159+
graph_template.triggers = [
160+
Trigger(
161+
type=TriggerTypeEnum.CRON,
162+
value={"expression": "0 9 * * *", "timezone": "America/New_York"}
163+
),
164+
Trigger(
165+
type=TriggerTypeEnum.CRON,
166+
value={"expression": "0 9 * * *", "timezone": "America/New_York"}
167+
)
168+
]
169+
170+
with patch('app.tasks.verify_graph.DatabaseTriggers') as mock_db_class:
171+
mock_db_class.return_value = MagicMock()
172+
mock_db_class.insert_many = AsyncMock()
173+
174+
await create_crons(graph_template)
175+
176+
# Should only create one DatabaseTriggers instance (deduplicated)
177+
assert mock_db_class.call_count == 1
178+
179+
# insert_many should be called with list of 1
180+
insert_call_args = mock_db_class.insert_many.call_args[0][0]
181+
assert len(insert_call_args) == 1
182+
183+
184+
@pytest.mark.asyncio
185+
async def test_create_crons_keeps_same_expression_different_timezones():
186+
"""Test create_crons keeps triggers with same expression but different timezones"""
187+
graph_template = MagicMock()
188+
graph_template.name = "test_graph"
189+
graph_template.namespace = "test_ns"
190+
graph_template.triggers = [
191+
Trigger(
192+
type=TriggerTypeEnum.CRON,
193+
value={"expression": "0 9 * * *", "timezone": "America/New_York"}
194+
),
195+
Trigger(
196+
type=TriggerTypeEnum.CRON,
197+
value={"expression": "0 9 * * *", "timezone": "Europe/London"}
198+
)
199+
]
200+
201+
with patch('app.tasks.verify_graph.DatabaseTriggers') as mock_db_class:
202+
mock_db_class.return_value = MagicMock()
203+
mock_db_class.insert_many = AsyncMock()
204+
205+
await create_crons(graph_template)
206+
207+
# Should create two DatabaseTriggers instances (different timezones)
208+
assert mock_db_class.call_count == 2
209+
210+
# insert_many should be called with list of 2
211+
insert_call_args = mock_db_class.insert_many.call_args[0][0]
212+
assert len(insert_call_args) == 2
213+
214+
215+
@pytest.mark.asyncio
216+
async def test_create_crons_trigger_time_is_datetime():
217+
"""Test that trigger_time is set to a datetime object"""
218+
graph_template = MagicMock()
219+
graph_template.name = "test_graph"
220+
graph_template.namespace = "test_ns"
221+
graph_template.triggers = [
222+
Trigger(
223+
type=TriggerTypeEnum.CRON,
224+
value={"expression": "0 9 * * *", "timezone": "America/New_York"}
225+
)
226+
]
227+
228+
with patch('app.tasks.verify_graph.DatabaseTriggers') as mock_db_class:
229+
mock_db_class.return_value = MagicMock()
230+
mock_db_class.insert_many = AsyncMock()
231+
232+
await create_crons(graph_template)
233+
234+
# Verify trigger_time is a datetime object
235+
call_kwargs = mock_db_class.call_args[1]
236+
assert isinstance(call_kwargs['trigger_time'], datetime)

0 commit comments

Comments
 (0)