@@ -126,6 +126,97 @@ void withPostProcessorForClass() {
126
126
assertThat (target .counter .get ()).isEqualTo (6 );
127
127
}
128
128
129
+ @ Test
130
+ void adaptReactiveResultWithMinimalRetrySpec () {
131
+ // Test minimal retry configuration: maxAttempts=1, delay=0, jitter=0, multiplier=1.0, maxDelay=0
132
+ MinimalRetryBean target = new MinimalRetryBean ();
133
+ ProxyFactory pf = new ProxyFactory ();
134
+ pf .setTarget (target );
135
+ pf .addAdvice (new SimpleRetryInterceptor (
136
+ new MethodRetrySpec ((m , t ) -> true , 1 , Duration .ZERO , Duration .ZERO , 1.0 , Duration .ZERO )));
137
+ MinimalRetryBean proxy = (MinimalRetryBean ) pf .getProxy ();
138
+
139
+ // Should execute only 2 times, because maxAttempts=1 means 1 call + 1 retry
140
+ assertThatIllegalStateException ().isThrownBy (() -> proxy .retryOperation ().block ())
141
+ .withCauseInstanceOf (IOException .class ).havingCause ().withMessage ("2" );
142
+ assertThat (target .counter .get ()).isEqualTo (2 );
143
+ }
144
+
145
+ @ Test
146
+ void adaptReactiveResultWithZeroDelayAndJitter () {
147
+ // Test case where delay=0 and jitter>0
148
+ ZeroDelayJitterBean target = new ZeroDelayJitterBean ();
149
+ ProxyFactory pf = new ProxyFactory ();
150
+ pf .setTarget (target );
151
+ pf .addAdvice (new SimpleRetryInterceptor (
152
+ new MethodRetrySpec ((m , t ) -> true , 3 , Duration .ZERO , Duration .ofMillis (10 ), 2.0 , Duration .ofMillis (100 ))));
153
+ ZeroDelayJitterBean proxy = (ZeroDelayJitterBean ) pf .getProxy ();
154
+
155
+ assertThatIllegalStateException ().isThrownBy (() -> proxy .retryOperation ().block ())
156
+ .withCauseInstanceOf (IOException .class ).havingCause ().withMessage ("4" );
157
+ assertThat (target .counter .get ()).isEqualTo (4 );
158
+ }
159
+
160
+ @ Test
161
+ void adaptReactiveResultWithJitterGreaterThanDelay () {
162
+ // Test case where jitter > delay
163
+ JitterGreaterThanDelayBean target = new JitterGreaterThanDelayBean ();
164
+ ProxyFactory pf = new ProxyFactory ();
165
+ pf .setTarget (target );
166
+ pf .addAdvice (new SimpleRetryInterceptor (
167
+ new MethodRetrySpec ((m , t ) -> true , 3 , Duration .ofMillis (5 ), Duration .ofMillis (20 ), 1.5 , Duration .ofMillis (50 ))));
168
+ JitterGreaterThanDelayBean proxy = (JitterGreaterThanDelayBean ) pf .getProxy ();
169
+
170
+ assertThatIllegalStateException ().isThrownBy (() -> proxy .retryOperation ().block ())
171
+ .withCauseInstanceOf (IOException .class ).havingCause ().withMessage ("4" );
172
+ assertThat (target .counter .get ()).isEqualTo (4 );
173
+ }
174
+
175
+ @ Test
176
+ void adaptReactiveResultWithFluxMultiValue () {
177
+ // Test Flux multi-value stream case
178
+ FluxMultiValueBean target = new FluxMultiValueBean ();
179
+ ProxyFactory pf = new ProxyFactory ();
180
+ pf .setTarget (target );
181
+ pf .addAdvice (new SimpleRetryInterceptor (
182
+ new MethodRetrySpec ((m , t ) -> true , 3 , Duration .ofMillis (10 ), Duration .ofMillis (5 ), 2.0 , Duration .ofMillis (100 ))));
183
+ FluxMultiValueBean proxy = (FluxMultiValueBean ) pf .getProxy ();
184
+
185
+ assertThatIllegalStateException ().isThrownBy (() -> proxy .retryOperation ().blockFirst ())
186
+ .withCauseInstanceOf (IOException .class ).havingCause ().withMessage ("4" );
187
+ assertThat (target .counter .get ()).isEqualTo (4 );
188
+ }
189
+
190
+ @ Test
191
+ void adaptReactiveResultWithSuccessfulOperation () {
192
+ // Test successful return case, ensuring retry mechanism doesn't activate
193
+ SuccessfulOperationBean target = new SuccessfulOperationBean ();
194
+ ProxyFactory pf = new ProxyFactory ();
195
+ pf .setTarget (target );
196
+ pf .addAdvice (new SimpleRetryInterceptor (
197
+ new MethodRetrySpec ((m , t ) -> true , 5 , Duration .ofMillis (10 ), Duration .ofMillis (5 ), 2.0 , Duration .ofMillis (100 ))));
198
+ SuccessfulOperationBean proxy = (SuccessfulOperationBean ) pf .getProxy ();
199
+
200
+ String result = proxy .retryOperation ().block ();
201
+ assertThat (result ).isEqualTo ("success" );
202
+ // Should execute only once because of successful return
203
+ assertThat (target .counter .get ()).isEqualTo (1 );
204
+ }
205
+
206
+ @ Test
207
+ void adaptReactiveResultWithImmediateFailure () {
208
+ // Test immediate failure case
209
+ ImmediateFailureBean target = new ImmediateFailureBean ();
210
+ ProxyFactory pf = new ProxyFactory ();
211
+ pf .setTarget (target );
212
+ pf .addAdvice (new SimpleRetryInterceptor (
213
+ new MethodRetrySpec ((m , t ) -> true , 3 , Duration .ofMillis (10 ), Duration .ofMillis (5 ), 1.5 , Duration .ofMillis (50 ))));
214
+ ImmediateFailureBean proxy = (ImmediateFailureBean ) pf .getProxy ();
215
+
216
+ assertThatIllegalStateException ().isThrownBy (() -> proxy .retryOperation ().block ())
217
+ .withCauseInstanceOf (RuntimeException .class ).havingCause ().withMessage ("immediate failure" );
218
+ assertThat (target .counter .get ()).isEqualTo (4 );
219
+ }
129
220
130
221
public static class NonAnnotatedBean {
131
222
@@ -193,4 +284,71 @@ public boolean shouldRetry(Method method, Throwable throwable) {
193
284
}
194
285
}
195
286
287
+ // Bean classes for boundary testing
288
+ public static class MinimalRetryBean {
289
+ AtomicInteger counter = new AtomicInteger ();
290
+
291
+ public Mono <Object > retryOperation () {
292
+ return Mono .fromCallable (() -> {
293
+ counter .incrementAndGet ();
294
+ throw new IOException (counter .toString ());
295
+ });
296
+ }
297
+ }
298
+
299
+ public static class ZeroDelayJitterBean {
300
+ AtomicInteger counter = new AtomicInteger ();
301
+
302
+ public Mono <Object > retryOperation () {
303
+ return Mono .fromCallable (() -> {
304
+ counter .incrementAndGet ();
305
+ throw new IOException (counter .toString ());
306
+ });
307
+ }
308
+ }
309
+
310
+ public static class JitterGreaterThanDelayBean {
311
+ AtomicInteger counter = new AtomicInteger ();
312
+
313
+ public Mono <Object > retryOperation () {
314
+ return Mono .fromCallable (() -> {
315
+ counter .incrementAndGet ();
316
+ throw new IOException (counter .toString ());
317
+ });
318
+ }
319
+ }
320
+
321
+ public static class FluxMultiValueBean {
322
+ AtomicInteger counter = new AtomicInteger ();
323
+
324
+ public Flux <Object > retryOperation () {
325
+ return Flux .from (Mono .fromCallable (() -> {
326
+ counter .incrementAndGet ();
327
+ throw new IOException (counter .toString ());
328
+ }));
329
+ }
330
+ }
331
+
332
+ public static class SuccessfulOperationBean {
333
+ AtomicInteger counter = new AtomicInteger ();
334
+
335
+ public Mono <String > retryOperation () {
336
+ return Mono .fromCallable (() -> {
337
+ counter .incrementAndGet ();
338
+ return "success" ;
339
+ });
340
+ }
341
+ }
342
+
343
+ public static class ImmediateFailureBean {
344
+ AtomicInteger counter = new AtomicInteger ();
345
+
346
+ public Mono <Object > retryOperation () {
347
+ return Mono .fromCallable (() -> {
348
+ counter .incrementAndGet ();
349
+ throw new RuntimeException ("immediate failure" );
350
+ });
351
+ }
352
+ }
353
+
196
354
}
0 commit comments