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