Skip to content

Commit 7ed39b9

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 <[email protected]>
1 parent db9e0a0 commit 7ed39b9

1 file changed

Lines changed: 237 additions & 0 deletions

File tree

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

0 commit comments

Comments
 (0)