Skip to content

Commit 0523dd7

Browse files
leehautsobychacko
authored andcommitted
Add unit tests for RetryUtils class
Signed-off-by: lance <[email protected]>
1 parent 323c590 commit 0523dd7

File tree

2 files changed

+127
-1
lines changed

2 files changed

+127
-1
lines changed

spring-ai-retry/src/main/java/org/springframework/ai/retry/RetryUtils.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ public void handleError(URI url, HttpMethod method, @NonNull ClientHttpResponse
5757
handleError(response);
5858
}
5959

60+
@Override
6061
@SuppressWarnings("removal")
6162
public void handleError(@NonNull ClientHttpResponse response) throws IOException {
6263
if (response.getStatusCode().isError()) {
@@ -82,7 +83,7 @@ public void handleError(@NonNull ClientHttpResponse response) throws IOException
8283
.maxAttempts(10)
8384
.retryOn(TransientAiException.class)
8485
.retryOn(ResourceAccessException.class)
85-
.exponentialBackoff(Duration.ofMillis(2000), 5, Duration.ofMillis(3 * 60000))
86+
.exponentialBackoff(Duration.ofMillis(2000), 5, Duration.ofMillis(3 * 60000L))
8687
.withListener(new RetryListener() {
8788

8889
@Override
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
/*
2+
* Copyright 2023-2025 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.ai.retry;
18+
19+
import java.io.ByteArrayInputStream;
20+
import java.io.IOException;
21+
import java.net.URI;
22+
import java.nio.charset.StandardCharsets;
23+
import java.util.concurrent.atomic.AtomicInteger;
24+
25+
import org.junit.jupiter.api.Test;
26+
27+
import org.springframework.http.HttpMethod;
28+
import org.springframework.http.HttpStatus;
29+
import org.springframework.http.client.ClientHttpResponse;
30+
import org.springframework.retry.support.RetryTemplate;
31+
32+
import static org.junit.jupiter.api.Assertions.assertEquals;
33+
import static org.junit.jupiter.api.Assertions.assertFalse;
34+
import static org.junit.jupiter.api.Assertions.assertThrows;
35+
import static org.mockito.Mockito.mock;
36+
import static org.mockito.Mockito.when;
37+
38+
/**
39+
* RetryUtils test
40+
*
41+
* @author lance
42+
*/
43+
class RetryUtilsTests {
44+
45+
/**
46+
* valid http 4xx
47+
* @throws IOException ex
48+
*/
49+
@Test
50+
void handleError4xx() throws IOException {
51+
try (ClientHttpResponse response = mock(ClientHttpResponse.class)) {
52+
URI url = mock(URI.class);
53+
HttpMethod method = HttpMethod.POST;
54+
55+
when(response.getStatusCode()).thenReturn(HttpStatus.BAD_REQUEST);
56+
when(response.getBody())
57+
.thenReturn(new ByteArrayInputStream("Bad request".getBytes(StandardCharsets.UTF_8)));
58+
59+
assertThrows(NonTransientAiException.class,
60+
() -> RetryUtils.DEFAULT_RESPONSE_ERROR_HANDLER.handleError(url, method, response));
61+
}
62+
}
63+
64+
/**
65+
* valid http 5xx
66+
* @throws IOException ex
67+
*/
68+
@Test
69+
void handleError5xx() throws IOException {
70+
try (ClientHttpResponse response = mock(ClientHttpResponse.class)) {
71+
URI url = mock(URI.class);
72+
HttpMethod method = HttpMethod.POST;
73+
when(response.getStatusCode()).thenReturn(HttpStatus.INTERNAL_SERVER_ERROR);
74+
when(response.getBody())
75+
.thenReturn(new ByteArrayInputStream("Server error".getBytes(StandardCharsets.UTF_8)));
76+
77+
assertThrows(TransientAiException.class,
78+
() -> RetryUtils.DEFAULT_RESPONSE_ERROR_HANDLER.handleError(url, method, response));
79+
}
80+
}
81+
82+
/**
83+
* valid not error
84+
* @throws IOException ex
85+
*/
86+
@Test
87+
void hasError() throws IOException {
88+
try (ClientHttpResponse response = mock(ClientHttpResponse.class)) {
89+
when(response.getStatusCode()).thenReturn(HttpStatus.OK);
90+
when(response.getBody()).thenReturn(new ByteArrayInputStream("success".getBytes(StandardCharsets.UTF_8)));
91+
92+
assertFalse(RetryUtils.DEFAULT_RESPONSE_ERROR_HANDLER.hasError(response));
93+
}
94+
}
95+
96+
@Test
97+
void shortRetryTemplateRetries() {
98+
AtomicInteger counter = new AtomicInteger(0);
99+
RetryTemplate template = RetryUtils.SHORT_RETRY_TEMPLATE;
100+
101+
assertThrows(TransientAiException.class, () -> template.execute(cb -> {
102+
counter.incrementAndGet();
103+
throw new TransientAiException("test fail");
104+
}));
105+
106+
assertEquals(10, counter.get());
107+
}
108+
109+
@Test
110+
void shortRetryTemplateSucceedsBeforeMaxAttempts() {
111+
AtomicInteger counter = new AtomicInteger(0);
112+
RetryTemplate template = RetryUtils.SHORT_RETRY_TEMPLATE;
113+
114+
String result = template.execute(cb -> {
115+
if (counter.incrementAndGet() < 5) {
116+
throw new TransientAiException("test fail");
117+
}
118+
return "success";
119+
});
120+
121+
assertEquals(5, counter.get());
122+
assertEquals("success", result);
123+
}
124+
125+
}

0 commit comments

Comments
 (0)