Skip to content

Commit 9ce8299

Browse files
Merge pull request #193 from bcgov/fix/GRAD2-2925
Changes to allow retry on IO & WebClientRequest exceptions.
2 parents 57a9673 + c5b7a6e commit 9ce8299

File tree

4 files changed

+53
-3
lines changed

4 files changed

+53
-3
lines changed

.github/workflows/on.pr.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ jobs:
4747
with:
4848
sarif_file: 'trivy-results.sarif'
4949
- name: Cache SonarCloud packages
50-
uses: actions/cache@v1
50+
uses: actions/cache@v4
5151
with:
5252
path: ~/.sonar/cache
5353
key: ${{ runner.os }}-sonar

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,12 @@
1010
import org.springframework.stereotype.Service;
1111
import org.springframework.web.reactive.function.BodyInserters;
1212
import org.springframework.web.reactive.function.client.WebClient;
13+
import org.springframework.web.reactive.function.client.WebClientRequestException;
1314
import org.springframework.web.reactive.function.client.WebClientResponseException;
1415
import reactor.core.publisher.Mono;
1516
import reactor.util.retry.Retry;
1617

18+
import java.io.IOException;
1719
import java.time.Duration;
1820

1921
@Service
@@ -42,7 +44,7 @@ public <T> T get(String url, Class<T> clazz) {
4244
// only does retry if initial error was 5xx as service may be temporarily down
4345
// 4xx errors will always happen if 404, 401, 403 etc, so does not retry
4446
.retryWhen(Retry.backoff(3, Duration.ofSeconds(2))
45-
.filter(ServiceException.class::isInstance)
47+
.filter(ex -> ex instanceof ServiceException || ex instanceof IOException || ex instanceof WebClientRequestException)
4648
.onRetryExhaustedThrow((retryBackoffSpec, retrySignal) -> {
4749
throw new ServiceException(getErrorMessage(url, SERVICE_FAILED_ERROR), HttpStatus.SERVICE_UNAVAILABLE.value());
4850
}))
@@ -68,7 +70,7 @@ public <T> T post(String url, Object body, Class<T> clazz) {
6870
clientResponse -> Mono.error(new ServiceException(getErrorMessage(url, SERVER_ERROR), clientResponse.statusCode().value())))
6971
.bodyToMono(clazz)
7072
.retryWhen(Retry.backoff(3, Duration.ofSeconds(2))
71-
.filter(ServiceException.class::isInstance)
73+
.filter(ex -> ex instanceof ServiceException || ex instanceof IOException || ex instanceof WebClientRequestException)
7274
.onRetryExhaustedThrow((retryBackoffSpec, retrySignal) -> {
7375
throw new ServiceException(getErrorMessage(url, SERVICE_FAILED_ERROR), HttpStatus.SERVICE_UNAVAILABLE.value());
7476
}))

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

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package ca.bc.gov.educ.api.gradbusiness.service;
22

33
import ca.bc.gov.educ.api.gradbusiness.exception.ServiceException;
4+
import io.netty.channel.ConnectTimeoutException;
45
import org.junit.Assert;
56
import org.junit.Before;
67
import org.junit.Test;
@@ -9,10 +10,13 @@
910
import org.springframework.beans.factory.annotation.Qualifier;
1011
import org.springframework.boot.test.context.SpringBootTest;
1112
import org.springframework.boot.test.mock.mockito.MockBean;
13+
import org.springframework.http.HttpHeaders;
14+
import org.springframework.http.HttpMethod;
1215
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
1316
import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository;
1417
import org.springframework.test.context.junit4.SpringRunner;
1518
import org.springframework.web.reactive.function.client.WebClient;
19+
import org.springframework.web.reactive.function.client.WebClientRequestException;
1620
import reactor.core.publisher.Mono;
1721

1822
import java.util.function.Consumer;
@@ -83,4 +87,25 @@ public void testGetOverride_Given4xxErrorFromService_ExpectServiceError(){
8387
when(this.responseMock.bodyToMono(ServiceException.class)).thenReturn(Mono.just(new ServiceException("Error", 500)));
8488
this.restService.get(TEST_URL_403, String.class);
8589
}
90+
91+
@Test(expected = ServiceException.class)
92+
public void testGet_Given5xxErrorFromService_ExpectConnectionError(){
93+
when(requestBodyUriMock.uri(TEST_URL_503)).thenReturn(requestBodyMock);
94+
when(requestBodyMock.retrieve()).thenReturn(responseMock);
95+
96+
Throwable cause = new RuntimeException("Simulated cause");
97+
when(responseMock.bodyToMono(String.class)).thenReturn(Mono.error(new ConnectTimeoutException("Connection closed")));
98+
restService.get(TEST_URL_503, String.class);
99+
}
100+
101+
@Test(expected = ServiceException.class)
102+
public void testGet_Given5xxErrorFromService_ExpectWebClientRequestError(){
103+
when(requestBodyUriMock.uri(TEST_URL_503)).thenReturn(requestBodyMock);
104+
when(requestBodyMock.retrieve()).thenReturn(responseMock);
105+
106+
Throwable cause = new RuntimeException("Simulated cause");
107+
when(responseMock.bodyToMono(String.class)).thenReturn(Mono.error(new WebClientRequestException(cause, HttpMethod.GET, null, new HttpHeaders())));
108+
restService.get(TEST_URL_503, String.class);
109+
}
110+
86111
}

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

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import ca.bc.gov.educ.api.gradbusiness.exception.ServiceException;
44
import ca.bc.gov.educ.api.gradbusiness.util.ThreadLocalStateUtil;
5+
import io.netty.channel.ConnectTimeoutException;
56
import org.junit.Assert;
67
import org.junit.Before;
78
import org.junit.Test;
@@ -13,11 +14,14 @@
1314
import org.springframework.beans.factory.annotation.Qualifier;
1415
import org.springframework.boot.test.context.SpringBootTest;
1516
import org.springframework.boot.test.mock.mockito.MockBean;
17+
import org.springframework.http.HttpHeaders;
18+
import org.springframework.http.HttpMethod;
1619
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
1720
import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository;
1821
import org.springframework.test.context.junit4.SpringRunner;
1922
import org.springframework.web.reactive.function.BodyInserter;
2023
import org.springframework.web.reactive.function.client.WebClient;
24+
import org.springframework.web.reactive.function.client.WebClientRequestException;
2125
import reactor.core.publisher.Mono;
2226

