@@ -739,3 +739,52 @@ async def test_group_force_accept(
739739 await session .refresh (group , ["status" , "metadata_" , "machine" ])
740740 group_status = group .status
741741 assert group_status is expected_status
742+
743+
744+ async def test_step_fail_reset (
745+ * ,
746+ test_campaign_groups : str ,
747+ session : AsyncSession ,
748+ aclient : AsyncClient ,
749+ ) -> None :
750+ """Tests that a failed Step is recoverable with the reset trigger"""
751+ campaign_id = urlparse (url = test_campaign_groups ).path .split ("/" )[- 2 :][0 ]
752+
753+ # Fetch and FAIL to prepare a step, as a broken manifest may cause
754+ step_id = uuid5 (UUID (campaign_id ), "lambert.1" )
755+ step = await session .get_one (Node , step_id )
756+ step_machine = StepMachine (o = step )
757+ with patch (
758+ "lsst.cmservice.machines.node.StepMachine.do_prepare" ,
759+ side_effect = RuntimeError ("Step failed to prepare" ),
760+ ):
761+ await step_machine .trigger ("prepare" )
762+
763+ await session .refresh (step , ["status" ])
764+ await session .commit ()
765+ step_status = step .status
766+ assert step_status is StatusEnum .failed
767+
768+ # trigger 'RESET' by setting the status to waiting
769+ x = await aclient .patch (
770+ f"/cm-service/v2/nodes/{ step .id } " ,
771+ json = {"status" : "waiting" , "force" : "true" },
772+ headers = {"Content-Type" : "application/merge-patch+json" },
773+ )
774+ assert x .is_success
775+
776+ # wait for and assert background task is successful
777+ update_url = x .headers ["StatusUpdate" ]
778+ for _ in range (5 ):
779+ x = await aclient .get (update_url )
780+ result = x .json ()
781+ if len (result ):
782+ activity_log_message = result [0 ]["detail" ]["message" ]
783+ assert "rolled back" in activity_log_message
784+ break
785+ else :
786+ await sleep (1.0 )
787+
788+ await session .refresh (step , ["status" , "metadata_" ])
789+ step_status = step .status
790+ assert step_status is StatusEnum .waiting
0 commit comments