diff --git a/src/test/java/io/github/sashirestela/cleverclient/http/HttpProcessorTest.java b/src/test/java/io/github/sashirestela/cleverclient/http/HttpProcessorTest.java index 2f839ec..59a3ce1 100644 --- a/src/test/java/io/github/sashirestela/cleverclient/http/HttpProcessorTest.java +++ b/src/test/java/io/github/sashirestela/cleverclient/http/HttpProcessorTest.java @@ -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; @@ -25,6 +33,8 @@ interface HttpProcessorTest { HttpProcessor getHttpProcessor(); + HttpProcessor getHttpProcessor(UnaryOperator responseInterceptor); + void setMocksForString(SyncType syncType, String result) throws IOException, InterruptedException; void setMocksForBinary(SyncType syncType, InputStream result) throws IOException, InterruptedException; @@ -39,6 +49,8 @@ interface HttpProcessorTest { void setMocksForStreamWithError(Stream result) throws IOException, URISyntaxException; + void setMocksForInterceptor(String result) throws IOException, InterruptedException, URISyntaxException; + void testShutdown(); @BeforeAll @@ -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("Sincere@april.biz") + .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); @@ -350,4 +390,50 @@ default void shouldShutdownWithoutExceptions() { testShutdown(); } + private String transformUsers(String jsonInput) { + List 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]"; + } + } diff --git a/src/test/java/io/github/sashirestela/cleverclient/http/ITest.java b/src/test/java/io/github/sashirestela/cleverclient/http/ITest.java index 730c168..d4d8afe 100644 --- a/src/test/java/io/github/sashirestela/cleverclient/http/ITest.java +++ b/src/test/java/io/github/sashirestela/cleverclient/http/ITest.java @@ -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; @@ -146,6 +148,17 @@ default Demo getDemo(Integer demoId) { } + @Resource("/users") + interface UserService { + + @GET + List getSyncUsers(); + + @GET + CompletableFuture> getAsyncUsers(); + + } + @NoArgsConstructor @AllArgsConstructor @Getter @@ -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; + + } + } diff --git a/src/test/java/io/github/sashirestela/cleverclient/http/JavaHttpProcessorTest.java b/src/test/java/io/github/sashirestela/cleverclient/http/JavaHttpProcessorTest.java index 93cf9de..cc006bd 100644 --- a/src/test/java/io/github/sashirestela/cleverclient/http/JavaHttpProcessorTest.java +++ b/src/test/java/io/github/sashirestela/cleverclient/http/JavaHttpProcessorTest.java @@ -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; @@ -27,12 +28,11 @@ class JavaHttpProcessorTest implements HttpProcessorTest { HttpProcessor httpProcessor; - HttpClient httpClient = mock(HttpClient.class); - HttpResponse httpResponse = mock(HttpResponse.class); - HttpResponse> httpResponseStream = mock(HttpResponse.class); - HttpResponse httpResponseBinary = mock(HttpResponse.class); - HttpRequest httpRequest = mock(HttpRequest.class); - HttpHeaders httpHeaders = mock(HttpHeaders.class); + HttpClient httpClient; + HttpResponse httpResponse; + HttpResponse> httpResponseStream; + HttpResponse httpResponseBinary; + HttpRequest httpRequest; JavaHttpProcessorTest() { httpClient = mock(HttpClient.class); @@ -40,7 +40,6 @@ class JavaHttpProcessorTest implements HttpProcessorTest { httpResponseStream = mock(HttpResponse.class); httpResponseBinary = mock(HttpResponse.class); httpRequest = mock(HttpRequest.class); - httpHeaders = mock(HttpHeaders.class); } @Override @@ -53,6 +52,18 @@ public HttpProcessor getHttpProcessor() { return httpProcessor; } + @Override + public HttpProcessor getHttpProcessor(UnaryOperator 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) { @@ -110,13 +121,12 @@ 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); } @@ -124,13 +134,12 @@ public void setMocksForStringWithError(String result) throws IOException, URISyn 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); } @@ -138,16 +147,28 @@ public void setMocksForBinaryWithError(InputStream result) throws IOException, U public void setMocksForStreamWithError(Stream 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(); diff --git a/src/test/java/io/github/sashirestela/cleverclient/http/OkHttpProcessorTest.java b/src/test/java/io/github/sashirestela/cleverclient/http/OkHttpProcessorTest.java index 43ba99f..913c29e 100644 --- a/src/test/java/io/github/sashirestela/cleverclient/http/OkHttpProcessorTest.java +++ b/src/test/java/io/github/sashirestela/cleverclient/http/OkHttpProcessorTest.java @@ -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; @@ -55,10 +56,21 @@ public HttpProcessor getHttpProcessor() { return httpProcessor; } + @Override + public HttpProcessor getHttpProcessor(UnaryOperator 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 { @@ -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); @@ -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 { @@ -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); @@ -98,7 +107,6 @@ public void setMocksForBinary(SyncType syncType, InputStream result) throws IOEx @Override public void setMocksForStream(SyncType syncType, Stream result) throws IOException { when(okHttpClient.newCall(any(okhttp3.Request.class))).thenReturn(call); - if (syncType == SyncType.SYNC) { when(call.execute()).thenReturn(okHttpResponse); } else { @@ -108,7 +116,6 @@ public void setMocksForStream(SyncType syncType, Stream 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); @@ -180,6 +187,25 @@ public void setMocksForStreamWithError(Stream 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(); diff --git a/src/test/resources/users.json b/src/test/resources/users.json new file mode 100644 index 0000000..51623d3 --- /dev/null +++ b/src/test/resources/users.json @@ -0,0 +1 @@ +[{"id":1,"name":"Leanne Graham","username":"Bret","email":"Sincere@april.biz","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":"Shanna@melissa.tv","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"}}] \ No newline at end of file