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