22// SPDX-License-Identifier: Apache-2.0
33package com .amazonaws .lambda .durable ;
44
5- import static org .junit .jupiter .api .Assertions .*;
5+ import static org .junit .jupiter .api .Assertions .assertEquals ;
6+ import static org .junit .jupiter .api .Assertions .assertNotNull ;
7+ import static org .junit .jupiter .api .Assertions .assertTrue ;
68
7- import com .amazonaws .lambda .durable .exception .StepFailedException ;
89import com .amazonaws .lambda .durable .exception .StepInterruptedException ;
910import com .amazonaws .lambda .durable .model .ExecutionStatus ;
1011import com .amazonaws .lambda .durable .retry .RetryStrategies ;
@@ -47,7 +48,7 @@ void testStepFailedExceptionCanBeCaughtWithFallback() {
4748 StepConfig .builder ()
4849 .retryStrategy (RetryStrategies .Presets .NO_RETRY )
4950 .build ());
50- } catch (StepFailedException e ) {
51+ } catch (RuntimeException e ) {
5152 return ctx .step ("fallback" , String .class , () -> "fallback-result" );
5253 }
5354 });
@@ -58,6 +59,90 @@ void testStepFailedExceptionCanBeCaughtWithFallback() {
5859 assertEquals ("fallback-result" , result .getResult (String .class ));
5960 }
6061
62+ @ Test
63+ void testOriginalExceptionTypeIsPreserved () {
64+ var runner = LocalDurableTestRunner .create (String .class , (input , ctx ) -> {
65+ ctx .step (
66+ "throws-illegal-arg" ,
67+ String .class ,
68+ () -> {
69+ throw new IllegalArgumentException ("Invalid parameter" );
70+ },
71+ StepConfig .builder ()
72+ .retryStrategy (RetryStrategies .Presets .NO_RETRY )
73+ .build ());
74+ return "should-not-reach" ;
75+ });
76+
77+ // First run - exception is thrown and checkpointed
78+ var result = runner .run ("test" );
79+ assertEquals (ExecutionStatus .FAILED , result .getStatus ());
80+
81+ // Verify the operation failed with the correct exception type
82+ var failedOp = result .getOperation ("throws-illegal-arg" );
83+ assertNotNull (failedOp );
84+ var error = failedOp .getError ();
85+ assertNotNull (error );
86+ assertEquals ("java.lang.IllegalArgumentException" , error .errorType ());
87+ assertEquals ("Invalid parameter" , error .errorMessage ());
88+ }
89+
90+ @ Test
91+ void testOriginalExceptionTypeCanBeCaughtSpecifically () {
92+ var runner = LocalDurableTestRunner .create (String .class , (input , ctx ) -> {
93+ try {
94+ return ctx .step (
95+ "throws-illegal-state" ,
96+ String .class ,
97+ () -> {
98+ throw new IllegalStateException ("Invalid state" );
99+ },
100+ StepConfig .builder ()
101+ .retryStrategy (RetryStrategies .Presets .NO_RETRY )
102+ .build ());
103+ } catch (IllegalStateException e ) {
104+ // Catch specific exception type
105+ return ctx .step ("handle-illegal-state" , String .class , () -> "recovered-from-illegal-state" );
106+ } catch (Exception e ) {
107+ // This should NOT be caught
108+ return ctx .step ("handle-illegal-arg" , String .class , () -> "recovered-from-exception" );
109+ }
110+ });
111+
112+ var result = runner .runUntilComplete ("test" );
113+
114+ assertEquals (ExecutionStatus .SUCCEEDED , result .getStatus ());
115+ assertEquals ("recovered-from-illegal-state" , result .getResult (String .class ));
116+ }
117+
118+ @ Test
119+ void testCustomExceptionTypeIsPreserved () {
120+ var runner = LocalDurableTestRunner .create (String .class , (input , ctx ) -> {
121+ ctx .step (
122+ "throws-custom" ,
123+ String .class ,
124+ () -> {
125+ throw new CustomBusinessException ("Business rule violated" , 42 );
126+ },
127+ StepConfig .builder ()
128+ .retryStrategy (RetryStrategies .Presets .NO_RETRY )
129+ .build ());
130+ return "should-not-reach" ;
131+ });
132+
133+ var result = runner .runUntilComplete ("test" );
134+
135+ assertEquals (ExecutionStatus .FAILED , result .getStatus ());
136+
137+ // Verify the operation failed with the correct exception type
138+ var failedOp = result .getOperation ("throws-custom" );
139+ assertNotNull (failedOp );
140+ var error = failedOp .getError ();
141+ assertNotNull (error );
142+ assertTrue (error .errorType ().contains ("CustomBusinessException" ));
143+ assertEquals ("Business rule violated" , error .errorMessage ());
144+ }
145+
61146 @ Test
62147 void testStepInterruptedExceptionForAtMostOnceAfterCheckpointLoss () {
63148 var executionCount = new AtomicInteger (0 );
@@ -87,6 +172,7 @@ void testStepInterruptedExceptionForAtMostOnceAfterCheckpointLoss() {
87172
88173 assertEquals (ExecutionStatus .FAILED , result .getStatus ());
89174 assertEquals (1 , executionCount .get ()); // Should NOT have re-executed
175+ assertEquals (result .getError ().get ().errorType (), StepInterruptedException .class .getName ());
90176 }
91177
92178 @ Test
@@ -144,4 +230,18 @@ void testNonDeterministicExceptionOnStepNameChange() {
144230
145231 assertEquals (ExecutionStatus .FAILED , result .getStatus ());
146232 }
233+
234+ // Custom exception for testing exception preservation
235+ public static class CustomBusinessException extends RuntimeException {
236+ private final int errorCode ;
237+
238+ public CustomBusinessException (String message , int errorCode ) {
239+ super (message );
240+ this .errorCode = errorCode ;
241+ }
242+
243+ public int getErrorCode () {
244+ return errorCode ;
245+ }
246+ }
147247}
0 commit comments