@@ -66,9 +66,10 @@ def setUp(self):
6666 @patch ('krkn.scenario_plugins.abstract_scenario_plugin.cleanup_rollback_version_files' )
6767 @patch ('krkn.scenario_plugins.abstract_scenario_plugin.utils.collect_and_put_ocp_logs' )
6868 @patch ('krkn.scenario_plugins.abstract_scenario_plugin.signal_handler.signal_context' )
69+ @patch ('krkn.scenario_plugins.abstract_scenario_plugin.os.path.exists' , return_value = True )
6970 @patch ('time.sleep' )
7071 def test_cerberus_publish_called_after_successful_scenario (
71- self , mock_sleep , mock_signal_ctx , mock_collect_logs , mock_cleanup , mock_cerberus_publish
72+ self , mock_sleep , mock_exists , mock_signal_ctx , mock_collect_logs , mock_cleanup , mock_cerberus_publish
7273 ):
7374 """Test that cerberus.publish_kraken_status is called after a successful scenario"""
7475 mock_signal_ctx .return_value .__enter__ = Mock ()
@@ -97,9 +98,10 @@ def test_cerberus_publish_called_after_successful_scenario(
9798 @patch ('krkn.scenario_plugins.abstract_scenario_plugin.execute_rollback_version_files' )
9899 @patch ('krkn.scenario_plugins.abstract_scenario_plugin.utils.collect_and_put_ocp_logs' )
99100 @patch ('krkn.scenario_plugins.abstract_scenario_plugin.signal_handler.signal_context' )
101+ @patch ('krkn.scenario_plugins.abstract_scenario_plugin.os.path.exists' , return_value = True )
100102 @patch ('time.sleep' )
101103 def test_cerberus_publish_called_after_failed_scenario (
102- self , mock_sleep , mock_signal_ctx , mock_collect_logs , mock_rollback , mock_cerberus_publish
104+ self , mock_sleep , mock_exists , mock_signal_ctx , mock_collect_logs , mock_rollback , mock_cerberus_publish
103105 ):
104106 """Test that cerberus.publish_kraken_status is called even after a failed scenario"""
105107 mock_signal_ctx .return_value .__enter__ = Mock ()
@@ -122,9 +124,10 @@ def test_cerberus_publish_called_after_failed_scenario(
122124 @patch ('krkn.scenario_plugins.abstract_scenario_plugin.cleanup_rollback_version_files' )
123125 @patch ('krkn.scenario_plugins.abstract_scenario_plugin.utils.collect_and_put_ocp_logs' )
124126 @patch ('krkn.scenario_plugins.abstract_scenario_plugin.signal_handler.signal_context' )
127+ @patch ('krkn.scenario_plugins.abstract_scenario_plugin.os.path.exists' , return_value = True )
125128 @patch ('time.sleep' )
126129 def test_cerberus_publish_called_for_multiple_scenarios (
127- self , mock_sleep , mock_signal_ctx , mock_collect_logs , mock_cleanup , mock_cerberus_publish
130+ self , mock_sleep , mock_exists , mock_signal_ctx , mock_collect_logs , mock_cleanup , mock_cerberus_publish
128131 ):
129132 """Test that cerberus.publish_kraken_status is called for each scenario"""
130133 mock_signal_ctx .return_value .__enter__ = Mock ()
@@ -148,10 +151,11 @@ def test_cerberus_publish_called_for_multiple_scenarios(
148151 @patch ('krkn.scenario_plugins.abstract_scenario_plugin.execute_rollback_version_files' )
149152 @patch ('krkn.scenario_plugins.abstract_scenario_plugin.utils.collect_and_put_ocp_logs' )
150153 @patch ('krkn.scenario_plugins.abstract_scenario_plugin.signal_handler.signal_context' )
154+ @patch ('krkn.scenario_plugins.abstract_scenario_plugin.os.path.exists' , return_value = True )
151155 @patch ('time.sleep' )
152156 @patch ('time.time' )
153157 def test_cerberus_publish_timing (
154- self , mock_time , mock_sleep , mock_signal_ctx , mock_collect_logs ,
158+ self , mock_time , mock_sleep , mock_exists , mock_signal_ctx , mock_collect_logs ,
155159 mock_rollback , mock_cleanup , mock_cerberus_publish
156160 ):
157161 """Test that cerberus.publish_kraken_status receives correct timestamps"""
@@ -181,9 +185,10 @@ def test_cerberus_publish_timing(
181185 @patch ('krkn.scenario_plugins.abstract_scenario_plugin.cleanup_rollback_version_files' )
182186 @patch ('krkn.scenario_plugins.abstract_scenario_plugin.utils.collect_and_put_ocp_logs' )
183187 @patch ('krkn.scenario_plugins.abstract_scenario_plugin.signal_handler.signal_context' )
188+ @patch ('krkn.scenario_plugins.abstract_scenario_plugin.os.path.exists' , return_value = True )
184189 @patch ('time.sleep' )
185190 def test_cerberus_publish_exception_does_not_break_flow (
186- self , mock_sleep , mock_signal_ctx , mock_collect_logs , mock_cleanup , mock_cerberus_publish
191+ self , mock_sleep , mock_exists , mock_signal_ctx , mock_collect_logs , mock_cleanup , mock_cerberus_publish
187192 ):
188193 """Test that exceptions in cerberus.publish_kraken_status don't break scenario execution"""
189194 mock_signal_ctx .return_value .__enter__ = Mock ()
@@ -210,9 +215,10 @@ def test_cerberus_publish_exception_does_not_break_flow(
210215 @patch ('krkn.scenario_plugins.abstract_scenario_plugin.execute_rollback_version_files' )
211216 @patch ('krkn.scenario_plugins.abstract_scenario_plugin.utils.collect_and_put_ocp_logs' )
212217 @patch ('krkn.scenario_plugins.abstract_scenario_plugin.signal_handler.signal_context' )
218+ @patch ('krkn.scenario_plugins.abstract_scenario_plugin.os.path.exists' , return_value = True )
213219 @patch ('time.sleep' )
214220 def test_cerberus_publish_called_for_mixed_success_and_failure (
215- self , mock_sleep , mock_signal_ctx , mock_collect_logs , mock_rollback ,
221+ self , mock_sleep , mock_exists , mock_signal_ctx , mock_collect_logs , mock_rollback ,
216222 mock_cleanup , mock_cerberus_publish
217223 ):
218224 """Test cerberus publish is called for both successful and failed scenarios"""
@@ -250,9 +256,10 @@ def get_scenario_types(self):
250256 @patch ('krkn.scenario_plugins.abstract_scenario_plugin.cerberus.publish_kraken_status' )
251257 @patch ('krkn.scenario_plugins.abstract_scenario_plugin.utils.collect_and_put_ocp_logs' )
252258 @patch ('krkn.scenario_plugins.abstract_scenario_plugin.signal_handler.signal_context' )
259+ @patch ('krkn.scenario_plugins.abstract_scenario_plugin.os.path.exists' , return_value = True )
253260 @patch ('time.sleep' )
254261 def test_cerberus_not_called_for_deprecated_post_scenarios (
255- self , mock_sleep , mock_signal_ctx , mock_collect_logs , mock_cerberus_publish
262+ self , mock_sleep , mock_exists , mock_signal_ctx , mock_collect_logs , mock_cerberus_publish
256263 ):
257264 """Test that cerberus is not called for deprecated post scenarios (list format)"""
258265 mock_signal_ctx .return_value .__enter__ = Mock ()
@@ -277,9 +284,10 @@ def test_cerberus_not_called_for_deprecated_post_scenarios(
277284 @patch ('krkn.scenario_plugins.abstract_scenario_plugin.utils.collect_and_put_ocp_logs' )
278285 @patch ('krkn.scenario_plugins.abstract_scenario_plugin.utils.populate_cluster_events' )
279286 @patch ('krkn.scenario_plugins.abstract_scenario_plugin.signal_handler.signal_context' )
287+ @patch ('krkn.scenario_plugins.abstract_scenario_plugin.os.path.exists' , return_value = True )
280288 @patch ('time.sleep' )
281289 def test_cerberus_called_with_events_backup_enabled (
282- self , mock_sleep , mock_signal_ctx , mock_populate_events ,
290+ self , mock_sleep , mock_exists , mock_signal_ctx , mock_populate_events ,
283291 mock_collect_logs , mock_cleanup , mock_cerberus_publish
284292 ):
285293 """Test that cerberus is called even when events_backup is enabled"""
@@ -308,9 +316,10 @@ def test_cerberus_called_with_events_backup_enabled(
308316 @patch ('krkn.scenario_plugins.abstract_scenario_plugin.execute_rollback_version_files' )
309317 @patch ('krkn.scenario_plugins.abstract_scenario_plugin.utils.collect_and_put_ocp_logs' )
310318 @patch ('krkn.scenario_plugins.abstract_scenario_plugin.signal_handler.signal_context' )
319+ @patch ('krkn.scenario_plugins.abstract_scenario_plugin.os.path.exists' , return_value = True )
311320 @patch ('time.sleep' )
312321 def test_cerberus_called_after_exception_in_run (
313- self , mock_sleep , mock_signal_ctx , mock_collect_logs ,
322+ self , mock_sleep , mock_exists , mock_signal_ctx , mock_collect_logs ,
314323 mock_rollback , mock_cerberus_publish
315324 ):
316325 """Test that cerberus is called even if run() raises an uncaught exception"""
@@ -345,5 +354,73 @@ def get_scenario_types(self):
345354 self .assertEqual (telemetries [0 ].exit_status , 1 )
346355
347356
357+ @patch ('krkn.scenario_plugins.abstract_scenario_plugin.cerberus.publish_kraken_status' )
358+ @patch ('krkn.scenario_plugins.abstract_scenario_plugin.os.path.exists' , return_value = False )
359+ @patch ('time.sleep' )
360+ def test_missing_scenario_file_logs_error_and_marks_failed (
361+ self , mock_sleep , mock_exists , mock_cerberus_publish
362+ ):
363+ """Test that a missing scenario file logs a clear error and is marked as failed without crashing"""
364+ scenarios_list = ["scenarios/openshift/cnv.yml" ]
365+
366+ with self .assertLogs ('root' , level = 'ERROR' ) as log_ctx :
367+ failed_scenarios , telemetries = self .plugin .run_scenarios (
368+ "test-uuid" ,
369+ scenarios_list ,
370+ self .krkn_config ,
371+ self .mock_telemetry ,
372+ )
373+
374+ # scenario is marked failed and returned in failed list
375+ self .assertEqual (len (failed_scenarios ), 1 )
376+ self .assertEqual (failed_scenarios [0 ], "scenarios/openshift/cnv.yml" )
377+
378+ # telemetry recorded with exit_status=1
379+ self .assertEqual (len (telemetries ), 1 )
380+ self .assertEqual (telemetries [0 ].exit_status , 1 )
381+
382+ # error message contains the missing path
383+ self .assertTrue (
384+ any ("scenarios/openshift/cnv.yml" in msg for msg in log_ctx .output ),
385+ f"Expected file path in error log, got: { log_ctx .output } " ,
386+ )
387+
388+ # set_parameters_base64 and cerberus should not be called
389+ self .mock_telemetry .set_parameters_base64 .assert_not_called ()
390+ mock_cerberus_publish .assert_not_called ()
391+
392+ @patch ('krkn.scenario_plugins.abstract_scenario_plugin.cerberus.publish_kraken_status' )
393+ @patch ('krkn.scenario_plugins.abstract_scenario_plugin.cleanup_rollback_version_files' )
394+ @patch ('krkn.scenario_plugins.abstract_scenario_plugin.utils.collect_and_put_ocp_logs' )
395+ @patch ('krkn.scenario_plugins.abstract_scenario_plugin.signal_handler.signal_context' )
396+ @patch ('krkn.scenario_plugins.abstract_scenario_plugin.os.path.exists' )
397+ @patch ('time.sleep' )
398+ def test_missing_scenario_file_skipped_others_continue (
399+ self , mock_sleep , mock_exists , mock_signal_ctx , mock_collect_logs ,
400+ mock_cleanup , mock_cerberus_publish
401+ ):
402+ """Test that a missing file is skipped and remaining scenarios still run"""
403+ mock_signal_ctx .return_value .__enter__ = Mock ()
404+ mock_signal_ctx .return_value .__exit__ = Mock (return_value = False )
405+ # first file missing, second exists
406+ mock_exists .side_effect = [False , True ]
407+
408+ scenarios_list = ["missing.yml" , "scenario2.yaml" ]
409+
410+ with self .assertLogs ('root' , level = 'ERROR' ):
411+ failed_scenarios , telemetries = self .plugin .run_scenarios (
412+ "test-uuid" ,
413+ scenarios_list ,
414+ self .krkn_config ,
415+ self .mock_telemetry ,
416+ )
417+
418+ self .assertIn ("missing.yml" , failed_scenarios )
419+ self .assertNotIn ("scenario2.yaml" , failed_scenarios )
420+ self .assertEqual (len (telemetries ), 2 )
421+ # cerberus called only for the scenario that ran
422+ self .assertEqual (mock_cerberus_publish .call_count , 1 )
423+
424+
348425if __name__ == '__main__' :
349426 unittest .main ()
0 commit comments