Skip to content

Commit 7add8ed

Browse files
Changes to allow retry on IO & WebClientRequest exceptions.
1 parent 2ca554c commit 7add8ed

File tree

3 files changed

+51
-2
lines changed

3 files changed

+51
-2
lines changed

api/src/main/java/ca/bc/gov/educ/api/course/service/RESTService.java

+4-2
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,12 @@
99
import org.springframework.stereotype.Service;
1010
import org.springframework.web.reactive.function.BodyInserters;
1111
import org.springframework.web.reactive.function.client.WebClient;
12+
import org.springframework.web.reactive.function.client.WebClientRequestException;
1213
import org.springframework.web.reactive.function.client.WebClientResponseException;
1314
import reactor.core.publisher.Mono;
1415
import reactor.util.retry.Retry;
1516

17+
import java.io.IOException;
1618
import java.time.Duration;
1719

1820
@Service
@@ -48,7 +50,7 @@ public <T> T get(String url, Class<T> clazz) {
4850
// only does retry if initial error was 5xx as service may be temporarily down
4951
// 4xx errors will always happen if 404, 401, 403 etc, so does not retry
5052
.retryWhen(Retry.backoff(3, Duration.ofSeconds(2))
51-
.filter(ServiceException.class::isInstance)
53+
.filter(ex -> ex instanceof ServiceException || ex instanceof IOException || ex instanceof WebClientRequestException)
5254
.onRetryExhaustedThrow((retryBackoffSpec, retrySignal) -> {
5355
throw new ServiceException(getErrorMessage(url, SERVICE_FAILED_ERROR), HttpStatus.SERVICE_UNAVAILABLE.value());
5456
}))
@@ -76,7 +78,7 @@ public <T> T post(String url, Object body, Class<T> clazz) {
7678
clientResponse -> Mono.error(new ServiceException(getErrorMessage(url, ERROR_5xx), clientResponse.statusCode().value())))
7779
.bodyToMono(clazz)
7880
.retryWhen(Retry.backoff(3, Duration.ofSeconds(2))
79-
.filter(ServiceException.class::isInstance)
81+
.filter(ex -> ex instanceof ServiceException || ex instanceof IOException || ex instanceof WebClientRequestException)
8082
.onRetryExhaustedThrow((retryBackoffSpec, retrySignal) -> {
8183
throw new ServiceException(getErrorMessage(url, SERVICE_FAILED_ERROR), HttpStatus.SERVICE_UNAVAILABLE.value());
8284
}))

api/src/test/java/ca/bc/gov/educ/api/course/service/RESTServiceGETTest.java

+24
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,23 @@
11
package ca.bc.gov.educ.api.course.service;
22

33
import ca.bc.gov.educ.api.course.exception.ServiceException;
4+
import io.netty.channel.ConnectTimeoutException;
45
import org.junit.Assert;
56
import org.junit.Before;
67
import org.junit.Test;
78
import org.junit.runner.RunWith;
89
import org.springframework.beans.factory.annotation.Autowired;
910
import org.springframework.boot.test.context.SpringBootTest;
1011
import org.springframework.boot.test.mock.mockito.MockBean;
12+
import org.springframework.http.HttpHeaders;
13+
import org.springframework.http.HttpMethod;
1114
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService;
1215
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
1316
import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository;
1417
import org.springframework.test.context.ActiveProfiles;
1518
import org.springframework.test.context.junit4.SpringRunner;
1619
import org.springframework.web.reactive.function.client.WebClient;
20+
import org.springframework.web.reactive.function.client.WebClientRequestException;
1721
import reactor.core.publisher.Mono;
1822

1923
import java.util.function.Consumer;
@@ -106,4 +110,24 @@ public void testGetOverride_Given4xxErrorFromService_ExpectServiceError(){
106110
this.restService.get(TEST_URL_403, String.class);
107111
}
108112

113+
@Test(expected = ServiceException.class)
114+
public void testGet_Given5xxErrorFromService_ExpectConnectionError(){
115+
when(requestBodyUriMock.uri(TEST_URL_503)).thenReturn(requestBodyMock);
116+
when(requestBodyMock.retrieve()).thenReturn(responseMock);
117+
118+
Throwable cause = new RuntimeException("Simulated cause");
119+
when(responseMock.bodyToMono(String.class)).thenReturn(Mono.error(new ConnectTimeoutException("Connection closed")));
120+
restService.get(TEST_URL_503, String.class);
121+
}
122+
123+
@Test(expected = ServiceException.class)
124+
public void testGet_Given5xxErrorFromService_ExpectWebClientRequestError(){
125+
when(requestBodyUriMock.uri(TEST_URL_503)).thenReturn(requestBodyMock);
126+
when(requestBodyMock.retrieve()).thenReturn(responseMock);
127+
128+
Throwable cause = new RuntimeException("Simulated cause");
129+
when(responseMock.bodyToMono(String.class)).thenReturn(Mono.error(new WebClientRequestException(cause, HttpMethod.GET, null, new HttpHeaders())));
130+
restService.get(TEST_URL_503, String.class);
131+
}
132+
109133
}

