@@ -126,3 +126,287 @@ async def test_trigger_graph_value_error_not_graph_template_not_found(mock_reque
126126
127127 with pytest .raises (ValueError , match = "Some other validation error" ):
128128 await trigger_graph (namespace_name , graph_name , mock_request , x_exosphere_request_id )
129+
130+
131+ @pytest .mark .asyncio
132+ async def test_trigger_graph_with_dependent_strings ():
133+ """Test trigger_graph with dependent strings in inputs"""
134+ namespace_name = "test_namespace"
135+ graph_name = "test_graph"
136+ x_exosphere_request_id = "test_request_id"
137+
138+ req = TriggerGraphRequestModel (
139+ store = {"store_key" : "store_value" },
140+ inputs = {"input1" : "{{store.store_key}}_suffix" }
141+ )
142+
143+ with patch ('app.controller.trigger_graph.GraphTemplate' ) as mock_graph_template_cls , \
144+ patch ('app.controller.trigger_graph.Store' ) as mock_store_cls , \
145+ patch ('app.controller.trigger_graph.State' ) as mock_state_cls , \
146+ patch ('app.controller.trigger_graph.Run' ) as mock_run_cls , \
147+ patch ('app.controller.trigger_graph.DependentString' ) as mock_dependent_string_cls :
148+
149+ mock_graph_template = MagicMock ()
150+ mock_graph_template .is_valid .return_value = True
151+ mock_graph_template .store_config .required_keys = []
152+ mock_root_node = MagicMock ()
153+ mock_root_node .node_name = "root_node"
154+ mock_root_node .identifier = "root_id"
155+ mock_root_node .inputs = {"input1" : "{{store.store_key}}_suffix" }
156+ mock_graph_template .get_root_node .return_value = mock_root_node
157+ mock_graph_template_cls .get = AsyncMock (return_value = mock_graph_template )
158+
159+ # Mock dependent string behavior
160+ mock_dependent_string = MagicMock ()
161+ mock_dependent = MagicMock ()
162+ mock_dependent .identifier = "store"
163+ mock_dependent .field = "store_key"
164+ mock_dependent_string .dependents = {0 : mock_dependent }
165+ mock_dependent_string .generate_string .return_value = "store_value_suffix"
166+ mock_dependent_string_cls .create_dependent_string .return_value = mock_dependent_string
167+
168+ mock_store_cls .insert_many = AsyncMock (return_value = None )
169+ mock_state_instance = MagicMock ()
170+ mock_state_instance .insert = AsyncMock (return_value = None )
171+ mock_state_cls .return_value = mock_state_instance
172+
173+ mock_run_instance = MagicMock ()
174+ mock_run_instance .insert = AsyncMock (return_value = None )
175+ mock_run_cls .return_value = mock_run_instance
176+
177+ result = await trigger_graph (namespace_name , graph_name , req , x_exosphere_request_id )
178+
179+ assert result .status == StateStatusEnum .CREATED
180+ mock_dependent_string_cls .create_dependent_string .assert_called ()
181+
182+
183+ @pytest .mark .asyncio
184+ async def test_trigger_graph_with_invalid_dependent_identifier ():
185+ """Test trigger_graph with invalid dependent identifier (not 'store')"""
186+ namespace_name = "test_namespace"
187+ graph_name = "test_graph"
188+ x_exosphere_request_id = "test_request_id"
189+
190+ req = TriggerGraphRequestModel (
191+ store = {"store_key" : "store_value" },
192+ inputs = {"input1" : "{{invalid.identifier}}" }
193+ )
194+
195+ with patch ('app.controller.trigger_graph.GraphTemplate' ) as mock_graph_template_cls , \
196+ patch ('app.controller.trigger_graph.DependentString' ) as mock_dependent_string_cls :
197+
198+ mock_graph_template = MagicMock ()
199+ mock_graph_template .is_valid .return_value = True
200+ mock_graph_template .store_config .required_keys = []
201+ mock_root_node = MagicMock ()
202+ mock_root_node .node_name = "root_node"
203+ mock_root_node .identifier = "root_id"
204+ mock_root_node .inputs = {"input1" : "{{invalid.identifier}}" }
205+ mock_graph_template .get_root_node .return_value = mock_root_node
206+ mock_graph_template_cls .get = AsyncMock (return_value = mock_graph_template )
207+
208+ # Mock dependent string behavior with invalid identifier
209+ mock_dependent_string = MagicMock ()
210+ mock_dependent = MagicMock ()
211+ mock_dependent .identifier = "invalid"
212+ mock_dependent .field = "identifier"
213+ mock_dependent_string .dependents = {0 : mock_dependent }
214+ mock_dependent_string_cls .create_dependent_string .return_value = mock_dependent_string
215+
216+ with pytest .raises (HTTPException ) as exc_info :
217+ await trigger_graph (namespace_name , graph_name , req , x_exosphere_request_id )
218+
219+ assert exc_info .value .status_code == 400
220+ assert "Root node can have only store identifier as dependent" in exc_info .value .detail
221+
222+
223+ @pytest .mark .asyncio
224+ async def test_trigger_graph_with_missing_store_field ():
225+ """Test trigger_graph with missing store field in dependent string"""
226+ namespace_name = "test_namespace"
227+ graph_name = "test_graph"
228+ x_exosphere_request_id = "test_request_id"
229+
230+ req = TriggerGraphRequestModel (
231+ store = {"other_key" : "other_value" },
232+ inputs = {"input1" : "{{store.missing_key}}" }
233+ )
234+
235+ with patch ('app.controller.trigger_graph.GraphTemplate' ) as mock_graph_template_cls , \
236+ patch ('app.controller.trigger_graph.DependentString' ) as mock_dependent_string_cls :
237+
238+ mock_graph_template = MagicMock ()
239+ mock_graph_template .is_valid .return_value = True
240+ mock_graph_template .store_config .required_keys = []
241+ mock_graph_template .store_config .default_values = {}
242+ mock_root_node = MagicMock ()
243+ mock_root_node .node_name = "root_node"
244+ mock_root_node .identifier = "root_id"
245+ mock_root_node .inputs = {"input1" : "{{store.missing_key}}" }
246+ mock_graph_template .get_root_node .return_value = mock_root_node
247+ mock_graph_template_cls .get = AsyncMock (return_value = mock_graph_template )
248+
249+ # Mock dependent string behavior with missing store field
250+ mock_dependent_string = MagicMock ()
251+ mock_dependent = MagicMock ()
252+ mock_dependent .identifier = "store"
253+ mock_dependent .field = "missing_key"
254+ mock_dependent_string .dependents = {0 : mock_dependent }
255+ mock_dependent_string_cls .create_dependent_string .return_value = mock_dependent_string
256+
257+ with pytest .raises (HTTPException ) as exc_info :
258+ await trigger_graph (namespace_name , graph_name , req , x_exosphere_request_id )
259+
260+ assert exc_info .value .status_code == 400
261+ assert "Dependent missing_key not found in store" in exc_info .value .detail
262+
263+
264+ @pytest .mark .asyncio
265+ async def test_trigger_graph_with_store_default_values ():
266+ """Test trigger_graph with store default values"""
267+ namespace_name = "test_namespace"
268+ graph_name = "test_graph"
269+ x_exosphere_request_id = "test_request_id"
270+
271+ req = TriggerGraphRequestModel (
272+ store = {"other_key" : "other_value" },
273+ inputs = {"input1" : "{{store.missing_key}}" }
274+ )
275+
276+ with patch ('app.controller.trigger_graph.GraphTemplate' ) as mock_graph_template_cls , \
277+ patch ('app.controller.trigger_graph.Store' ) as mock_store_cls , \
278+ patch ('app.controller.trigger_graph.State' ) as mock_state_cls , \
279+ patch ('app.controller.trigger_graph.Run' ) as mock_run_cls , \
280+ patch ('app.controller.trigger_graph.DependentString' ) as mock_dependent_string_cls :
281+
282+ mock_graph_template = MagicMock ()
283+ mock_graph_template .is_valid .return_value = True
284+ mock_graph_template .store_config .required_keys = []
285+ mock_graph_template .store_config .default_values = {"missing_key" : "default_value" }
286+ mock_root_node = MagicMock ()
287+ mock_root_node .node_name = "root_node"
288+ mock_root_node .identifier = "root_id"
289+ mock_root_node .inputs = {"input1" : "{{store.missing_key}}" }
290+ mock_graph_template .get_root_node .return_value = mock_root_node
291+ mock_graph_template_cls .get = AsyncMock (return_value = mock_graph_template )
292+
293+ # Mock dependent string behavior with default value
294+ mock_dependent_string = MagicMock ()
295+ mock_dependent = MagicMock ()
296+ mock_dependent .identifier = "store"
297+ mock_dependent .field = "missing_key"
298+ mock_dependent_string .dependents = {0 : mock_dependent }
299+ mock_dependent_string .generate_string .return_value = "default_value"
300+ mock_dependent_string_cls .create_dependent_string .return_value = mock_dependent_string
301+
302+ mock_store_cls .insert_many = AsyncMock (return_value = None )
303+ mock_state_instance = MagicMock ()
304+ mock_state_instance .insert = AsyncMock (return_value = None )
305+ mock_state_cls .return_value = mock_state_instance
306+
307+ mock_run_instance = MagicMock ()
308+ mock_run_instance .insert = AsyncMock (return_value = None )
309+ mock_run_cls .return_value = mock_run_instance
310+
311+ result = await trigger_graph (namespace_name , graph_name , req , x_exosphere_request_id )
312+
313+ assert result .status == StateStatusEnum .CREATED
314+ mock_dependent_string .set_value .assert_called_with ("store" , "missing_key" , "default_value" )
315+
316+
317+ @pytest .mark .asyncio
318+ async def test_trigger_graph_with_input_processing_error ():
319+ """Test trigger_graph with error during input processing"""
320+ namespace_name = "test_namespace"
321+ graph_name = "test_graph"
322+ x_exosphere_request_id = "test_request_id"
323+
324+ req = TriggerGraphRequestModel (
325+ store = {"key" : "value" },
326+ inputs = {"input1" : "{{store.key}}" }
327+ )
328+
329+ with patch ('app.controller.trigger_graph.GraphTemplate' ) as mock_graph_template_cls , \
330+ patch ('app.controller.trigger_graph.DependentString' ) as mock_dependent_string_cls :
331+
332+ mock_graph_template = MagicMock ()
333+ mock_graph_template .is_valid .return_value = True
334+ mock_graph_template .store_config .required_keys = []
335+ mock_root_node = MagicMock ()
336+ mock_root_node .node_name = "root_node"
337+ mock_root_node .identifier = "root_id"
338+ mock_root_node .inputs = {"input1" : "{{store.key}}" }
339+ mock_graph_template .get_root_node .return_value = mock_root_node
340+ mock_graph_template_cls .get = AsyncMock (return_value = mock_graph_template )
341+
342+ # Mock dependent string behavior that raises an error
343+ mock_dependent_string = MagicMock ()
344+ mock_dependent = MagicMock ()
345+ mock_dependent .identifier = "store"
346+ mock_dependent .field = "key"
347+ mock_dependent_string .dependents = {0 : mock_dependent }
348+ mock_dependent_string_cls .create_dependent_string .return_value = mock_dependent_string
349+ mock_dependent_string .generate_string .side_effect = Exception ("Input processing error" )
350+
351+ with pytest .raises (HTTPException ) as exc_info :
352+ await trigger_graph (namespace_name , graph_name , req , x_exosphere_request_id )
353+
354+ assert exc_info .value .status_code == 400
355+ assert "Invalid input: Input processing error" in exc_info .value .detail
356+
357+
358+ @pytest .mark .asyncio
359+ async def test_trigger_graph_with_empty_store ():
360+ """Test trigger_graph with empty store (no stores to insert)"""
361+ namespace_name = "test_namespace"
362+ graph_name = "test_graph"
363+ x_exosphere_request_id = "test_request_id"
364+
365+ req = TriggerGraphRequestModel (store = {}, inputs = {})
366+
367+ with patch ('app.controller.trigger_graph.GraphTemplate' ) as mock_graph_template_cls , \
368+ patch ('app.controller.trigger_graph.Store' ) as mock_store_cls , \
369+ patch ('app.controller.trigger_graph.State' ) as mock_state_cls , \
370+ patch ('app.controller.trigger_graph.Run' ) as mock_run_cls :
371+
372+ mock_graph_template = MagicMock ()
373+ mock_graph_template .is_valid .return_value = True
374+ mock_graph_template .store_config .required_keys = []
375+ mock_root_node = MagicMock ()
376+ mock_root_node .node_name = "root_node"
377+ mock_root_node .identifier = "root_id"
378+ mock_root_node .inputs = {}
379+ mock_graph_template .get_root_node .return_value = mock_root_node
380+ mock_graph_template_cls .get = AsyncMock (return_value = mock_graph_template )
381+
382+ mock_store_cls .insert_many = AsyncMock (return_value = None )
383+ mock_state_instance = MagicMock ()
384+ mock_state_instance .insert = AsyncMock (return_value = None )
385+ mock_state_cls .return_value = mock_state_instance
386+
387+ mock_run_instance = MagicMock ()
388+ mock_run_instance .insert = AsyncMock (return_value = None )
389+ mock_run_cls .return_value = mock_run_instance
390+
391+ result = await trigger_graph (namespace_name , graph_name , req , x_exosphere_request_id )
392+
393+ assert result .status == StateStatusEnum .CREATED
394+ # Store.insert_many should not be called when store is empty
395+ mock_store_cls .insert_many .assert_not_called ()
396+
397+
398+ @pytest .mark .asyncio
399+ async def test_trigger_graph_general_exception ():
400+ """Test trigger_graph with general exception handling"""
401+ namespace_name = "test_namespace"
402+ graph_name = "test_graph"
403+ x_exosphere_request_id = "test_request_id"
404+
405+ req = TriggerGraphRequestModel (store = {"key" : "value" }, inputs = {})
406+
407+ with patch ('app.controller.trigger_graph.GraphTemplate' ) as mock_graph_template_cls :
408+ # Simulate a general exception during graph template retrieval
409+ mock_graph_template_cls .get .side_effect = Exception ("Database connection error" )
410+
411+ with pytest .raises (Exception , match = "Database connection error" ):
412+ await trigger_graph (namespace_name , graph_name , req , x_exosphere_request_id )
0 commit comments