2327
import java.util.function.Consumer;
@@ -87,4 +91,23 @@ public void testPostOverride_Given4xxErrorFromService_ExpectServiceError() {
8791
this.restService.post(TEST_URL, TEST_BODY, byte[].class);
8892
}
8993

94+
@Test(expected = ServiceException.class)
95+
public void testPost_Given5xxErrorFromService_ExpectConnectionError(){
96+
when(requestBodyUriMock.uri(TEST_URL)).thenReturn(requestBodyMock);
97+
when(requestBodyMock.retrieve()).thenReturn(responseMock);
98+
99+
when(responseMock.bodyToMono(byte[].class)).thenReturn(Mono.error(new ConnectTimeoutException("Connection closed")));
100+
this.restService.post(TEST_URL, TEST_BODY, byte[].class);
101+
}
102+
103+
@Test(expected = ServiceException.class)
104+
public void testPost_Given5xxErrorFromService_ExpectWebClientRequestError(){
105+
when(requestBodyUriMock.uri(TEST_URL)).thenReturn(requestBodyMock);
106+
when(requestBodyMock.retrieve()).thenReturn(responseMock);
107+
108+
Throwable cause = new RuntimeException("Simulated cause");
109+
when(responseMock.bodyToMono(byte[].class)).thenReturn(Mono.error(new WebClientRequestException(cause, HttpMethod.POST, null, new HttpHeaders())));
110+
this.restService.post(TEST_URL, TEST_BODY, byte[].class);
111+
}
112+
90113
}

0 commit comments

Comments
 (0)