diff --git a/core/src/test/java/feign/JavaLoggerTest.java b/core/src/test/java/feign/JavaLoggerTest.java index f05d839e1..3799d79b4 100644 --- a/core/src/test/java/feign/JavaLoggerTest.java +++ b/core/src/test/java/feign/JavaLoggerTest.java @@ -43,8 +43,7 @@ void rebuffersResponseBodyWhenJulLevelIsInfo() throws Exception { Response.builder() .status(200) .reason("OK") - .request( - Request.create(HttpMethod.GET, "/api", Collections.emptyMap(), null, Util.UTF_8)) + .request(Request.create(HttpMethod.GET, "/api", Collections.emptyMap(), null, null)) .headers(Collections.>emptyMap()) .body("{\"error\":\"test\"}", Util.UTF_8) .build(); @@ -69,8 +68,7 @@ void rebuffersResponseBodyWhenJulLevelIsWarning() throws Exception { Response.builder() .status(500) .reason("Internal Server Error") - .request( - Request.create(HttpMethod.GET, "/api", Collections.emptyMap(), null, Util.UTF_8)) + .request(Request.create(HttpMethod.GET, "/api", Collections.emptyMap(), null, null)) .headers(Collections.>emptyMap()) .body("{\"message\":\"error details\"}", Util.UTF_8) .build(); @@ -95,8 +93,7 @@ void responseBodyReadableMultipleTimesForErrorDecoder() throws Exception { .status(400) .reason("Bad Request") .request( - Request.create( - HttpMethod.POST, "/api/submit", Collections.emptyMap(), null, Util.UTF_8)) + Request.create(HttpMethod.POST, "/api/submit", Collections.emptyMap(), null, null)) .headers(Collections.>emptyMap()) .body(originalBody, Util.UTF_8) .build(); diff --git a/core/src/test/java/feign/LoggerRebufferTest.java b/core/src/test/java/feign/LoggerRebufferTest.java index 5928a8f25..ab1e1f230 100644 --- a/core/src/test/java/feign/LoggerRebufferTest.java +++ b/core/src/test/java/feign/LoggerRebufferTest.java @@ -43,8 +43,7 @@ void headersLevelRebuffersResponseBody() throws Exception { .status(404) .reason("Not Found") .request( - Request.create( - HttpMethod.GET, "/api/resource", Collections.emptyMap(), null, Util.UTF_8)) + Request.create(HttpMethod.GET, "/api/resource", Collections.emptyMap(), null, null)) .headers(Collections.>emptyMap()) .body(originalBody, Util.UTF_8) .build(); @@ -70,8 +69,7 @@ void basicLevelDoesNotRebufferResponseBody() throws Exception { .status(200) .reason("OK") .request( - Request.create( - HttpMethod.GET, "/api/resource", Collections.emptyMap(), null, Util.UTF_8)) + Request.create(HttpMethod.GET, "/api/resource", Collections.emptyMap(), null, null)) .headers(Collections.>emptyMap()) .body(originalBody, Util.UTF_8) .build(); @@ -94,8 +92,7 @@ void fullLevelRebuffersResponseBody() throws Exception { .status(200) .reason("OK") .request( - Request.create( - HttpMethod.POST, "/api/create", Collections.emptyMap(), null, Util.UTF_8)) + Request.create(HttpMethod.POST, "/api/create", Collections.emptyMap(), null, null)) .headers(Collections.>emptyMap()) .body(originalBody, Util.UTF_8) .build(); @@ -122,8 +119,7 @@ void noneLevelDoesNotRebufferResponseBody() throws Exception { .status(200) .reason("OK") .request( - Request.create( - HttpMethod.GET, "/api/status", Collections.emptyMap(), null, Util.UTF_8)) + Request.create(HttpMethod.GET, "/api/status", Collections.emptyMap(), null, null)) .headers(Collections.>emptyMap()) .body(originalBody, Util.UTF_8) .build(); @@ -146,7 +142,7 @@ void http204DoesNotRebufferEvenAtHeadersLevel() throws Exception { .reason("No Content") .request( Request.create( - HttpMethod.DELETE, "/api/resource/1", Collections.emptyMap(), null, Util.UTF_8)) + HttpMethod.DELETE, "/api/resource/1", Collections.emptyMap(), null, null)) .headers(Collections.>emptyMap()) .body("should be ignored", Util.UTF_8) .build(); @@ -168,8 +164,7 @@ void http205DoesNotRebufferEvenAtHeadersLevel() throws Exception { .status(205) .reason("Reset Content") .request( - Request.create( - HttpMethod.POST, "/api/form", Collections.emptyMap(), null, Util.UTF_8)) + Request.create(HttpMethod.POST, "/api/form", Collections.emptyMap(), null, null)) .headers(Collections.>emptyMap()) .body("should be ignored", Util.UTF_8) .build(); @@ -192,7 +187,7 @@ void nullBodyHandledCorrectlyAtHeadersLevel() throws Exception { .reason("OK") .request( Request.create( - HttpMethod.HEAD, "/api/resource", Collections.emptyMap(), null, Util.UTF_8)) + HttpMethod.HEAD, "/api/resource", Collections.emptyMap(), null, null)) .headers(Collections.>emptyMap()) .body((byte[]) null) .build(); diff --git a/java11/src/test/java/feign/http2client/test/Http2ClientTest.java b/java11/src/test/java/feign/http2client/test/Http2ClientTest.java index a769bda6b..4f3602322 100644 --- a/java11/src/test/java/feign/http2client/test/Http2ClientTest.java +++ b/java11/src/test/java/feign/http2client/test/Http2ClientTest.java @@ -26,16 +26,20 @@ import feign.http2client.Http2Client; import java.io.ByteArrayInputStream; import java.io.IOException; +import java.io.InputStream; +import java.io.UncheckedIOException; +import java.net.InetAddress; +import java.net.ServerSocket; +import java.net.Socket; import java.net.http.HttpTimeoutException; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; import okhttp3.mockwebserver.MockResponse; import okhttp3.mockwebserver.RecordedRequest; -import org.json.JSONException; import org.junit.jupiter.api.Test; -import org.skyscreamer.jsonassert.JSONAssert; /** Tests client-specific behavior, such as ensuring Content-Length is sent when specified. */ public class Http2ClientTest extends AbstractClientTest { @@ -83,17 +87,39 @@ public interface TestInterface { @Override @Test public void patch() throws Exception { + server.enqueue(new MockResponse().setBody("foo")); + final TestInterface api = - newBuilder().target(TestInterface.class, "https://nghttp2.org/httpbin/"); - assertThat(api.patch("")).contains("https://nghttp2.org/httpbin/patch"); + newBuilder().target(TestInterface.class, "http://localhost:" + server.getPort()); + + assertThat(api.patch("some text")).isEqualTo("foo"); + + MockWebServerAssertions.assertThat(server.takeRequest()) + .hasMethod("PATCH") + .hasPath("/patch") + .hasBody("some text"); } @Override @Test public void noResponseBodyForPatch() { + server.enqueue(new MockResponse()); + final TestInterface api = - newBuilder().target(TestInterface.class, "https://nghttp2.org/httpbin/"); - assertThat(api.patch()).contains("https://nghttp2.org/httpbin/patch"); + newBuilder().target(TestInterface.class, "http://localhost:" + server.getPort()); + + assertThat(api.patch()).isEmpty(); + + MockWebServerAssertions.assertThat(takeRequest()).hasMethod("PATCH").hasPath("/patch"); + } + + private RecordedRequest takeRequest() { + try { + return server.takeRequest(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new IllegalStateException(e); + } } @Override @@ -150,21 +176,61 @@ void timeoutTest() { } @Test - void getWithRequestBody() throws JSONException { - final TestInterface api = - newBuilder().target(TestInterface.class, "https://nghttp2.org/httpbin/"); - String result = api.getWithBody(); - String expected = "{ \"data\":\"some request body\" }"; - JSONAssert.assertEquals(expected, result, false); + void getWithRequestBody() throws Exception { + // MockWebServer rejects GET requests carrying a body ("Request must not have a body"), + // so this test runs against a minimal local socket server instead. + try (ServerSocket httpServer = new ServerSocket(0, 1, InetAddress.getLoopbackAddress())) { + final AtomicReference receivedRequest = new AtomicReference<>(); + final Thread serverThread = + new Thread( + () -> { + try (Socket socket = httpServer.accept()) { + socket.setSoTimeout(5000); + final InputStream in = socket.getInputStream(); + final StringBuilder request = new StringBuilder(); + final byte[] buffer = new byte[8192]; + while (!request.toString().contains("some request body")) { + final int read = in.read(buffer); + if (read == -1) { + break; + } + request.append(new String(buffer, 0, read, UTF_8)); + } + receivedRequest.set(request.toString()); + socket + .getOutputStream() + .write("HTTP/1.1 200 OK\r\nContent-Length: 3\r\n\r\nfoo".getBytes(UTF_8)); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + }); + serverThread.start(); + + final TestInterface api = + newBuilder().target(TestInterface.class, "http://localhost:" + httpServer.getLocalPort()); + + assertThat(api.getWithBody()).isEqualTo("foo"); + + serverThread.join(5000); + assertThat(receivedRequest.get()) + .startsWith("GET /anything HTTP/1.1") + .contains("some request body"); + } } @Test - void deleteWithRequestBody() throws JSONException { + void deleteWithRequestBody() throws Exception { + server.enqueue(new MockResponse().setBody("foo")); + final TestInterface api = - newBuilder().target(TestInterface.class, "https://nghttp2.org/httpbin/"); - String result = api.deleteWithBody(); - String expected = "{ \"data\":\"some request body\" }"; - JSONAssert.assertEquals(expected, result, false); + newBuilder().target(TestInterface.class, "http://localhost:" + server.getPort()); + + assertThat(api.deleteWithBody()).isEqualTo("foo"); + + MockWebServerAssertions.assertThat(server.takeRequest()) + .hasMethod("DELETE") + .hasPath("/anything") + .hasBody("some request body"); } @Override diff --git a/slf4j/src/test/java/feign/slf4j/Slf4jLoggerTest.java b/slf4j/src/test/java/feign/slf4j/Slf4jLoggerTest.java index 12973ff4e..6df54f9ef 100644 --- a/slf4j/src/test/java/feign/slf4j/Slf4jLoggerTest.java +++ b/slf4j/src/test/java/feign/slf4j/Slf4jLoggerTest.java @@ -21,6 +21,7 @@ import feign.Request.HttpMethod; import feign.RequestTemplate; import feign.Response; +import feign.Util; import java.util.Collection; import java.util.Collections; import org.junit.jupiter.api.Test; @@ -124,8 +125,7 @@ void rebuffersResponseBodyWhenLogLevelIsInfo() throws Exception { Response.builder() .status(200) .reason("OK") - .request( - Request.create(HttpMethod.GET, "/api", Collections.emptyMap(), null, Util.UTF_8)) + .request(Request.create(HttpMethod.GET, "/api", Collections.emptyMap(), null, null)) .headers(Collections.>emptyMap()) .body("{\"error\":\"test\"}", Util.UTF_8) .build(); @@ -150,8 +150,7 @@ void rebuffersResponseBodyWhenLogLevelIsFull() throws Exception { Response.builder() .status(500) .reason("Internal Server Error") - .request( - Request.create(HttpMethod.GET, "/api", Collections.emptyMap(), null, Util.UTF_8)) + .request(Request.create(HttpMethod.GET, "/api", Collections.emptyMap(), null, null)) .headers(Collections.>emptyMap()) .body("{\"message\":\"error details\"}", Util.UTF_8) .build(); @@ -177,8 +176,7 @@ void responseBodyReadableMultipleTimes() throws Exception { .status(400) .reason("Bad Request") .request( - Request.create( - HttpMethod.POST, "/api/submit", Collections.emptyMap(), null, Util.UTF_8)) + Request.create(HttpMethod.POST, "/api/submit", Collections.emptyMap(), null, null)) .headers(Collections.>emptyMap()) .body(originalBody, Util.UTF_8) .build(); diff --git a/validation-jakarta/src/test/java/feign/validation/BeanValidationMethodInterceptorTest.java b/validation-jakarta/src/test/java/feign/validation/BeanValidationMethodInterceptorTest.java index b2c977a24..36748ac29 100644 --- a/validation-jakarta/src/test/java/feign/validation/BeanValidationMethodInterceptorTest.java +++ b/validation-jakarta/src/test/java/feign/validation/BeanValidationMethodInterceptorTest.java @@ -20,6 +20,7 @@ import feign.Feign; import feign.Param; +import feign.Request; import feign.RequestLine; import jakarta.validation.ConstraintViolationException; import jakarta.validation.Valid; @@ -76,7 +77,8 @@ interface Api { private Api api() { return Feign.builder() - .encoder((object, bodyType, template) -> template.body(String.valueOf(object))) + .encoder( + (object, bodyType, template) -> template.body(Request.Body.of(String.valueOf(object)))) .methodInterceptor(BeanValidationMethodInterceptor.usingDefaultFactory()) .target(Api.class, "http://localhost:" + server.getPort()); } diff --git a/validation/src/test/java/feign/validation/BeanValidationMethodInterceptorTest.java b/validation/src/test/java/feign/validation/BeanValidationMethodInterceptorTest.java index 11c19dd11..a25b393a3 100644 --- a/validation/src/test/java/feign/validation/BeanValidationMethodInterceptorTest.java +++ b/validation/src/test/java/feign/validation/BeanValidationMethodInterceptorTest.java @@ -20,6 +20,7 @@ import feign.Feign; import feign.Param; +import feign.Request; import feign.RequestLine; import javax.validation.ConstraintViolationException; import javax.validation.Valid; @@ -76,7 +77,8 @@ interface Api { private Api api() { return Feign.builder() - .encoder((object, bodyType, template) -> template.body(String.valueOf(object))) + .encoder( + (object, bodyType, template) -> template.body(Request.Body.of(String.valueOf(object)))) .methodInterceptor(BeanValidationMethodInterceptor.usingDefaultFactory()) .target(Api.class, "http://localhost:" + server.getPort()); }