99import java .util .List ;
1010import java .util .concurrent .atomic .AtomicInteger ;
1111import org .junit .jupiter .api .Test ;
12- import software .amazon .lambda .durable .model .CompletionReason ;
12+ import software .amazon .lambda .durable .model .ConcurrencyCompletionStatus ;
1313import software .amazon .lambda .durable .model .ExecutionStatus ;
1414import software .amazon .lambda .durable .model .MapResultItem ;
1515import software .amazon .lambda .durable .testing .LocalDurableTestRunner ;
@@ -81,7 +81,7 @@ void testMapPartialFailure_failedItemDoesNotPreventOthers() {
8181 assertNull (result .getError (0 ));
8282 assertNull (result .getError (2 ));
8383
84- assertEquals (CompletionReason .ALL_COMPLETED , result .completionReason ());
84+ assertEquals (ConcurrencyCompletionStatus .ALL_COMPLETED , result .completionReason ());
8585
8686 return "done" ;
8787 });
@@ -252,7 +252,7 @@ void testMapWithToleratedFailureCount_earlyTermination() {
252252 },
253253 config );
254254
255- assertEquals (CompletionReason .FAILURE_TOLERANCE_EXCEEDED , result .completionReason ());
255+ assertEquals (ConcurrencyCompletionStatus .FAILURE_TOLERANCE_EXCEEDED , result .completionReason ());
256256 assertFalse (result .allSucceeded ());
257257 assertEquals (5 , result .size ());
258258 assertEquals ("OK" , result .getResult (0 ));
@@ -279,7 +279,7 @@ void testMapWithMinSuccessful_earlyTermination() {
279279 var result = context .map (
280280 "min-successful" , items , String .class , (item , index , ctx ) -> item .toUpperCase (), config );
281281
282- assertEquals (CompletionReason .MIN_SUCCESSFUL_REACHED , result .completionReason ());
282+ assertEquals (ConcurrencyCompletionStatus .MIN_SUCCESSFUL_REACHED , result .completionReason ());
283283 assertEquals (5 , result .size ());
284284 assertEquals ("A" , result .getResult (0 ));
285285 assertEquals ("B" , result .getResult (1 ));
@@ -419,7 +419,7 @@ void testMapUnlimitedConcurrencyWithToleratedFailureCount() {
419419 },
420420 config );
421421
422- assertEquals (CompletionReason .FAILURE_TOLERANCE_EXCEEDED , result .completionReason ());
422+ assertEquals (ConcurrencyCompletionStatus .FAILURE_TOLERANCE_EXCEEDED , result .completionReason ());
423423 assertFalse (result .allSucceeded ());
424424 return "done" ;
425425 });
@@ -442,7 +442,7 @@ void testMapReplayWithFailedBranches() {
442442 return item .toUpperCase ();
443443 });
444444
445- // Errors survive replay since they are stored as ErrorObject (not raw Throwable)
445+ // Errors survive replay since they are stored as MapError (not raw Throwable)
446446 assertEquals ("OK" , result .getResult (0 ));
447447 assertEquals ("OK2" , result .getResult (2 ));
448448 return "done" ;
@@ -531,7 +531,7 @@ void testMapWithAllSuccessfulCompletionConfig_stopsOnFirstFailure() {
531531 },
532532 config );
533533
534- assertEquals (CompletionReason .FAILURE_TOLERANCE_EXCEEDED , result .completionReason ());
534+ assertEquals (ConcurrencyCompletionStatus .FAILURE_TOLERANCE_EXCEEDED , result .completionReason ());
535535 assertEquals ("OK1" , result .getResult (0 ));
536536 assertNotNull (result .getError (1 ));
537537 // Items after the failure should be NOT_STARTED
@@ -622,14 +622,51 @@ void testMapWithToleratedFailurePercentage() {
622622 },
623623 config );
624624
625- assertEquals (CompletionReason .FAILURE_TOLERANCE_EXCEEDED , result .completionReason ());
625+ assertEquals (ConcurrencyCompletionStatus .FAILURE_TOLERANCE_EXCEEDED , result .completionReason ());
626626 return "done" ;
627627 });
628628
629629 var result = runner .runUntilComplete ("test" );
630630 assertEquals (ExecutionStatus .SUCCEEDED , result .getStatus ());
631631 }
632632
633+ @ Test
634+ void testMapWithToleratedFailurePercentage_replay () {
635+ var executionCount = new AtomicInteger (0 );
636+
637+ var runner = LocalDurableTestRunner .create (String .class , (input , context ) -> {
638+ var items = List .of ("ok1" , "FAIL1" , "ok2" , "FAIL2" , "ok3" , "FAIL3" , "ok4" );
639+ var config = MapConfig .builder ()
640+ .completionConfig (CompletionConfig .toleratedFailurePercentage (0.3 ))
641+ .build ();
642+ var result = context .map (
643+ "pct-fail-replay" ,
644+ items ,
645+ String .class ,
646+ (item , index , ctx ) -> {
647+ executionCount .incrementAndGet ();
648+ if (item .startsWith ("FAIL" )) {
649+ throw new RuntimeException ("failed: " + item );
650+ }
651+ return item .toUpperCase ();
652+ },
653+ config );
654+
655+ assertEquals (ConcurrencyCompletionStatus .FAILURE_TOLERANCE_EXCEEDED , result .completionReason ());
656+ return "done" ;
657+ });
658+
659+ var result1 = runner .runUntilComplete ("test" );
660+ assertEquals (ExecutionStatus .SUCCEEDED , result1 .getStatus ());
661+ var firstRunCount = executionCount .get ();
662+
663+ // Replay — with unlimited concurrency, children replay simultaneously.
664+ // Verify completionReason is consistent and no re-execution occurs.
665+ var result2 = runner .run ("test" );
666+ assertEquals (ExecutionStatus .SUCCEEDED , result2 .getStatus ());
667+ assertEquals (firstRunCount , executionCount .get (), "Map functions should not re-execute on replay" );
668+ }
669+
633670 @ Test
634671 void testMapAsyncWithWaitInsideBranches () {
635672 var runner = LocalDurableTestRunner .create (String .class , (input , context ) -> {
@@ -747,7 +784,7 @@ void testMapWithMinSuccessful_replay() {
747784 },
748785 config );
749786
750- assertEquals (CompletionReason .MIN_SUCCESSFUL_REACHED , result .completionReason ());
787+ assertEquals (ConcurrencyCompletionStatus .MIN_SUCCESSFUL_REACHED , result .completionReason ());
751788 assertEquals ("A" , result .getResult (0 ));
752789 assertEquals ("B" , result .getResult (1 ));
753790 return "done" ;
@@ -826,4 +863,24 @@ void testMapWithLargeResult_replayChildren() {
826863 assertEquals (ExecutionStatus .SUCCEEDED , result2 .getStatus ());
827864 assertEquals (firstRunCount , executionCount .get (), "Map functions should not re-execute on replay" );
828865 }
866+
867+ @ Test
868+ void testMapWithNullResults () {
869+ var runner = LocalDurableTestRunner .create (String .class , (input , context ) -> {
870+ var items = List .of ("a" , "b" , "c" );
871+ var result = context .map ("null-map" , items , String .class , (item , index , ctx ) -> null );
872+
873+ assertTrue (result .allSucceeded ());
874+ assertEquals (3 , result .size ());
875+ for (int i = 0 ; i < result .size (); i ++) {
876+ assertEquals (MapResultItem .Status .SUCCEEDED , result .getItem (i ).status ());
877+ assertNull (result .getResult (i ));
878+ assertNull (result .getError (i ));
879+ }
880+ return "done" ;
881+ });
882+
883+ var result = runner .runUntilComplete ("test" );
884+ assertEquals (ExecutionStatus .SUCCEEDED , result .getStatus ());
885+ }
829886}
0 commit comments