99
1010from uipath_langchain .agent .tools .durable_interrupt import (
1111 _durable_state ,
12- _interrupt_offset ,
13- add_interrupt_offset ,
1412 durable_interrupt ,
1513)
1614
@@ -35,12 +33,10 @@ def _make_config(scratchpad: FakeScratchpad | None = None) -> dict[str, Any]:
3533
3634@pytest .fixture (autouse = True )
3735def _reset_durable_state () -> Generator [None ]:
38- """Reset per-node counter and offset between tests for isolation."""
36+ """Reset per-node counter between tests for isolation."""
3937 state_token = _durable_state .set (None )
40- offset_token = _interrupt_offset .set (0 )
4138 yield
4239 _durable_state .reset (state_token )
43- _interrupt_offset .reset (offset_token )
4440
4541
4642class TestAsyncFirstExecution :
@@ -515,161 +511,3 @@ def my_sync_fn() -> str:
515511 return "value"
516512
517513 assert not asyncio .iscoroutinefunction (my_sync_fn )
518-
519-
520- class TestInterruptOffsetWithPriorInterrupts :
521- """Offset accounts for prior interrupt() calls (e.g. HITL) in the same node."""
522-
523- @patch (PATCH_INTERRUPT )
524- @patch (PATCH_GET_CONFIG )
525- def test_offset_skips_hitl_resume_slot (
526- self , mock_get_config : MagicMock , mock_interrupt : MagicMock
527- ) -> None :
528- """With resume=["hitl_value"] and offset=1, durable's idx=1, 1 < 1 is False → body runs."""
529- scratchpad = FakeScratchpad (resume = ["hitl_value" ])
530- mock_get_config .return_value = _make_config (scratchpad )
531- mock_interrupt .side_effect = lambda v : v
532-
533- action = MagicMock (return_value = "job-started" )
534-
535- @durable_interrupt
536- def start_job () -> str :
537- return action ()
538-
539- add_interrupt_offset ()
540- start_job ()
541-
542- action .assert_called_once ()
543- mock_interrupt .assert_called_once_with ("job-started" )
544-
545- @patch (PATCH_INTERRUPT , return_value = "durable-result" )
546- @patch (PATCH_GET_CONFIG )
547- def test_offset_full_resume (
548- self , mock_get_config : MagicMock , mock_interrupt : MagicMock
549- ) -> None :
550- """With resume=["hitl_value", "durable_result"] and offset=1, durable reads index 1."""
551- scratchpad = FakeScratchpad (resume = ["hitl_value" , "durable_result" ])
552- mock_get_config .return_value = _make_config (scratchpad )
553-
554- action = MagicMock ()
555-
556- @durable_interrupt
557- def start_job () -> Any :
558- return action ()
559-
560- add_interrupt_offset ()
561- result = start_job ()
562-
563- action .assert_not_called () # body skipped — idx=1 < len(resume)=2
564- mock_interrupt .assert_called_once_with (None )
565- assert result == "durable-result"
566-
567- @patch (PATCH_INTERRUPT , return_value = "resumed" )
568- @patch (PATCH_GET_CONFIG )
569- def test_offset_resets_on_scratchpad_change (
570- self , mock_get_config : MagicMock , mock_interrupt : MagicMock
571- ) -> None :
572- """After consuming offset on first scratchpad, a new scratchpad starts at 0."""
573- sp1 = FakeScratchpad (resume = ["hitl" , "durable" ])
574- mock_get_config .return_value = _make_config (sp1 )
575-
576- @durable_interrupt
577- def task_a () -> str :
578- return "should-not-run"
579-
580- add_interrupt_offset ()
581- task_a () # consumes offset, idx=1
582-
583- # New scratchpad — offset should have been consumed/reset
584- sp2 = FakeScratchpad (resume = ["val" ])
585- mock_get_config .return_value = _make_config (sp2 )
586-
587- action = MagicMock ()
588-
589- @durable_interrupt
590- def task_b () -> Any :
591- return action ()
592-
593- task_b () # idx should be 0, not 1
594-
595- action .assert_not_called () # idx=0 < len(resume)=1 → skipped
596-
597- @patch (PATCH_INTERRUPT )
598- @patch (PATCH_GET_CONFIG )
599- def test_offset_with_multiple_durable_interrupts (
600- self , mock_get_config : MagicMock , mock_interrupt : MagicMock
601- ) -> None :
602- """HITL + two durable_interrupts: offset shifts both indices correctly."""
603- # resume[0] = HITL value, resume[1] = durable_a result
604- # durable_a at idx=1 → resumed, durable_b at idx=2 → body runs
605- scratchpad = FakeScratchpad (resume = ["hitl_approval" , "durable_a_result" ])
606- mock_get_config .return_value = _make_config (scratchpad )
607- mock_interrupt .side_effect = lambda v : f"interrupt({ v } )"
608-
609- action_a = MagicMock ()
610- action_b = MagicMock (return_value = "job-B" )
611-
612- @durable_interrupt
613- def durable_a () -> Any :
614- return action_a ()
615-
616- @durable_interrupt
617- def durable_b () -> str :
618- return action_b ()
619-
620- add_interrupt_offset ()
621- result_a = durable_a () # idx=1, 1 < 2 → skipped
622- result_b = durable_b () # idx=2, 2 < 2 → False → body runs
623-
624- action_a .assert_not_called ()
625- action_b .assert_called_once ()
626- assert result_a == "interrupt(None)"
627- assert result_b == "interrupt(job-B)"
628-
629- @patch (PATCH_INTERRUPT )
630- @patch (PATCH_GET_CONFIG )
631- def test_multiple_offsets_accumulate (
632- self , mock_get_config : MagicMock , mock_interrupt : MagicMock
633- ) -> None :
634- """Two prior interrupt() calls → offset accumulates to 2."""
635- # resume[0] = first confirmation, resume[1] = second confirmation
636- # durable at idx=2 → body runs (2 < 2 is False)
637- scratchpad = FakeScratchpad (resume = ["confirm_1" , "confirm_2" ])
638- mock_get_config .return_value = _make_config (scratchpad )
639- mock_interrupt .side_effect = lambda v : v
640-
641- action = MagicMock (return_value = "job-started" )
642-
643- @durable_interrupt
644- def start_job () -> str :
645- return action ()
646-
647- add_interrupt_offset () # first confirmation
648- add_interrupt_offset () # second confirmation
649- start_job ()
650-
651- action .assert_called_once () # idx=2, 2 < 2 → False → body runs
652- mock_interrupt .assert_called_once_with ("job-started" )
653-
654- @patch (PATCH_INTERRUPT , return_value = "durable-result" )
655- @patch (PATCH_GET_CONFIG )
656- def test_multiple_offsets_full_resume (
657- self , mock_get_config : MagicMock , mock_interrupt : MagicMock
658- ) -> None :
659- """Two prior interrupts fully resumed + durable resumed → body skipped."""
660- scratchpad = FakeScratchpad (resume = ["confirm_1" , "confirm_2" , "durable-result" ])
661- mock_get_config .return_value = _make_config (scratchpad )
662-
663- action = MagicMock ()
664-
665- @durable_interrupt
666- def start_job () -> Any :
667- return action ()
668-
669- add_interrupt_offset ()
670- add_interrupt_offset ()
671- result = start_job ()
672-
673- action .assert_not_called () # idx=2, 2 < 3 → True → skipped
674- mock_interrupt .assert_called_once_with (None )
675- assert result == "durable-result"
0 commit comments