api/src/test/java/ca/bc/gov/educ/api/course/service/RESTServicePOSTTest.java

+23
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package ca.bc.gov.educ.api.course.service;
22

33
import ca.bc.gov.educ.api.course.exception.ServiceException;
4+
import io.netty.channel.ConnectTimeoutException;
45
import org.junit.Assert;
56
import org.junit.Before;
67
import org.junit.Test;
@@ -10,13 +11,16 @@
1011
import org.springframework.beans.factory.annotation.Autowired;
1112
import org.springframework.boot.test.context.SpringBootTest;
1213
import org.springframework.boot.test.mock.mockito.MockBean;
14+
import org.springframework.http.HttpHeaders;
15+
import org.springframework.http.HttpMethod;
1316
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService;
1417
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
1518
import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository;
1619
import org.springframework.test.context.ActiveProfiles;
1720
import org.springframework.test.context.junit4.SpringRunner;
1821
import org.springframework.web.reactive.function.BodyInserter;
1922
import org.springframework.web.reactive.function.client.WebClient;
23+
import org.springframework.web.reactive.function.client.WebClientRequestException;
2024
import reactor.core.publisher.Mono;
2125

2226
import java.util.function.Consumer;
@@ -96,4 +100,23 @@ public void testPostOverride_Given4xxErrorFromService_ExpectServiceError() {
96100
this.restService.post(TEST_URL, TEST_BODY, byte[].class);
97101
}
98102

103+
@Test(expected = ServiceException.class)
104+
public void testPost_Given5xxErrorFromService_ExpectConnectionError(){
105+
when(requestBodyUriMock.uri(TEST_URL)).thenReturn(requestBodyMock);
106+
when(requestBodyMock.retrieve()).thenReturn(responseMock);
107+
108+
when(responseMock.bodyToMono(byte[].class)).thenReturn(Mono.error(new ConnectTimeoutException("Connection closed")));
109+
this.restService.post(TEST_URL, TEST_BODY, byte[].class);
110+
}
111+
112+
@Test(expected = ServiceException.class)
113+
public void testPost_Given5xxErrorFromService_ExpectWebClientRequestError(){
114+
when(requestBodyUriMock.uri(TEST_URL)).thenReturn(requestBodyMock);
115+
when(requestBodyMock.retrieve()).thenReturn(responseMock);
116+
117+
Throwable cause = new RuntimeException("Simulated cause");
118+
when(responseMock.bodyToMono(byte[].class)).thenReturn(Mono.error(new WebClientRequestException(cause, HttpMethod.POST, null, new HttpHeaders())));
119+
this.restService.post(TEST_URL, TEST_BODY, byte[].class);
120+
}
121+
99122
}

0 commit comments

Comments
 (0)