1212import org .junit .jupiter .api .Test ;
1313import software .amazon .lambda .durable .model .ExecutionStatus ;
1414import software .amazon .lambda .durable .retry .JitterStrategy ;
15+ import software .amazon .lambda .durable .retry .WaitForConditionWaitStrategy ;
16+ import software .amazon .lambda .durable .retry .WaitStrategies ;
1517import software .amazon .lambda .durable .testing .LocalDurableTestRunner ;
1618
1719class WaitForConditionIntegrationTest {
1820
19- // ---- 5.1: Basic integration tests ----
21+ // ---- Basic integration tests ----
2022
2123 @ Test
2224 void testBasicPollingSucceedsAfterNChecks () {
2325 var checkCount = new AtomicInteger (0 );
2426 var targetCount = 3 ;
2527
2628 var runner = LocalDurableTestRunner .create (String .class , (input , ctx ) -> {
27- var strategy = WaitStrategies .<Integer >builder (state -> state < targetCount )
28- .initialDelay (Duration .ofSeconds (1 ))
29- .jitter (JitterStrategy .NONE )
30- .build ();
29+ var strategy = WaitStrategies .<Integer >exponentialBackoff (
30+ 60 , Duration .ofSeconds (1 ), Duration .ofSeconds (300 ), 1.5 , JitterStrategy .NONE );
3131
32- var config = WaitForConditionConfig .<Integer >builder (strategy , 0 ).build ();
32+ var config = WaitForConditionConfig .<Integer >builder ()
33+ .waitStrategy (strategy )
34+ .build ();
3335
3436 return ctx .waitForCondition (
3537 "poll-counter" ,
3638 Integer .class ,
3739 (state , stepCtx ) -> {
3840 checkCount .incrementAndGet ();
39- return state + 1 ;
41+ var next = state + 1 ;
42+ return next >= targetCount
43+ ? WaitForConditionResult .stopPolling (next )
44+ : WaitForConditionResult .continuePolling (next );
4045 },
46+ 0 ,
4147 config );
4248 });
4349
@@ -51,26 +57,23 @@ void testBasicPollingSucceedsAfterNChecks() {
5157 @ Test
5258 void testCustomWaitStrategy () {
5359 var runner = LocalDurableTestRunner .create (String .class , (input , ctx ) -> {
54- // Custom strategy: stop when state equals "done", fixed 2s delay
55- WaitForConditionWaitStrategy <String > customStrategy = (state , attempt ) -> {
56- if ("done" .equals (state )) {
57- return WaitForConditionDecision .stopPolling ();
58- }
59- return WaitForConditionDecision .continuePolling (Duration .ofSeconds (2 ));
60- };
60+ // Custom strategy: fixed 2s delay
61+ WaitForConditionWaitStrategy <String > customStrategy = (state , attempt ) -> Duration .ofSeconds (2 );
6162
62- var config = WaitForConditionConfig .<String >builder (customStrategy , "pending" )
63+ var config = WaitForConditionConfig .<String >builder ()
64+ .waitStrategy (customStrategy )
6365 .build ();
6466
6567 return ctx .waitForCondition (
6668 "custom-strategy" ,
6769 String .class ,
6870 (state , stepCtx ) -> {
6971 if ("pending" .equals (state )) {
70- return "processing" ;
72+ return WaitForConditionResult . continuePolling ( "processing" ) ;
7173 }
72- return "done" ;
74+ return WaitForConditionResult . stopPolling ( "done" ) ;
7375 },
76+ "pending" ,
7477 config );
7578 });
7679
@@ -83,16 +86,19 @@ void testCustomWaitStrategy() {
8386 @ Test
8487 void testMaxAttemptsExceeded () {
8588 var runner = LocalDurableTestRunner .create (String .class , (input , ctx ) -> {
86- var strategy = WaitStrategies .<String >builder (state -> true ) // always continue
87- .maxAttempts (3 )
88- .initialDelay (Duration .ofSeconds (1 ))
89- .jitter (JitterStrategy .NONE )
90- .build ();
89+ var strategy = WaitStrategies .<String >exponentialBackoff (
90+ 3 , Duration .ofSeconds (1 ), Duration .ofSeconds (300 ), 1.5 , JitterStrategy .NONE );
9191
92- var config =
93- WaitForConditionConfig .<String >builder (strategy , "initial" ).build ();
92+ var config = WaitForConditionConfig .<String >builder ()
93+ .waitStrategy (strategy )
94+ .build ();
9495
95- return ctx .waitForCondition ("max-attempts" , String .class , (state , stepCtx ) -> state , config );
96+ return ctx .waitForCondition (
97+ "max-attempts" ,
98+ String .class ,
99+ (state , stepCtx ) -> WaitForConditionResult .continuePolling (state ),
100+ "initial" ,
101+ config );
96102 });
97103
98104 var result = runner .runUntilComplete ("test" );
@@ -103,20 +109,20 @@ void testMaxAttemptsExceeded() {
103109 @ Test
104110 void testCheckFunctionError () {
105111 var runner = LocalDurableTestRunner .create (String .class , (input , ctx ) -> {
106- var strategy = WaitStrategies .<String >builder (state -> true )
107- .initialDelay (Duration .ofSeconds (1 ))
108- .jitter (JitterStrategy .NONE )
109- .build ();
112+ var strategy = WaitStrategies .<String >exponentialBackoff (
113+ 60 , Duration .ofSeconds (1 ), Duration .ofSeconds (300 ), 1.5 , JitterStrategy .NONE );
110114
111- var config =
112- WaitForConditionConfig .<String >builder (strategy , "initial" ).build ();
115+ var config = WaitForConditionConfig .<String >builder ()
116+ .waitStrategy (strategy )
117+ .build ();
113118
114119 return ctx .waitForCondition (
115120 "error-check" ,
116121 String .class ,
117122 (state , stepCtx ) -> {
118123 throw new IllegalStateException ("Check function failed" );
119124 },
125+ "initial" ,
120126 config );
121127 });
122128
@@ -130,20 +136,24 @@ void testReplayAcrossInvocations() {
130136 var checkCount = new AtomicInteger (0 );
131137
132138 var runner = LocalDurableTestRunner .create (String .class , (input , ctx ) -> {
133- var strategy = WaitStrategies .<Integer >builder (state -> state < 2 )
134- .initialDelay (Duration .ofSeconds (1 ))
135- .jitter (JitterStrategy .NONE )
136- .build ();
139+ var strategy = WaitStrategies .<Integer >exponentialBackoff (
140+ 60 , Duration .ofSeconds (1 ), Duration .ofSeconds (300 ), 1.5 , JitterStrategy .NONE );
137141
138- var config = WaitForConditionConfig .<Integer >builder (strategy , 0 ).build ();
142+ var config = WaitForConditionConfig .<Integer >builder ()
143+ .waitStrategy (strategy )
144+ .build ();
139145
140146 var result = ctx .waitForCondition (
141147 "replay-poll" ,
142148 Integer .class ,
143149 (state , stepCtx ) -> {
144150 checkCount .incrementAndGet ();
145- return state + 1 ;
151+ var next = state + 1 ;
152+ return next >= 2
153+ ? WaitForConditionResult .stopPolling (next )
154+ : WaitForConditionResult .continuePolling (next );
146155 },
156+ 0 ,
147157 config );
148158
149159 return result .toString ();
@@ -162,8 +172,7 @@ void testReplayAcrossInvocations() {
162172 assertEquals (firstCheckCount , checkCount .get ());
163173 }
164174
165- // ---- 5.2: PBT — stopPolling completes with that state as result ----
166- // **Validates: Requirements 1.5, 2.1**
175+ // ---- PBT — isDone=true completes with that state as result ----
167176
168177 @ RepeatedTest (50 )
169178 void propertyStopPollingCompletesWithState () {
@@ -172,28 +181,33 @@ void propertyStopPollingCompletesWithState() {
172181 var target = random .nextInt (1 , 11 );
173182
174183 var runner = LocalDurableTestRunner .create (String .class , (input , ctx ) -> {
175- // Strategy: stop when state reaches target
176- WaitForConditionWaitStrategy <Integer > strategy = (state , attempt ) -> {
177- if (state >= target ) {
178- return WaitForConditionDecision .stopPolling ();
179- }
180- return WaitForConditionDecision .continuePolling (Duration .ofSeconds (1 ));
181- };
184+ var strategy = WaitStrategies .<Integer >fixedDelay (target + 1 , Duration .ofSeconds (1 ));
182185
183- var config = WaitForConditionConfig .<Integer >builder (strategy , 0 ).build ();
186+ var config = WaitForConditionConfig .<Integer >builder ()
187+ .waitStrategy (strategy )
188+ .build ();
184189
185- return ctx .waitForCondition ("stop-polling-prop" , Integer .class , (state , stepCtx ) -> state + 1 , config );
190+ return ctx .waitForCondition (
191+ "stop-polling-prop" ,
192+ Integer .class ,
193+ (state , stepCtx ) -> {
194+ var next = state + 1 ;
195+ return next >= target
196+ ? WaitForConditionResult .stopPolling (next )
197+ : WaitForConditionResult .continuePolling (next );
198+ },
199+ 0 ,
200+ config );
186201 });
187202
188203 var result = runner .runUntilComplete ("test" );
189204
190205 assertEquals (ExecutionStatus .SUCCEEDED , result .getStatus ());
191- // The result should be the state that caused stopPolling — which is target
206+ // The result should be the state that caused isDone=true — which is target
192207 assertEquals (target , result .getResult (Integer .class ));
193208 }
194209
195- // ---- 5.3: PBT — wait strategy receives correct state and attempt ----
196- // **Validates: Requirements 1.3, 2.1**
210+ // ---- PBT — wait strategy receives correct state and attempt ----
197211
198212 @ RepeatedTest (50 )
199213 void propertyWaitStrategyReceivesCorrectStateAndAttempt () {
@@ -207,28 +221,39 @@ void propertyWaitStrategyReceivesCorrectStateAndAttempt() {
207221 WaitForConditionWaitStrategy <Integer > strategy = (state , attempt ) -> {
208222 observedStates .add (state );
209223 observedAttempts .add (attempt );
210- if (attempt + 1 >= totalChecks ) {
211- return WaitForConditionDecision .stopPolling ();
212- }
213- return WaitForConditionDecision .continuePolling (Duration .ofSeconds (1 ));
224+ return Duration .ofSeconds (1 );
214225 };
215226
216- var config = WaitForConditionConfig .<Integer >builder (strategy , 0 ).build ();
227+ var config = WaitForConditionConfig .<Integer >builder ()
228+ .waitStrategy (strategy )
229+ .build ();
217230
218- return ctx .waitForCondition ("state-attempt-prop" , Integer .class , (state , stepCtx ) -> state + 1 , config );
231+ return ctx .waitForCondition (
232+ "state-attempt-prop" ,
233+ Integer .class ,
234+ (state , stepCtx ) -> {
235+ var next = state + 1 ;
236+ return next >= totalChecks
237+ ? WaitForConditionResult .stopPolling (next )
238+ : WaitForConditionResult .continuePolling (next );
239+ },
240+ 0 ,
241+ config );
219242 });
220243
221244 var result = runner .runUntilComplete ("test" );
222245
223246 assertEquals (ExecutionStatus .SUCCEEDED , result .getStatus ());
224247
225- // Verify each strategy call received the correct state and attempt
226- assertEquals (totalChecks , observedStates .size ());
227- assertEquals (totalChecks , observedAttempts .size ());
248+ // The strategy is only called when isDone=false, so it's called totalChecks-1 times
249+ // (the last check returns isDone=true, so the strategy is not called)
250+ var expectedStrategyCalls = totalChecks - 1 ;
251+ assertEquals (expectedStrategyCalls , observedStates .size ());
252+ assertEquals (expectedStrategyCalls , observedAttempts .size ());
228253
229- for (int i = 0 ; i < totalChecks ; i ++) {
254+ for (int i = 0 ; i < expectedStrategyCalls ; i ++) {
230255 // Check function returns state + 1, starting from 0
231- // So after check i, state = i + 1
256+ // So after check i, state = i + 1, and strategy receives that value
232257 assertEquals (i + 1 , observedStates .get (i ), "State at strategy call " + (i + 1 ));
233258 assertEquals (i , observedAttempts .get (i ), "Attempt at strategy call " + (i + 1 ));
234259 }
0 commit comments