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,98 @@ 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+ // Verify stackTrace is preserved
90+ assertNotNull (error .stackTrace ());
91+ assertTrue (error .stackTrace ().size () > 0 , "Stack trace should not be empty" );
92+
93+ // Verify errorData contains serialized exception
94+ assertNotNull (error .errorData ());
95+ assertTrue (error .errorData ().contains ("Invalid parameter" ), "errorData should contain the exception message" );
96+ }
97+
98+ @ Test
99+ void testOriginalExceptionTypeCanBeCaughtSpecifically () {
100+ var runner = LocalDurableTestRunner .create (String .class , (input , ctx ) -> {
101+ try {
102+ return ctx .step (
103+ "throws-illegal-state" ,
104+ String .class ,
105+ () -> {
106+ throw new IllegalStateException ("Invalid state" );
107+ },
108+ StepConfig .builder ()
109+ .retryStrategy (RetryStrategies .Presets .NO_RETRY )
110+ .build ());
111+ } catch (IllegalStateException e ) {
112+ // Catch specific exception type
113+ return ctx .step ("handle-illegal-state" , String .class , () -> "recovered-from-illegal-state" );
114+ } catch (Exception e ) {
115+ // This should NOT be caught
116+ return ctx .step ("handle-illegal-arg" , String .class , () -> "recovered-from-exception" );
117+ }
118+ });
119+
120+ var result = runner .runUntilComplete ("test" );
121+
122+ assertEquals (ExecutionStatus .SUCCEEDED , result .getStatus ());
123+ assertEquals ("recovered-from-illegal-state" , result .getResult (String .class ));
124+ }
125+
126+ @ Test
127+ void testCustomExceptionTypeIsPreserved () {
128+ var runner = LocalDurableTestRunner .create (String .class , (input , ctx ) -> {
129+ ctx .step (
130+ "throws-custom" ,
131+ String .class ,
132+ () -> {
133+ throw new CustomBusinessException ("Business rule violated" , 42 );
134+ },
135+ StepConfig .builder ()
136+ .retryStrategy (RetryStrategies .Presets .NO_RETRY )
137+ .build ());
138+ return "should-not-reach" ;
139+ });
140+
141+ var result = runner .runUntilComplete ("test" );
142+
143+ assertEquals (ExecutionStatus .FAILED , result .getStatus ());
144+
145+ // Verify the operation failed with the correct exception type
146+ var failedOp = result .getOperation ("throws-custom" );
147+ assertNotNull (failedOp );
148+ var error = failedOp .getError ();
149+ assertNotNull (error );
150+ assertTrue (error .errorType ().contains ("CustomBusinessException" ));
151+ assertEquals ("Business rule violated" , error .errorMessage ());
152+ }
153+
61154 @ Test
62155 void testStepInterruptedExceptionForAtMostOnceAfterCheckpointLoss () {
63156 var executionCount = new AtomicInteger (0 );
@@ -87,6 +180,7 @@ void testStepInterruptedExceptionForAtMostOnceAfterCheckpointLoss() {
87180
88181 assertEquals (ExecutionStatus .FAILED , result .getStatus ());
89182 assertEquals (1 , executionCount .get ()); // Should NOT have re-executed
183+ assertEquals (result .getError ().get ().errorType (), StepInterruptedException .class .getName ());
90184 }
91185
92186 @ Test
@@ -144,4 +238,18 @@ void testNonDeterministicExceptionOnStepNameChange() {
144238
145239 assertEquals (ExecutionStatus .FAILED , result .getStatus ());
146240 }
241+
242+ // Custom exception for testing exception preservation
243+ public static class CustomBusinessException extends RuntimeException {
244+ private final int errorCode ;
245+
246+ public CustomBusinessException (String message , int errorCode ) {
247+ super (message );
248+ this .errorCode = errorCode ;
249+ }
250+
251+ public int getErrorCode () {
252+ return errorCode ;
253+ }
254+ }
147255}
0 commit comments