1313import org .junit .jupiter .api .Test ;
1414import software .amazon .awssdk .services .lambda .model .*;
1515
16- /**
17- * Unit tests for DurableContext.
18- *
19- * <p>Note: Async step execution tests (stepAsync with first execution) are tested in integration tests using
20- * LocalDurableTestRunner, which properly handles the thread coordination required for async operations. This test class
21- * focuses on synchronous operations and replay scenarios which can be reliably tested at the unit level.
22- */
2316class DurableContextTest {
2417
25- private static final String TEST_EXECUTION_ARN =
26- "arn:aws:lambda:us-east-1:123456789012:function:test:$LATEST/durable-execution/"
27- + "349beff4-a89d-4bc8-a56f-af7a8af67a5f/20dae574-53da-37a1-bfd5-b0e2e6ec715d" ;
28-
2918 private DurableContext createTestContext () {
3019 var executionOp = Operation .builder ()
3120 .id ("0" )
@@ -38,7 +27,12 @@ private DurableContext createTestContext() {
3827 private DurableContext createTestContext (List <Operation > initialOperations ) {
3928 var client = TestUtils .createMockClient ();
4029 var initialExecutionState = new InitialExecutionState (initialOperations , null );
41- var executionManager = new ExecutionManager (TEST_EXECUTION_ARN , "test-token" , initialExecutionState , client );
30+ var executionManager = new ExecutionManager (
31+ "arn:aws:lambda:us-east-1:123456789012:function:test:$LATEST/durable-execution/"
32+ + "349beff4-a89d-4bc8-a56f-af7a8af67a5f/20dae574-53da-37a1-bfd5-b0e2e6ec715d" ,
33+ "test-token" ,
34+ initialExecutionState ,
35+ client );
4236 return new DurableContext (executionManager , DurableConfig .builder ().build (), null );
4337 }
4438
@@ -90,8 +84,18 @@ void testStepReplay() {
9084 }
9185
9286 @ Test
93- void testStepAsyncReplay () {
94- // Create context with existing completed operation - tests replay behavior
87+ void testStepAsync () throws Exception {
88+ var context = createTestContext ();
89+
90+ var future = context .stepAsync ("async-test" , String .class , () -> "Async Result" );
91+
92+ assertNotNull (future );
93+ assertEquals ("Async Result" , future .get ());
94+ }
95+
96+ @ Test
97+ void testStepAsyncReplay () throws Exception {
98+ // Create context with existing operation
9599 var existingOp = Operation .builder ()
96100 .id ("1" )
97101 .status (OperationStatus .SUCCEEDED )
@@ -100,7 +104,7 @@ void testStepAsyncReplay() {
100104 .build ();
101105 var context = createTestContext (List .of (existingOp ));
102106
103- // This should return cached result immediately without blocking
107+ // This should return cached result immediately
104108 var future = context .stepAsync ("async-test" , String .class , () -> "New Async Result" );
105109 assertEquals ("Cached Async Result" , future .get ());
106110 }
@@ -129,8 +133,26 @@ void testWaitReplay() {
129133 }
130134
131135 @ Test
132- void testCombinedReplay () {
133- // Create context with all operations completed - tests replay behavior
136+ void testCombinedSyncAsyncWait () throws Exception {
137+ var context = createTestContext ();
138+
139+ // Execute sync step
140+ var syncResult = context .step ("sync-step" , String .class , () -> "Sync Done" );
141+ assertEquals ("Sync Done" , syncResult );
142+
143+ // Execute async step
144+ var asyncFuture = context .stepAsync ("async-step" , Integer .class , () -> 42 );
145+ assertEquals (42 , asyncFuture .get ());
146+
147+ // Wait should suspend (throw exception)
148+ assertThrows (SuspendExecutionException .class , () -> {
149+ context .wait (Duration .ofSeconds (30 ));
150+ });
151+ }
152+
153+ @ Test
154+ void testCombinedReplay () throws Exception {
155+ // Create context with all operations completed
134156 var syncOp = Operation .builder ()
135157 .id ("1" )
136158 .status (OperationStatus .SUCCEEDED )
@@ -228,8 +250,21 @@ void testStepWithTypeTokenAndConfig() {
228250 }
229251
230252 @ Test
231- void testStepAsyncWithTypeTokenReplay () {
232- // Create context with existing completed operation - tests replay behavior
253+ void testStepAsyncWithTypeToken () throws Exception {
254+ var context = createTestContext ();
255+
256+ DurableFuture <List <String >> future =
257+ context .stepAsync ("async-list" , new TypeToken <List <String >>() {}, () -> List .of ("x" , "y" , "z" ));
258+
259+ assertNotNull (future );
260+ List <String > result = future .get ();
261+ assertEquals (3 , result .size ());
262+ assertEquals ("x" , result .get (0 ));
263+ }
264+
265+ @ Test
266+ void testStepAsyncWithTypeTokenReplay () throws Exception {
267+ // Create context with existing operation
233268 var existingOp = Operation .builder ()
234269 .id ("1" )
235270 .status (OperationStatus .SUCCEEDED )
@@ -239,7 +274,7 @@ void testStepAsyncWithTypeTokenReplay() {
239274 .build ();
240275 var context = createTestContext (List .of (existingOp ));
241276
242- // This should return cached result immediately without blocking
277+ // This should return cached result immediately
243278 DurableFuture <List <String >> future = context .stepAsync (
244279 "async-list" , new TypeToken <List <String >>() {}, () -> List .of ("async-new1" , "async-new2" ));
245280
@@ -248,4 +283,22 @@ void testStepAsyncWithTypeTokenReplay() {
248283 assertEquals ("async-cached1" , result .get (0 ));
249284 assertEquals ("async-cached2" , result .get (1 ));
250285 }
286+
287+ @ Test
288+ void testStepAsyncWithTypeTokenAndConfig () throws Exception {
289+ var context = createTestContext ();
290+
291+ DurableFuture <List <Integer >> future = context .stepAsync (
292+ "async-numbers" ,
293+ new TypeToken <List <Integer >>() {},
294+ () -> List .of (10 , 20 , 30 ),
295+ StepConfig .builder ()
296+ .retryStrategy (RetryStrategies .Presets .DEFAULT )
297+ .build ());
298+
299+ assertNotNull (future );
300+ List <Integer > result = future .get ();
301+ assertEquals (3 , result .size ());
302+ assertEquals (10 , result .get (0 ));
303+ }
251304}
0 commit comments