Skip to content

Commit

Permalink
Adding unit testing for response interceptor
Browse files Browse the repository at this point in the history
  • Loading branch information
sashirestela committed Jan 18, 2025
1 parent 60469b8 commit 15a2627
Show file tree
Hide file tree
Showing 5 changed files with 186 additions and 22 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,16 @@
import java.io.IOException;
import java.io.InputStream;
import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletionException;
import java.util.function.UnaryOperator;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Stream;

import static org.junit.jupiter.api.Assertions.assertEquals;
Expand All @@ -25,6 +33,8 @@ interface HttpProcessorTest {

HttpProcessor getHttpProcessor();

HttpProcessor getHttpProcessor(UnaryOperator<HttpResponseData> responseInterceptor);

void setMocksForString(SyncType syncType, String result) throws IOException, InterruptedException;

void setMocksForBinary(SyncType syncType, InputStream result) throws IOException, InterruptedException;
Expand All @@ -39,6 +49,8 @@ interface HttpProcessorTest {

void setMocksForStreamWithError(Stream<String> result) throws IOException, URISyntaxException;

void setMocksForInterceptor(String result) throws IOException, InterruptedException, URISyntaxException;

void testShutdown();

@BeforeAll
Expand Down Expand Up @@ -336,6 +348,34 @@ default void shouldThrownExceptionWhenCallingStreamingMethodAndServerRespondsWit
assertTrue(nestedException.responseInfo().get().getData().contains("The resource does not exist"));
}

@Test
default void shouldModifyAsyncResponseWhenPassingInterceptor()
throws IOException, InterruptedException, URISyntaxException {
var originalJsonResponse = Files.readString(Path.of("src/test/resources/users.json"), StandardCharsets.UTF_8);
setMocksForInterceptor(originalJsonResponse);

var service = getHttpProcessor(response -> {
var body = response.getBody();
var newBody = transformUsers(body);
response.setBody(newBody);
return response;
}).createProxy(ITest.UserService.class);
var actualUserList = service.getAsyncUsers().join();
var actualUser = actualUserList.get(0);
var expectedUser = ITest.User.builder()
.id(1)
.name("Leanne Graham")
.username("Bret")
.email("[email protected]")
.address("Kulas Light, Apt. 556, Gwenborough")
.phone("1-770-736-8031 x56442")
.website("hildegard.org")
.company("Romaguera-Crona")
.build();

assertEquals(expectedUser, actualUser);
}

@Test
default void shouldExecuteDefaultMethodWhenItIsCalled() {
var service = getHttpProcessor().createProxy(ITest.AsyncService.class);
Expand All @@ -350,4 +390,50 @@ default void shouldShutdownWithoutExceptions() {
testShutdown();
}

private String transformUsers(String jsonInput) {
List<String> flatUsers = new ArrayList<>();
String patternStr = "\"id\":\\s*(\\d+).*?" + // id
"\"name\":\\s*\"([^\"]+)\".*?" + // name
"\"username\":\\s*\"([^\"]+)\".*?" + // username
"\"email\":\\s*\"([^\"]+)\".*?" + // email
"\"street\":\\s*\"([^\"]+)\".*?" + // street
"\"suite\":\\s*\"([^\"]+)\".*?" + // suite
"\"city\":\\s*\"([^\"]+)\".*?" + // city
"\"phone\":\\s*\"([^\"]+)\".*?" + // phone
"\"website\":\\s*\"([^\"]+)\".*?" + // website
"\"company\":\\s*\\{\\s*\"name\":\\s*\"([^\"]+)\""; // company name
Pattern pattern = Pattern.compile(patternStr, Pattern.DOTALL);
Matcher matcher = pattern.matcher(jsonInput);
while (matcher.find()) {
String flatUser = String.format(
"{\n" +
" \"id\": %s,\n" +
" \"name\": \"%s\",\n" +
" \"username\": \"%s\",\n" +
" \"email\": \"%s\",\n" +
" \"address\": \"%s, %s, %s\",\n" +
" \"phone\": \"%s\",\n" +
" \"website\": \"%s\",\n" +
" \"company\": \"%s\"\n" +
"}",
matcher.group(1), // id
matcher.group(2), // name
matcher.group(3), // username
matcher.group(4), // email
matcher.group(5), // street
matcher.group(6), // suite
matcher.group(7), // city
matcher.group(8), // phone
matcher.group(9), // website
matcher.group(10) // company name
);
flatUsers.add(flatUser);
}
if (flatUsers.isEmpty()) {
System.err.println("No matches found in input: " + jsonInput);
return "[]";
}
return "[\n " + String.join(",\n ", flatUsers) + "\n]";
}

}
30 changes: 30 additions & 0 deletions src/test/java/io/github/sashirestela/cleverclient/http/ITest.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
import io.github.sashirestela.cleverclient.annotation.Resource;
import io.github.sashirestela.cleverclient.annotation.StreamType;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;
Expand Down Expand Up @@ -146,6 +148,17 @@ default Demo getDemo(Integer demoId) {

}

@Resource("/users")
interface UserService {

@GET
List<User> getSyncUsers();

@GET
CompletableFuture<List<User>> getAsyncUsers();

}

@NoArgsConstructor
@AllArgsConstructor
@Getter
Expand Down Expand Up @@ -199,4 +212,21 @@ static class MultipartClass {

}

@NoArgsConstructor
@AllArgsConstructor
@Builder
@Data
static class User {

private Integer id;
private String name;
private String username;
private String email;
private String address;
private String phone;
private String website;
private String company;

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executors;
import java.util.function.UnaryOperator;
import java.util.stream.Stream;

import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
Expand All @@ -27,20 +28,18 @@
class JavaHttpProcessorTest implements HttpProcessorTest {

HttpProcessor httpProcessor;
HttpClient httpClient = mock(HttpClient.class);
HttpResponse<String> httpResponse = mock(HttpResponse.class);
HttpResponse<Stream<String>> httpResponseStream = mock(HttpResponse.class);
HttpResponse<InputStream> httpResponseBinary = mock(HttpResponse.class);
HttpRequest httpRequest = mock(HttpRequest.class);
HttpHeaders httpHeaders = mock(HttpHeaders.class);
HttpClient httpClient;
HttpResponse<String> httpResponse;
HttpResponse<Stream<String>> httpResponseStream;
HttpResponse<InputStream> httpResponseBinary;
HttpRequest httpRequest;

JavaHttpProcessorTest() {
httpClient = mock(HttpClient.class);
httpResponse = mock(HttpResponse.class);
httpResponseStream = mock(HttpResponse.class);
httpResponseBinary = mock(HttpResponse.class);
httpRequest = mock(HttpRequest.class);
httpHeaders = mock(HttpHeaders.class);
}

@Override
Expand All @@ -53,6 +52,18 @@ public HttpProcessor getHttpProcessor() {
return httpProcessor;
}

@Override
public HttpProcessor getHttpProcessor(UnaryOperator<HttpResponseData> responseInterceptor) {
var clientAdapter = new JavaHttpClientAdapter(httpClient);
clientAdapter.setResponseInterceptor(responseInterceptor);
httpProcessor = HttpProcessor.builder()
.baseUrl("https://api.demo")
.headers(List.of())
.clientAdapter(clientAdapter)
.build();
return httpProcessor;
}

@Override
public void setMocksForString(SyncType syncType, String result) throws IOException, InterruptedException {
if (syncType == SyncType.SYNC) {
Expand Down Expand Up @@ -110,44 +121,54 @@ public void setMocksForException() throws IOException, InterruptedException {
public void setMocksForStringWithError(String result) throws IOException, URISyntaxException {
when(httpClient.sendAsync(any(HttpRequest.class), any(HttpResponse.BodyHandlers.ofString().getClass())))
.thenReturn(CompletableFuture.completedFuture(httpResponse));
when(httpHeaders.map()).thenReturn(Map.of());
when(httpRequest.method()).thenReturn("GET");
when(httpRequest.uri()).thenReturn(new URI("https://api.com"));
when(httpRequest.headers()).thenReturn(httpHeaders);
when(httpRequest.headers()).thenReturn(HttpHeaders.of(Map.of(), (t, s) -> true));
when(httpResponse.statusCode()).thenReturn(HttpURLConnection.HTTP_NOT_FOUND);
when(httpResponse.body()).thenReturn(result);
when(httpResponse.headers()).thenReturn(httpHeaders);
when(httpResponse.headers()).thenReturn(HttpHeaders.of(Map.of(), (t, s) -> true));
when(httpResponse.request()).thenReturn(httpRequest);
}

@Override
public void setMocksForBinaryWithError(InputStream result) throws IOException, URISyntaxException {
when(httpClient.sendAsync(any(HttpRequest.class), any(HttpResponse.BodyHandlers.ofInputStream().getClass())))
.thenReturn(CompletableFuture.completedFuture(httpResponseBinary));
when(httpHeaders.map()).thenReturn(Map.of());
when(httpRequest.method()).thenReturn("GET");
when(httpRequest.uri()).thenReturn(new URI("https://api.com"));
when(httpRequest.headers()).thenReturn(httpHeaders);
when(httpRequest.headers()).thenReturn(HttpHeaders.of(Map.of(), (t, s) -> true));
when(httpResponseBinary.statusCode()).thenReturn(HttpURLConnection.HTTP_NOT_FOUND);
when(httpResponseBinary.body()).thenReturn(result);
when(httpResponseBinary.headers()).thenReturn(httpHeaders);
when(httpResponseBinary.headers()).thenReturn(HttpHeaders.of(Map.of(), (t, s) -> true));
when(httpResponseBinary.request()).thenReturn(httpRequest);
}

@Override
public void setMocksForStreamWithError(Stream<String> result) throws IOException, URISyntaxException {
when(httpClient.sendAsync(any(HttpRequest.class), any(HttpResponse.BodyHandlers.ofLines().getClass())))
.thenReturn(CompletableFuture.completedFuture(httpResponseStream));
when(httpHeaders.map()).thenReturn(Map.of());
when(httpRequest.method()).thenReturn("GET");
when(httpRequest.uri()).thenReturn(new URI("https://api.com"));
when(httpRequest.headers()).thenReturn(httpHeaders);
when(httpRequest.headers()).thenReturn(HttpHeaders.of(Map.of(), (t, s) -> true));
when(httpResponseStream.statusCode()).thenReturn(HttpURLConnection.HTTP_NOT_FOUND);
when(httpResponseStream.body()).thenReturn(result);
when(httpResponseStream.headers()).thenReturn(httpHeaders);
when(httpResponseStream.headers()).thenReturn(HttpHeaders.of(Map.of(), (t, s) -> true));
when(httpResponseStream.request()).thenReturn(httpRequest);
}

@Override
public void setMocksForInterceptor(String result) throws IOException, InterruptedException, URISyntaxException {
when(httpClient.sendAsync(any(HttpRequest.class), any(HttpResponse.BodyHandlers.ofString().getClass())))
.thenReturn(CompletableFuture.completedFuture(httpResponse));
when(httpRequest.method()).thenReturn("GET");
when(httpRequest.uri()).thenReturn(new URI("https://api.com"));
when(httpRequest.headers()).thenReturn(HttpHeaders.of(Map.of(), (t, s) -> true));
when(httpResponse.statusCode()).thenReturn(HttpURLConnection.HTTP_OK);
when(httpResponse.headers()).thenReturn(HttpHeaders.of(Map.of(), (t, s) -> true));
when(httpResponse.body()).thenReturn(result);
when(httpResponse.request()).thenReturn(httpRequest);
}

@Override
public void testShutdown() {
var defaultAdapter = new JavaHttpClientAdapter();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Map;
import java.util.function.UnaryOperator;
import java.util.stream.Collectors;
import java.util.stream.Stream;

Expand Down Expand Up @@ -55,10 +56,21 @@ public HttpProcessor getHttpProcessor() {
return httpProcessor;
}

@Override
public HttpProcessor getHttpProcessor(UnaryOperator<HttpResponseData> responseInterceptor) {
var clientAdapter = new OkHttpClientAdapter(okHttpClient);
clientAdapter.setResponseInterceptor(responseInterceptor);
httpProcessor = HttpProcessor.builder()
.baseUrl("https://api.demo")
.headers(List.of())
.clientAdapter(clientAdapter)
.build();
return httpProcessor;
}

@Override
public void setMocksForString(SyncType syncType, String result) throws IOException {
when(okHttpClient.newCall(any(okhttp3.Request.class))).thenReturn(call);

if (syncType == SyncType.SYNC) {
when(call.execute()).thenReturn(okHttpResponse);
} else {
Expand All @@ -68,7 +80,6 @@ public void setMocksForString(SyncType syncType, String result) throws IOExcepti
return null;
}).when(call).enqueue(any(Callback.class));
}

when(okHttpResponse.code()).thenReturn(HttpURLConnection.HTTP_OK);
when(okHttpResponse.headers()).thenReturn(okhttp3.Headers.of(Map.of()));
when(okHttpResponse.body()).thenReturn(responseBody);
Expand All @@ -78,7 +89,6 @@ public void setMocksForString(SyncType syncType, String result) throws IOExcepti
@Override
public void setMocksForBinary(SyncType syncType, InputStream result) throws IOException {
when(okHttpClient.newCall(any(okhttp3.Request.class))).thenReturn(call);

if (syncType == SyncType.SYNC) {
when(call.execute()).thenReturn(okHttpResponse);
} else {
Expand All @@ -88,7 +98,6 @@ public void setMocksForBinary(SyncType syncType, InputStream result) throws IOEx
return null;
}).when(call).enqueue(any(Callback.class));
}

when(okHttpResponse.code()).thenReturn(HttpURLConnection.HTTP_OK);
when(okHttpResponse.headers()).thenReturn(okhttp3.Headers.of(Map.of()));
when(okHttpResponse.body()).thenReturn(responseBody);
Expand All @@ -98,7 +107,6 @@ public void setMocksForBinary(SyncType syncType, InputStream result) throws IOEx
@Override
public void setMocksForStream(SyncType syncType, Stream<String> result) throws IOException {
when(okHttpClient.newCall(any(okhttp3.Request.class))).thenReturn(call);

if (syncType == SyncType.SYNC) {
when(call.execute()).thenReturn(okHttpResponse);
} else {
Expand All @@ -108,7 +116,6 @@ public void setMocksForStream(SyncType syncType, Stream<String> result) throws I
return null;
}).when(call).enqueue(any(Callback.class));
}

when(okHttpResponse.code()).thenReturn(HttpURLConnection.HTTP_OK);
when(okHttpResponse.headers()).thenReturn(okhttp3.Headers.of(Map.of()));
when(okHttpResponse.body()).thenReturn(responseBody);
Expand Down Expand Up @@ -180,6 +187,25 @@ public void setMocksForStreamWithError(Stream<String> result) throws IOException
when(okHttpResponse.request()).thenReturn(okHttpRequest);
}

@Override
public void setMocksForInterceptor(String result) throws IOException {
when(okHttpClient.newCall(any(okhttp3.Request.class))).thenReturn(call);
doAnswer(invocation -> {
Callback callback = invocation.getArgument(0);
callback.onResponse(call, okHttpResponse);
return null;
}).when(call).enqueue(any(Callback.class));
when(httpUrl.toString()).thenReturn("https://api.com");
when(okHttpRequest.method()).thenReturn("GET");
when(okHttpRequest.url()).thenReturn(httpUrl);
when(okHttpRequest.headers()).thenReturn(okhttp3.Headers.of(Map.of()));
when(okHttpResponse.code()).thenReturn(HttpURLConnection.HTTP_OK);
when(okHttpResponse.headers()).thenReturn(okhttp3.Headers.of(Map.of()));
when(okHttpResponse.body()).thenReturn(responseBody);
when(responseBody.string()).thenReturn(result);
when(okHttpResponse.request()).thenReturn(okHttpRequest);
}

@Override
public void testShutdown() {
var defaultAdapter = new OkHttpClientAdapter();
Expand Down
1 change: 1 addition & 0 deletions src/test/resources/users.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[{"id":1,"name":"Leanne Graham","username":"Bret","email":"[email protected]","address":{"street":"Kulas Light","suite":"Apt. 556","city":"Gwenborough","zipcode":"92998-3874","geo":{"lat":"-37.3159","lng":"81.1496"}},"phone":"1-770-736-8031 x56442","website":"hildegard.org","company":{"name":"Romaguera-Crona","catchPhrase":"Multi-layered client-server neural-net","bs":"harness real-time e-markets"}},{"id":2,"name":"Ervin Howell","username":"Antonette","email":"[email protected]","address":{"street":"Victor Plains","suite":"Suite 879","city":"Wisokyburgh","zipcode":"90566-7771","geo":{"lat":"-43.9509","lng":"-34.4618"}},"phone":"010-692-6593 x09125","website":"anastasia.net","company":{"name":"Deckow-Crist","catchPhrase":"Proactive didactic contingency","bs":"synergize scalable supply-chains"}}]

0 comments on commit 15a2627

Please sign in to comment.