@@ -14,18 +14,23 @@ import { Checkpoint } from "../../utils/checkpoint/checkpoint-helper";
1414
1515jest . mock ( "../../utils/logger/logger" ) ;
1616jest . mock ( "../../errors/serdes-errors/serdes-errors" ) ;
17+ jest . mock ( "../../utils/context-tracker/context-tracker" ) ;
1718
1819import {
1920 safeSerialize ,
2021 safeDeserialize ,
2122} from "../../errors/serdes-errors/serdes-errors" ;
23+ import { runWithContext } from "../../utils/context-tracker/context-tracker" ;
2224
2325const mockSafeSerialize = safeSerialize as jest . MockedFunction <
2426 typeof safeSerialize
2527> ;
2628const mockSafeDeserialize = safeDeserialize as jest . MockedFunction <
2729 typeof safeDeserialize
2830> ;
31+ const mockRunWithContext = runWithContext as jest . MockedFunction <
32+ typeof runWithContext
33+ > ;
2934
3035describe ( "WaitForCondition Handler" , ( ) => {
3136 let mockContext : ExecutionContext ;
@@ -59,6 +64,11 @@ describe("WaitForCondition Handler", () => {
5964 mockSafeDeserialize . mockImplementation ( async ( _serdes , value ) =>
6065 value ? JSON . parse ( value ) : undefined ,
6166 ) ;
67+
68+ // Set up mockRunWithContext to execute the provided function
69+ mockRunWithContext . mockImplementation ( async ( _stepId , _parentId , fn ) => {
70+ return await fn ( ) ;
71+ } ) ;
6272 } ) ;
6373
6474 describe ( "Parameter parsing" , ( ) => {
@@ -313,4 +323,174 @@ describe("WaitForCondition Handler", () => {
313323 await handler ( checkFunc , config ) ;
314324 } ) ;
315325 } ) ;
326+
327+ describe ( "currentAttempt parameter" , ( ) => {
328+ it ( "should pass currentAttempt 1 to both runWithContext and waitStrategy on first execution" , async ( ) => {
329+ const handler = createWaitForConditionHandler (
330+ mockContext ,
331+ mockCheckpoint ,
332+ createStepId ,
333+ createDefaultLogger ( ) ,
334+ "parent-123" ,
335+ ) ;
336+
337+ const checkFunc : WaitForConditionCheckFunc < string , DurableLogger > = jest
338+ . fn ( )
339+ . mockResolvedValue ( "result" ) ;
340+ const waitStrategy = jest . fn ( ) . mockReturnValue ( { shouldContinue : false } ) ;
341+ const config : WaitForConditionConfig < string > = {
342+ waitStrategy,
343+ initialState : "initial" ,
344+ } ;
345+
346+ await handler ( checkFunc , config ) ;
347+
348+ // Verify currentAttempt is passed correctly to both functions
349+ expect ( mockRunWithContext ) . toHaveBeenCalledWith (
350+ "step-1" ,
351+ "parent-123" ,
352+ expect . any ( Function ) ,
353+ 1 , // currentAttempt should be 1 on first execution
354+ expect . any ( String ) , // DurableExecutionMode.ExecutionMode
355+ ) ;
356+ expect ( waitStrategy ) . toHaveBeenCalledWith ( "result" , 1 ) ;
357+ } ) ;
358+
359+ it ( "should calculate currentAttempt correctly based on step data attempt count" , async ( ) => {
360+ const stepId = "step-1" ;
361+ const hashedStepId = hashId ( stepId ) ;
362+
363+ // Mock step data with attempt = 2 (so currentAttempt should be 3)
364+ ( mockContext as any ) . _stepData [ hashedStepId ] = {
365+ Id : hashedStepId ,
366+ Status : OperationStatus . READY ,
367+ StepDetails : {
368+ Attempt : 2 ,
369+ Result : JSON . stringify ( "previous-state" ) ,
370+ } ,
371+ } ;
372+
373+ ( mockContext . getStepData as jest . Mock ) . mockReturnValue (
374+ ( mockContext as any ) . _stepData [ hashedStepId ] ,
375+ ) ;
376+
377+ const handler = createWaitForConditionHandler (
378+ mockContext ,
379+ mockCheckpoint ,
380+ createStepId ,
381+ createDefaultLogger ( ) ,
382+ "parent-123" ,
383+ ) ;
384+
385+ const checkFunc : WaitForConditionCheckFunc < string , DurableLogger > = jest
386+ . fn ( )
387+ . mockResolvedValue ( "result" ) ;
388+ const waitStrategy = jest . fn ( ) . mockReturnValue ( { shouldContinue : false } ) ;
389+ const config : WaitForConditionConfig < string > = {
390+ waitStrategy,
391+ initialState : "initial" ,
392+ } ;
393+
394+ await handler ( checkFunc , config ) ;
395+
396+ // Verify currentAttempt = Attempt + 1 for both functions
397+ expect ( mockRunWithContext ) . toHaveBeenCalledWith (
398+ "step-1" ,
399+ "parent-123" ,
400+ expect . any ( Function ) ,
401+ 3 , // currentAttempt should be Attempt + 1 = 2 + 1 = 3
402+ expect . any ( String ) ,
403+ ) ;
404+ expect ( waitStrategy ) . toHaveBeenCalledWith ( "result" , 3 ) ;
405+ } ) ;
406+
407+ it ( "should pass incrementing currentAttempt through multiple retries" , async ( ) => {
408+ const stepId = "step-1" ;
409+ const hashedStepId = hashId ( stepId ) ;
410+
411+ // Track the retry cycle - first call has no step data, then subsequent calls have increasing attempt numbers
412+ let getStepDataCallCount = 0 ;
413+ ( mockContext . getStepData as jest . Mock ) . mockImplementation ( ( ) => {
414+ getStepDataCallCount ++ ;
415+
416+ // First call - no step data (fresh execution)
417+ if ( getStepDataCallCount <= 2 ) {
418+ return null ;
419+ }
420+ // Subsequent calls during retry cycles - simulate step data with increasing attempt number
421+ const attemptNumber = Math . floor ( ( getStepDataCallCount - 2 ) / 2 ) ;
422+ return {
423+ Id : hashedStepId ,
424+ Status : OperationStatus . READY ,
425+ StepDetails : {
426+ Attempt : attemptNumber ,
427+ Result : JSON . stringify ( 10 ) ,
428+ } ,
429+ } ;
430+ } ) ;
431+
432+ const handler = createWaitForConditionHandler (
433+ mockContext ,
434+ mockCheckpoint ,
435+ createStepId ,
436+ createDefaultLogger ( ) ,
437+ undefined ,
438+ ) ;
439+
440+ const checkFunc : WaitForConditionCheckFunc < number , DurableLogger > = jest
441+ . fn ( )
442+ . mockResolvedValue ( 10 ) ;
443+
444+ const waitStrategy = jest
445+ . fn ( )
446+ . mockReturnValueOnce ( {
447+ shouldContinue : true ,
448+ delay : { milliseconds : 1 } ,
449+ } )
450+ . mockReturnValueOnce ( {
451+ shouldContinue : true ,
452+ delay : { milliseconds : 1 } ,
453+ } )
454+ . mockReturnValue ( { shouldContinue : false } ) ;
455+
456+ const config : WaitForConditionConfig < number > = {
457+ waitStrategy,
458+ initialState : 0 ,
459+ } ;
460+
461+ await handler ( checkFunc , config ) ;
462+
463+ // Check that waitStrategy was called with incrementing currentAttempt values
464+ expect ( waitStrategy ) . toHaveBeenNthCalledWith ( 1 , 10 , 1 ) ; // first attempt
465+ expect ( waitStrategy ) . toHaveBeenNthCalledWith ( 2 , 10 , 2 ) ; // second attempt
466+ expect ( waitStrategy ) . toHaveBeenNthCalledWith ( 3 , 10 , 3 ) ; // third attempt
467+
468+ // Verify runWithContext was also called with correct attempts
469+ expect ( mockRunWithContext ) . toHaveBeenCalledTimes ( 3 ) ;
470+ expect ( mockRunWithContext ) . toHaveBeenNthCalledWith (
471+ 1 ,
472+ "step-1" ,
473+ undefined ,
474+ expect . any ( Function ) ,
475+ 1 ,
476+ expect . any ( String ) ,
477+ ) ;
478+ expect ( mockRunWithContext ) . toHaveBeenNthCalledWith (
479+ 2 ,
480+ "step-1" ,
481+ undefined ,
482+ expect . any ( Function ) ,
483+ 2 ,
484+ expect . any ( String ) ,
485+ ) ;
486+ expect ( mockRunWithContext ) . toHaveBeenNthCalledWith (
487+ 3 ,
488+ "step-1" ,
489+ undefined ,
490+ expect . any ( Function ) ,
491+ 3 ,
492+ expect . any ( String ) ,
493+ ) ;
494+ } ) ;
495+ } ) ;
316496} ) ;
0 commit comments