From 3fc385a112e2df07344dc1e51b6ba89e2970d278 Mon Sep 17 00:00:00 2001 From: Adrian Cole Date: Sun, 25 Jan 2015 17:48:00 -0800 Subject: [PATCH] Removes guava test dependency in favor of AssertJ AssertJ has more powerful test assertions and does not run the risk of interfering with the classpath of main code, such as guava does. This removes guava from test and example code and adjusts using AssertJ in some cases. --- core/build.gradle | 2 +- .../java/feign/AcceptAllHostnameVerifier.java | 26 -- .../test/java/feign/DefaultContractTest.java | 215 ++++++++-------- .../src/test/java/feign/FeignBuilderTest.java | 22 +- core/src/test/java/feign/FeignTest.java | 116 +++++---- core/src/test/java/feign/GZIPStreams.java | 41 --- core/src/test/java/feign/LoggerTest.java | 18 +- .../test/java/feign/RequestTemplateTest.java | 200 +++++++-------- .../java/feign/TrustingSSLSocketFactory.java | 38 +-- .../java/feign/assertj/FeignAssertions.java | 10 + .../assertj/MockWebServerAssertions.java | 10 + .../feign/assertj/RecordedRequestAssert.java | 87 +++++++ .../feign/assertj/RequestTemplateAssert.java | 67 +++++ .../auth/BasicAuthRequestInterceptorTest.java | 32 +-- .../feign/codec/DefaultErrorDecoderTest.java | 19 +- gson/build.gradle | 2 + .../test/java/feign/gson/GsonModuleTest.java | 30 +-- jackson/build.gradle | 3 +- .../java/feign/jackson/JacksonModuleTest.java | 17 +- jaxb/build.gradle | 3 +- .../test/java/feign/jaxb/JAXBModuleTest.java | 78 +++--- .../jaxb/examples/AWSSignatureVersion4.java | 85 +++---- .../java/feign/jaxb/examples/IAMExample.java | 108 +------- jaxrs/build.gradle | 5 +- .../java/feign/jaxrs/JAXRSContractTest.java | 240 +++++++++--------- ribbon/build.gradle | 1 + .../main/java/feign/ribbon/RibbonClient.java | 3 +- .../java/feign/ribbon/RibbonClientTest.java | 117 ++++----- sax/build.gradle | 2 +- .../sax/examples/AWSSignatureVersion4.java | 83 +++--- slf4j/build.gradle | 1 + 31 files changed, 820 insertions(+), 861 deletions(-) delete mode 100644 core/src/test/java/feign/AcceptAllHostnameVerifier.java delete mode 100644 core/src/test/java/feign/GZIPStreams.java create mode 100644 core/src/test/java/feign/assertj/FeignAssertions.java create mode 100644 core/src/test/java/feign/assertj/MockWebServerAssertions.java create mode 100644 core/src/test/java/feign/assertj/RecordedRequestAssert.java create mode 100644 core/src/test/java/feign/assertj/RequestTemplateAssert.java diff --git a/core/build.gradle b/core/build.gradle index e2ee72b0..9edfdcb7 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -3,9 +3,9 @@ apply plugin: 'java' sourceCompatibility = 1.6 dependencies { - testCompile 'com.google.guava:guava:14.0.1' testCompile 'com.google.code.gson:gson:2.2.4' testCompile 'com.fasterxml.jackson.core:jackson-databind:2.2.2' testCompile 'junit:junit:4.12' + testCompile 'org.assertj:assertj-core:1.7.1' testCompile 'com.squareup.okhttp:mockwebserver:2.2.0' } diff --git a/core/src/test/java/feign/AcceptAllHostnameVerifier.java b/core/src/test/java/feign/AcceptAllHostnameVerifier.java deleted file mode 100644 index fa0055db..00000000 --- a/core/src/test/java/feign/AcceptAllHostnameVerifier.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright 2013 Netflix, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package feign; - -import javax.net.ssl.HostnameVerifier; -import javax.net.ssl.SSLSession; - -final class AcceptAllHostnameVerifier implements HostnameVerifier { - @Override - public boolean verify(String s, SSLSession sslSession) { - return true; - } -} diff --git a/core/src/test/java/feign/DefaultContractTest.java b/core/src/test/java/feign/DefaultContractTest.java index 77174c25..404f9f55 100644 --- a/core/src/test/java/feign/DefaultContractTest.java +++ b/core/src/test/java/feign/DefaultContractTest.java @@ -15,21 +15,17 @@ */ package feign; -import com.google.common.collect.ImmutableList; import com.google.gson.reflect.TypeToken; import java.net.URI; -import java.util.Arrays; import java.util.List; import javax.inject.Named; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; -import static feign.Util.UTF_8; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; +import static feign.assertj.FeignAssertions.assertThat; +import static java.util.Arrays.asList; +import static org.assertj.core.data.MapEntry.entry; /** * Tests interfaces defined per {@link Contract.Default} are interpreted into expected {@link feign @@ -52,14 +48,17 @@ public class DefaultContractTest { } @Test public void httpMethods() throws Exception { - assertEquals("POST", - contract.parseAndValidatateMetadata(Methods.class.getDeclaredMethod("post")).template().method()); - assertEquals("PUT", - contract.parseAndValidatateMetadata(Methods.class.getDeclaredMethod("put")).template().method()); - assertEquals("GET", - contract.parseAndValidatateMetadata(Methods.class.getDeclaredMethod("get")).template().method()); - assertEquals("DELETE", - contract.parseAndValidatateMetadata(Methods.class.getDeclaredMethod("delete")).template().method()); + assertThat(contract.parseAndValidatateMetadata(Methods.class.getDeclaredMethod("post")).template()) + .hasMethod("POST"); + + assertThat(contract.parseAndValidatateMetadata(Methods.class.getDeclaredMethod("put")).template()) + .hasMethod("PUT"); + + assertThat(contract.parseAndValidatateMetadata(Methods.class.getDeclaredMethod("get")).template()) + .hasMethod("GET"); + + assertThat(contract.parseAndValidatateMetadata(Methods.class.getDeclaredMethod("delete")).template()) + .hasMethod("DELETE"); } interface BodyParams { @@ -69,14 +68,12 @@ public class DefaultContractTest { } @Test public void bodyParamIsGeneric() throws Exception { - MethodMetadata md = contract.parseAndValidatateMetadata(BodyParams.class.getDeclaredMethod("post", - List.class)); - assertNull(md.template().body()); - assertNull(md.template().bodyTemplate()); - assertNull(md.urlIndex()); - assertEquals(md.bodyIndex(), Integer.valueOf(0)); - assertEquals(md.bodyType(), new TypeToken>() { - }.getType()); + MethodMetadata md = contract.parseAndValidatateMetadata(BodyParams.class.getDeclaredMethod("post", List.class)); + + assertThat(md.bodyIndex()) + .isEqualTo(0); + assertThat(md.bodyType()) + .isEqualTo(new TypeToken>(){}.getType()); } @Test public void tooManyBodies() throws Exception { @@ -86,20 +83,14 @@ public class DefaultContractTest { BodyParams.class.getDeclaredMethod("tooMany", List.class, List.class)); } - interface CustomMethodAndURIParam { - @RequestLine("PATCH") Response patch(URI nextLink); + interface CustomMethod { + @RequestLine("PATCH") Response patch(); } - @Test public void requestLineOnlyRequiresMethod() throws Exception { - MethodMetadata md = contract.parseAndValidatateMetadata(CustomMethodAndURIParam.class.getDeclaredMethod("patch", - URI.class)); - assertEquals("PATCH", md.template().method()); - assertEquals("", md.template().url()); - assertTrue(md.template().queries().isEmpty()); - assertTrue(md.template().headers().isEmpty()); - assertNull(md.template().body()); - assertNull(md.template().bodyTemplate()); - assertEquals(Integer.valueOf(0), md.urlIndex()); + @Test public void customMethodWithoutPath() throws Exception { + assertThat(contract.parseAndValidatateMetadata(CustomMethod.class.getDeclaredMethod("patch")).template()) + .hasMethod("PATCH") + .hasUrl(""); } interface WithQueryParamsInPath { @@ -115,41 +106,38 @@ public class DefaultContractTest { } @Test public void queryParamsInPathExtract() throws Exception { - { - MethodMetadata md = contract.parseAndValidatateMetadata(WithQueryParamsInPath.class.getDeclaredMethod("none")); - assertEquals("/", md.template().url()); - assertTrue(md.template().queries().isEmpty()); - assertEquals("GET / HTTP/1.1\n", md.template().toString()); - } - { - MethodMetadata md = contract.parseAndValidatateMetadata(WithQueryParamsInPath.class.getDeclaredMethod("one")); - assertEquals("/", md.template().url()); - assertEquals(Arrays.asList("GetUser"), md.template().queries().get("Action")); - assertEquals("GET /?Action=GetUser HTTP/1.1\n", md.template().toString()); - } - { - MethodMetadata md = contract.parseAndValidatateMetadata(WithQueryParamsInPath.class.getDeclaredMethod("two")); - assertEquals("/", md.template().url()); - assertEquals(Arrays.asList("GetUser"), md.template().queries().get("Action")); - assertEquals(Arrays.asList("2010-05-08"), md.template().queries().get("Version")); - assertEquals("GET /?Action=GetUser&Version=2010-05-08 HTTP/1.1\n", md.template().toString()); - } - { - MethodMetadata md = contract.parseAndValidatateMetadata(WithQueryParamsInPath.class.getDeclaredMethod("three")); - assertEquals("/", md.template().url()); - assertEquals(Arrays.asList("GetUser"), md.template().queries().get("Action")); - assertEquals(Arrays.asList("2010-05-08"), md.template().queries().get("Version")); - assertEquals(Arrays.asList("1"), md.template().queries().get("limit")); - assertEquals("GET /?Action=GetUser&Version=2010-05-08&limit=1 HTTP/1.1\n", md.template().toString()); - } - { - MethodMetadata md = contract.parseAndValidatateMetadata(WithQueryParamsInPath.class.getDeclaredMethod("empty")); - assertEquals("/", md.template().url()); - assertTrue(md.template().queries().containsKey("flag")); - assertEquals(Arrays.asList("GetUser"), md.template().queries().get("Action")); - assertEquals(Arrays.asList("2010-05-08"), md.template().queries().get("Version")); - assertEquals("GET /?flag&Action=GetUser&Version=2010-05-08 HTTP/1.1\n", md.template().toString()); - } + assertThat(contract.parseAndValidatateMetadata(WithQueryParamsInPath.class.getDeclaredMethod("none")).template()) + .hasUrl("/") + .hasQueries(); + + assertThat(contract.parseAndValidatateMetadata(WithQueryParamsInPath.class.getDeclaredMethod("one")).template()) + .hasUrl("/") + .hasQueries( + entry("Action", asList("GetUser")) + ); + + assertThat(contract.parseAndValidatateMetadata(WithQueryParamsInPath.class.getDeclaredMethod("two")).template()) + .hasUrl("/") + .hasQueries( + entry("Action", asList("GetUser")), + entry("Version", asList("2010-05-08")) + ); + + assertThat(contract.parseAndValidatateMetadata(WithQueryParamsInPath.class.getDeclaredMethod("three")).template()) + .hasUrl("/") + .hasQueries( + entry("Action", asList("GetUser")), + entry("Version", asList("2010-05-08")), + entry("limit", asList("1")) + ); + + assertThat(contract.parseAndValidatateMetadata(WithQueryParamsInPath.class.getDeclaredMethod("empty")).template()) + .hasUrl("/") + .hasQueries( + entry("flag", asList(new String[] { null })), + entry("Action", asList("GetUser")), + entry("Version", asList("2010-05-08")) + ); } interface BodyWithoutParameters { @@ -160,33 +148,37 @@ public class DefaultContractTest { @Test public void bodyWithoutParameters() throws Exception { MethodMetadata md = contract.parseAndValidatateMetadata(BodyWithoutParameters.class.getDeclaredMethod("post")); - assertEquals("", new String(md.template().body(), UTF_8)); - assertFalse(md.template().bodyTemplate() != null); - assertTrue(md.formParams().isEmpty()); - assertTrue(md.indexToName().isEmpty()); + + assertThat(md.template()) + .hasBody(""); } @Test public void producesAddsContentTypeHeader() throws Exception { MethodMetadata md = contract.parseAndValidatateMetadata(BodyWithoutParameters.class.getDeclaredMethod("post")); - assertEquals(Arrays.asList("application/xml"), md.template().headers().get("Content-Type")); + + assertThat(md.template()) + .hasHeaders( + entry("Content-Type", asList("application/xml")), + entry("Content-Length", asList(String.valueOf(md.template().body().length))) + ); } interface WithURIParam { @RequestLine("GET /{1}/{2}") Response uriParam(@Named("1") String one, URI endpoint, @Named("2") String two); } - @Test public void methodCanHaveUriParam() throws Exception { - MethodMetadata md = contract.parseAndValidatateMetadata(WithURIParam.class.getDeclaredMethod("uriParam", String.class, - URI.class, String.class)); - assertEquals(Integer.valueOf(1), md.urlIndex()); - } + @Test public void withPathAndURIParam() throws Exception { + MethodMetadata md = contract.parseAndValidatateMetadata( + WithURIParam.class.getDeclaredMethod("uriParam", String.class, URI.class, String.class)); - @Test public void pathParamsParseIntoIndexToName() throws Exception { - MethodMetadata md = contract.parseAndValidatateMetadata(WithURIParam.class.getDeclaredMethod("uriParam", String.class, - URI.class, String.class)); - assertEquals("/{1}/{2}", md.template().url()); - assertEquals(Arrays.asList("1"), md.indexToName().get(0)); - assertEquals(Arrays.asList("2"), md.indexToName().get(2)); + assertThat(md.indexToName()) + .containsExactly( + entry(0, asList("1")), + // Skips 1 as it is a url index! + entry(2, asList("2")) + ); + + assertThat(md.urlIndex()).isEqualTo(1); } interface WithPathAndQueryParams { @@ -195,19 +187,18 @@ public class DefaultContractTest { @Named("type") String typeFilter); } - @Test public void mixedRequestLineParams() throws Exception { + @Test public void pathAndQueryParams() throws Exception { MethodMetadata md = contract.parseAndValidatateMetadata(WithPathAndQueryParams.class.getDeclaredMethod ("recordsByNameAndType", int.class, String.class, String.class)); - assertNull(md.template().body()); - assertNull(md.template().bodyTemplate()); - assertTrue(md.template().headers().isEmpty()); - assertEquals("/domains/{domainId}/records", md.template().url()); - assertEquals(Arrays.asList("{name}"), md.template().queries().get("name")); - assertEquals(Arrays.asList("{type}"), md.template().queries().get("type")); - assertEquals(Arrays.asList("domainId"), md.indexToName().get(0)); - assertEquals(Arrays.asList("name"), md.indexToName().get(1)); - assertEquals(Arrays.asList("type"), md.indexToName().get(2)); - assertEquals("GET /domains/{domainId}/records?name={name}&type={type} HTTP/1.1\n", md.template().toString()); + + assertThat(md.template()) + .hasQueries(entry("name", asList("{name}")), entry("type", asList("{type}"))); + + assertThat(md.indexToName()).containsExactly( + entry(0, asList("domainId")), + entry(1, asList("name")), + entry(2, asList("type")) + ); } interface FormParams { @@ -218,18 +209,26 @@ public class DefaultContractTest { @Named("user_name") String user, @Named("password") String password); } + @Test public void bodyWithTemplate() throws Exception { + MethodMetadata md = contract.parseAndValidatateMetadata(FormParams.class.getDeclaredMethod("login", String.class, + String.class, String.class)); + + assertThat(md.template()) + .hasBodyTemplate("%7B\"customer_name\": \"{customer_name}\", \"user_name\": \"{user_name}\", \"password\": \"{password}\"%7D"); + } + @Test public void formParamsParseIntoIndexToName() throws Exception { MethodMetadata md = contract.parseAndValidatateMetadata(FormParams.class.getDeclaredMethod("login", String.class, String.class, String.class)); - assertFalse(md.template().body() != null); - assertEquals( - "%7B\"customer_name\": \"{customer_name}\", \"user_name\": \"{user_name}\", \"password\": \"{password}\"%7D", - md.template().bodyTemplate()); - assertEquals(ImmutableList.of("customer_name", "user_name", "password"), md.formParams()); - assertEquals(Arrays.asList("customer_name"), md.indexToName().get(0)); - assertEquals(Arrays.asList("user_name"), md.indexToName().get(1)); - assertEquals(Arrays.asList("password"), md.indexToName().get(2)); + assertThat(md.formParams()) + .containsExactly("customer_name", "user_name", "password"); + + assertThat(md.indexToName()).containsExactly( + entry(0, asList("customer_name")), + entry(1, asList("user_name")), + entry(2, asList("password")) + ); } interface HeaderParams { @@ -240,7 +239,9 @@ public class DefaultContractTest { @Test public void headerParamsParseIntoIndexToName() throws Exception { MethodMetadata md = contract.parseAndValidatateMetadata(HeaderParams.class.getDeclaredMethod("logout", String.class)); - assertEquals(Arrays.asList("{Auth-Token}"), md.template().headers().get("Auth-Token")); - assertEquals(Arrays.asList("Auth-Token"), md.indexToName().get(0)); + assertThat(md.template()).hasHeaders(entry("Auth-Token", asList("{Auth-Token}"))); + + assertThat(md.indexToName()) + .containsExactly(entry(0, asList("Auth-Token"))); } } diff --git a/core/src/test/java/feign/FeignBuilderTest.java b/core/src/test/java/feign/FeignBuilderTest.java index 1ab2f583..5020e2b2 100644 --- a/core/src/test/java/feign/FeignBuilderTest.java +++ b/core/src/test/java/feign/FeignBuilderTest.java @@ -16,7 +16,6 @@ package feign; import com.squareup.okhttp.mockwebserver.MockResponse; -import com.squareup.okhttp.mockwebserver.RecordedRequest; import com.squareup.okhttp.mockwebserver.rule.MockWebServerRule; import feign.codec.Decoder; import feign.codec.EncodeException; @@ -32,6 +31,7 @@ import java.util.Map; import java.util.concurrent.atomic.AtomicInteger; import org.junit.Test; +import static feign.assertj.MockWebServerAssertions.assertThat; import static org.junit.Assert.assertEquals; public class FeignBuilderTest { @@ -54,8 +54,8 @@ public class FeignBuilderTest { Response response = api.codecPost("request data"); assertEquals("response data", Util.toString(response.body().asReader())); - assertEquals(1, server.getRequestCount()); - assertEquals("request data", server.takeRequest().getUtf8Body()); + assertThat(server.takeRequest()) + .hasBody("request data"); } @Test public void testOverrideEncoder() throws Exception { @@ -72,8 +72,8 @@ public class FeignBuilderTest { TestInterface api = Feign.builder().encoder(encoder).target(TestInterface.class, url); api.encodedPost(Arrays.asList("This", "is", "my", "request")); - assertEquals(1, server.getRequestCount()); - assertEquals("[This, is, my, request]", server.takeRequest().getUtf8Body()); + assertThat(server.takeRequest()) + .hasBody("[This, is, my, request]"); } @Test public void testOverrideDecoder() throws Exception { @@ -108,10 +108,9 @@ public class FeignBuilderTest { Response response = api.codecPost("request data"); assertEquals(Util.toString(response.body().asReader()), "response data"); - assertEquals(1, server.getRequestCount()); - RecordedRequest request = server.takeRequest(); - assertEquals("request data", request.getUtf8Body()); - assertEquals("text/plain", request.getHeader("Content-Type")); + assertThat(server.takeRequest()) + .hasHeaders("Content-Type: text/plain") + .hasBody("request data"); } @Test public void testProvideInvocationHandlerFactory() throws Exception { @@ -133,8 +132,7 @@ public class FeignBuilderTest { assertEquals("response data", Util.toString(response.body().asReader())); assertEquals(1, callCount.get()); - assertEquals(1, server.getRequestCount()); - RecordedRequest request = server.takeRequest(); - assertEquals("request data", request.getUtf8Body()); + assertThat(server.takeRequest()) + .hasBody("request data"); } } diff --git a/core/src/test/java/feign/FeignTest.java b/core/src/test/java/feign/FeignTest.java index f63a1225..b409ea90 100644 --- a/core/src/test/java/feign/FeignTest.java +++ b/core/src/test/java/feign/FeignTest.java @@ -15,12 +15,9 @@ */ package feign; -import com.google.common.base.Joiner; -import com.google.common.io.ByteStreams; -import com.google.common.io.CharStreams; +import com.google.gson.Gson; import com.squareup.okhttp.mockwebserver.MockResponse; import com.squareup.okhttp.mockwebserver.MockWebServer; -import com.squareup.okhttp.mockwebserver.RecordedRequest; import com.squareup.okhttp.mockwebserver.SocketPolicy; import com.squareup.okhttp.mockwebserver.rule.MockWebServerRule; import dagger.Module; @@ -40,6 +37,7 @@ import java.util.concurrent.Executor; import javax.inject.Named; import javax.inject.Singleton; import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.SSLSession; import javax.net.ssl.SSLSocketFactory; import org.junit.Rule; import org.junit.Test; @@ -47,10 +45,8 @@ import org.junit.rules.ExpectedException; import static dagger.Provides.Type.SET; import static feign.Util.UTF_8; -import static org.junit.Assert.assertArrayEquals; +import static feign.assertj.MockWebServerAssertions.assertThat; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotEquals; -import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; // unbound wildcards are not currently injectable in dagger. @@ -90,7 +86,7 @@ public class FeignTest { return new Encoder() { @Override public void encode(Object object, RequestTemplate template) { if (object instanceof Map) { - template.body(Joiner.on(',').withKeyValueSeparator("=").join((Map) object)); + template.body(new Gson().toJson(object)); } else { template.body(object.toString()); } @@ -100,14 +96,16 @@ public class FeignTest { } } - @Test - public void iterableQueryParams() throws IOException, InterruptedException { + @Test public void iterableQueryParams() throws IOException, InterruptedException { server.enqueue(new MockResponse().setBody("foo")); - TestInterface api = Feign.create(TestInterface.class, "http://localhost:" + server.getPort(), new TestInterface.Module()); + TestInterface api = + Feign.create(TestInterface.class, "http://localhost:" + server.getPort(), new TestInterface.Module()); api.queryParams("user", Arrays.asList("apple", "pear")); - assertEquals("GET /?1=user&2=apple&2=pear HTTP/1.1", server.takeRequest().getRequestLine()); + + assertThat(server.takeRequest()) + .hasPath("/?1=user&2=apple&2=pear"); } interface OtherTestInterface { @@ -136,8 +134,9 @@ public class FeignTest { TestInterface api = Feign.create(TestInterface.class, "http://localhost:" + server.getPort(), new TestInterface.Module()); api.login("netflix", "denominator", "password"); - assertEquals("{\"customer_name\": \"netflix\", \"user_name\": \"denominator\", \"password\": \"password\"}", - server.takeRequest().getUtf8Body()); + + assertThat(server.takeRequest()) + .hasBody("{\"customer_name\": \"netflix\", \"user_name\": \"denominator\", \"password\": \"password\"}"); } @Test @@ -159,8 +158,9 @@ public class FeignTest { TestInterface api = Feign.create(TestInterface.class, "http://localhost:" + server.getPort(), new TestInterface.Module()); api.form("netflix", "denominator", "password"); - assertEquals("customer_name=netflix,user_name=denominator,password=password", - server.takeRequest().getUtf8Body()); + + assertThat(server.takeRequest()) + .hasBody("{\"customer_name\":\"netflix\",\"user_name\":\"denominator\",\"password\":\"password\"}"); } @Test @@ -170,9 +170,10 @@ public class FeignTest { TestInterface api = Feign.create(TestInterface.class, "http://localhost:" + server.getPort(), new TestInterface.Module()); api.body(Arrays.asList("netflix", "denominator", "password")); - RecordedRequest request = server.takeRequest(); - assertEquals("32", request.getHeader("Content-Length")); - assertEquals("[netflix, denominator, password]", request.getUtf8Body()); + + assertThat(server.takeRequest()) + .hasHeaders("Content-Length: 32") + .hasBody("[netflix, denominator, password]"); } @Test @@ -182,12 +183,10 @@ public class FeignTest { TestInterface api = Feign.create(TestInterface.class, "http://localhost:" + server.getPort(), new TestInterface.Module()); api.gzipBody(Arrays.asList("netflix", "denominator", "password")); - RecordedRequest request = server.takeRequest(); - assertNull(request.getHeader("Content-Length")); - byte[] compressedBody = request.getBody(); - String uncompressedBody = CharStreams.toString(CharStreams.newReaderSupplier( - GZIPStreams.newInputStreamSupplier(ByteStreams.newInputStreamSupplier(compressedBody)), UTF_8)); - assertEquals("[netflix, denominator, password]", uncompressedBody); + + assertThat(server.takeRequest()) + .hasNoHeaderNamed("Content-Length") + .hasGzippedBody("[netflix, denominator, password]".getBytes(UTF_8)); } @Module(library = true) @@ -209,7 +208,9 @@ public class FeignTest { new TestInterface.Module(), new ForwardedForInterceptor()); api.post(); - assertEquals("origin.host.com", server.takeRequest().getHeader("X-Forwarded-For")); + + assertThat(server.takeRequest()) + .hasHeaders("X-Forwarded-For: origin.host.com"); } @Module(library = true) @@ -231,9 +232,9 @@ public class FeignTest { new TestInterface.Module(), new ForwardedForInterceptor(), new UserAgentInterceptor()); api.post(); - RecordedRequest request = server.takeRequest(); - assertEquals("origin.host.com", request.getHeader("X-Forwarded-For")); - assertEquals("Feign", request.getHeader("User-Agent")); + + assertThat(server.takeRequest()) + .hasHeaders("X-Forwarded-For: origin.host.com", "User-Agent: Feign"); } @Test public void toKeyMethodFormatsAsExpected() throws Exception { @@ -278,6 +279,7 @@ public class FeignTest { new TestInterface.Module()); api.post(); + assertEquals(2, server.getRequestCount()); } @@ -293,14 +295,13 @@ public class FeignTest { } } - public void overrideTypeSpecificDecoder() throws IOException, InterruptedException { + @Test public void overrideTypeSpecificDecoder() throws IOException, InterruptedException { server.enqueue(new MockResponse().setBody("success!")); TestInterface api = Feign.create(TestInterface.class, "http://localhost:" + server.getPort(), new DecodeFail()); assertEquals(api.post(), "fail"); - assertEquals(1, server.getRequestCount()); } @dagger.Module(overrides = true, library = true, includes = TestInterface.Module.class) @@ -385,7 +386,12 @@ public class FeignTest { @Module(overrides = true, includes = TrustSSLSockets.class) static class DisableHostnameVerification { @Provides HostnameVerifier acceptAllHostnameVerifier() { - return new AcceptAllHostnameVerifier(); + return new HostnameVerifier() { + @Override + public boolean verify(String s, SSLSession sslSession) { + return true; + } + }; } } @@ -431,28 +437,29 @@ public class FeignTest { TestInterface i3 = Feign.builder().target(t2); OtherTestInterface i4 = Feign.builder().target(t3); - assertEquals(i1, i1); - assertEquals(i2, i1); - assertNotEquals(i3, i1); - assertNotEquals(i4, i1); + assertThat(i1) + .isEqualTo(i2) + .isNotEqualTo(i3) + .isNotEqualTo(i4); + + assertThat(i1.hashCode()) + .isEqualTo(i2.hashCode()) + .isNotEqualTo(i3.hashCode()) + .isNotEqualTo(i4.hashCode()); - assertEquals(i1.hashCode(), i1.hashCode()); - assertEquals(i2.hashCode(), i1.hashCode()); - assertNotEquals(i3.hashCode(), i1.hashCode()); - assertNotEquals(i4.hashCode(), i1.hashCode()); + assertThat(i1.toString()) + .isEqualTo(i2.toString()) + .isNotEqualTo(i3.toString()) + .isNotEqualTo(i4.toString()); - assertEquals(t1.hashCode(), i1.hashCode()); - assertEquals(t2.hashCode(), i3.hashCode()); - assertEquals(t3.hashCode(), i4.hashCode()); + assertThat(t1) + .isNotEqualTo(i1); - assertEquals(i1.toString(), i1.toString()); - assertEquals(i2.toString(), i1.toString()); - assertNotEquals(i3.toString(), i1.toString()); - assertNotEquals(i4.toString(), i1.toString()); + assertThat(t1.hashCode()) + .isEqualTo(i1.hashCode()); - assertEquals(t1.toString(), i1.toString()); - assertEquals(t2.toString(), i3.toString()); - assertEquals(t3.toString(), i4.toString()); + assertThat(t1.toString()) + .isEqualTo(i1.toString()); } @Test public void decodeLogicSupportsByteArray() throws Exception { @@ -461,8 +468,8 @@ public class FeignTest { OtherTestInterface api = Feign.builder().target(OtherTestInterface.class, "http://localhost:" + server.getPort()); - byte[] actualResponse = api.binaryResponseBody(); - assertArrayEquals(expectedResponse, actualResponse); + assertThat(api.binaryResponseBody()) + .containsExactly(expectedResponse); } @Test public void encodeLogicSupportsByteArray() throws Exception { @@ -472,7 +479,8 @@ public class FeignTest { OtherTestInterface api = Feign.builder().target(OtherTestInterface.class, "http://localhost:" + server.getPort()); api.binaryRequestBody(expectedRequest); - byte[] actualRequest = server.takeRequest().getBody(); - assertArrayEquals(expectedRequest, actualRequest); + + assertThat(server.takeRequest()) + .hasBody(expectedRequest); } } diff --git a/core/src/test/java/feign/GZIPStreams.java b/core/src/test/java/feign/GZIPStreams.java deleted file mode 100644 index 42b28868..00000000 --- a/core/src/test/java/feign/GZIPStreams.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright 2013 Netflix, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package feign; - -import com.google.common.io.InputSupplier; - -import java.io.IOException; -import java.io.InputStream; -import java.util.zip.GZIPInputStream; - -class GZIPStreams { - static InputSupplier newInputStreamSupplier(InputSupplier supplier) { - return new GZIPInputStreamSupplier(supplier); - } - - private static class GZIPInputStreamSupplier implements InputSupplier { - private final InputSupplier supplier; - - GZIPInputStreamSupplier(InputSupplier supplier) { - this.supplier = supplier; - } - - @Override - public GZIPInputStream getInput() throws IOException { - return new GZIPInputStream(supplier.getInput()); - } - } -} diff --git a/core/src/test/java/feign/LoggerTest.java b/core/src/test/java/feign/LoggerTest.java index 5e200115..57cc9ca1 100644 --- a/core/src/test/java/feign/LoggerTest.java +++ b/core/src/test/java/feign/LoggerTest.java @@ -15,7 +15,6 @@ */ package feign; -import com.google.common.base.Joiner; import com.squareup.okhttp.mockwebserver.MockResponse; import com.squareup.okhttp.mockwebserver.rule.MockWebServerRule; import feign.Logger.Level; @@ -24,8 +23,8 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.concurrent.TimeUnit; -import java.util.regex.Pattern; import javax.inject.Named; +import org.assertj.core.api.SoftAssertions; import org.junit.Rule; import org.junit.Test; import org.junit.experimental.runners.Enclosed; @@ -37,10 +36,6 @@ import org.junit.runners.Parameterized; import org.junit.runners.Parameterized.Parameters; import org.junit.runners.model.Statement; -import static feign.Util.UTF_8; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; - @RunWith(Enclosed.class) public class LoggerTest { @Rule public final MockWebServerRule server = new MockWebServerRule(); @@ -104,9 +99,6 @@ public class LoggerTest { .target(SendsStuff.class, "http://localhost:" + server.getUrl("").getPort()); api.login("netflix", "denominator", "password"); - - assertEquals(new String(server.takeRequest().getBody(), UTF_8), - "{\"customer_name\": \"netflix\", \"user_name\": \"denominator\", \"password\": \"password\"}"); } } @@ -247,7 +239,7 @@ public class LoggerTest { SendsStuff api = Feign.builder() .logger(logger) .logLevel(logLevel) - .retryer( new Retryer() { + .retryer(new Retryer() { boolean retried; @Override public void continueOrPropagate(RetryableException e) { @@ -281,11 +273,11 @@ public class LoggerTest { return new Statement() { @Override public void evaluate() throws Throwable { base.evaluate(); - assertEquals(messages.size(), expectedMessages.size()); + SoftAssertions softly = new SoftAssertions(); for (int i = 0; i < messages.size(); i++) { - assertTrue("Didn't match at message " + (i + 1) + ":\n" + Joiner.on('\n').join(messages), - Pattern.compile(expectedMessages.get(i), Pattern.DOTALL).matcher(messages.get(i)).matches()); + softly.assertThat(messages.get(i)).matches(expectedMessages.get(i)); } + softly.assertAll(); } }; } diff --git a/core/src/test/java/feign/RequestTemplateTest.java b/core/src/test/java/feign/RequestTemplateTest.java index 6c873a04..032c82c6 100644 --- a/core/src/test/java/feign/RequestTemplateTest.java +++ b/core/src/test/java/feign/RequestTemplateTest.java @@ -15,89 +15,84 @@ */ package feign; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableListMultimap; -import com.google.common.collect.ImmutableMap; - import java.util.Arrays; +import java.util.LinkedHashMap; +import java.util.Map; import org.junit.Test; +import static feign.assertj.FeignAssertions.assertThat; import static feign.RequestTemplate.expand; -import static org.junit.Assert.assertEquals; +import static java.util.Arrays.asList; +import static org.assertj.core.data.MapEntry.entry; public class RequestTemplateTest { + @Test public void expandNotUrlEncoded() { - for (String val : ImmutableList.of("apples", "sp ace", "unic???de", "qu?stion")) - assertEquals("/users/" + val, expand("/users/{user}", ImmutableMap.of("user", val))); + for (String val : Arrays.asList("apples", "sp ace", "unic???de", "qu?stion")) { + assertThat(expand("/users/{user}", mapOf("user", val))) + .isEqualTo("/users/" + val); + } } @Test public void expandMultipleParams() { - assertEquals("/users/unic???de/foo", - expand("/users/{user}/{repo}", ImmutableMap.of("user", "unic???de", "repo", "foo"))); + assertThat(expand("/users/{user}/{repo}", mapOf("user", "unic???de", "repo", "foo"))) + .isEqualTo("/users/unic???de/foo"); } @Test public void expandParamKeyHyphen() { - assertEquals("/foo", expand("/{user-dir}", ImmutableMap.of("user-dir", "foo"))); + assertThat(expand("/{user-dir}", mapOf("user-dir", "foo"))) + .isEqualTo("/foo"); } @Test public void expandMissingParamProceeds() { - assertEquals("/{user-dir}", expand("/{user-dir}", ImmutableMap.of("user_dir", "foo"))); + assertThat(expand("/{user-dir}", mapOf("user_dir", "foo"))) + .isEqualTo("/{user-dir}"); } @Test public void resolveTemplateWithParameterizedPathSkipsEncodingSlash() { - RequestTemplate template = new RequestTemplate().method("GET") .append("{zoneId}"); - assertEquals("GET {zoneId} HTTP/1.1\n", template.toString()); + template.resolve(mapOf("zoneId", "/hostedzone/Z1PA6795UKMFR9")); - template.resolve(ImmutableMap.of("zoneId", "/hostedzone/Z1PA6795UKMFR9")); + assertThat(template) + .hasUrl("/hostedzone/Z1PA6795UKMFR9"); + } - assertEquals("GET /hostedzone/Z1PA6795UKMFR9 HTTP/1.1\n", template.toString()); + @Test public void canInsertAbsoluteHref() { + RequestTemplate template = new RequestTemplate().method("GET") + .append("/hostedzone/Z1PA6795UKMFR9"); template.insert(0, "https://route53.amazonaws.com/2012-12-12"); - assertEquals("GET https://route53.amazonaws.com/2012-12-12/hostedzone/Z1PA6795UKMFR9 HTTP/1.1\n", - template.request().toString()); + assertThat(template) + .hasUrl("https://route53.amazonaws.com/2012-12-12/hostedzone/Z1PA6795UKMFR9"); } @Test public void resolveTemplateWithBaseAndParameterizedQuery() { RequestTemplate template = new RequestTemplate().method("GET") .append("/?Action=DescribeRegions").query("RegionName.1", "{region}"); - assertEquals( - ImmutableListMultimap.of("Action", "DescribeRegions", "RegionName.1", "{region}").asMap(), - template.queries()); - assertEquals("GET /?Action=DescribeRegions&RegionName.1={region} HTTP/1.1\n", - template.toString()); - - template.resolve(ImmutableMap.of("region", "eu-west-1")); - assertEquals( - ImmutableListMultimap.of("Action", "DescribeRegions", "RegionName.1", "eu-west-1").asMap(), - template.queries()); - - assertEquals("GET /?Action=DescribeRegions&RegionName.1=eu-west-1 HTTP/1.1\n", - template.toString()); + template.resolve(mapOf("region", "eu-west-1")); - template.insert(0, "https://iam.amazonaws.com"); - - assertEquals( - "GET https://iam.amazonaws.com/?Action=DescribeRegions&RegionName.1=eu-west-1 HTTP/1.1\n", - template.request().toString()); + assertThat(template) + .hasQueries( + entry("Action", asList("DescribeRegions")), + entry("RegionName.1", asList("eu-west-1")) + ); } @Test public void resolveTemplateWithBaseAndParameterizedIterableQuery() { RequestTemplate template = new RequestTemplate().method("GET") .append("/?Query=one").query("Queries", "{queries}"); - template.resolve(ImmutableMap.of("queries", Arrays.asList("us-east-1", "eu-west-1"))); - assertEquals(template.queries(), - ImmutableListMultimap. builder() - .put("Query", "one") - .putAll("Queries", "us-east-1", "eu-west-1") - .build().asMap()); + template.resolve(mapOf("queries", Arrays.asList("us-east-1", "eu-west-1"))); - assertEquals("GET /?Query=one&Queries=us-east-1&Queries=eu-west-1 HTTP/1.1\n", template.toString()); + assertThat(template) + .hasQueries( + entry("Query", asList("one")), + entry("Queries", asList("us-east-1", "eu-west-1")) + ); } @Test public void resolveTemplateWithMixedRequestLineParams() throws Exception { @@ -106,44 +101,33 @@ public class RequestTemplateTest { .query("name", "{name}")// .query("type", "{type}"); - template = template.resolve(ImmutableMap.builder()// - .put("domainId", 1001)// - .put("name", "denominator.io")// - .put("type", "CNAME")// - .build() + template = template.resolve( + mapOf("domainId", 1001, "name", "denominator.io", "type", "CNAME") ); - assertEquals("GET /domains/1001/records?name=denominator.io&type=CNAME HTTP/1.1\n", - template.toString()); - - template.insert(0, "https://dns.api.rackspacecloud.com/v1.0/1234"); - - assertEquals(""// - + "GET https://dns.api.rackspacecloud.com/v1.0/1234"// - + "/domains/1001/records?name=denominator.io&type=CNAME HTTP/1.1\n", - template.request().toString()); + assertThat(template) + .hasUrl("/domains/1001/records") + .hasQueries( + entry("name", asList("denominator.io")), + entry("type", asList("CNAME")) + ); } @Test public void insertHasQueryParams() throws Exception { RequestTemplate template = new RequestTemplate().method("GET")// - .append("/domains/{domainId}/records")// - .query("name", "{name}")// - .query("type", "{type}"); - - template = template.resolve(ImmutableMap.builder()// - .put("domainId", 1001)// - .put("name", "denominator.io")// - .put("type", "CNAME")// - .build() - ); - - assertEquals("GET /domains/1001/records?name=denominator.io&type=CNAME HTTP/1.1\n", - template.toString()); + .append("/domains/1001/records")// + .query("name", "denominator.io")// + .query("type", "CNAME"); template.insert(0, "https://host/v1.0/1234?provider=foo"); - assertEquals("GET https://host/v1.0/1234/domains/1001/records?provider=foo&name=denominator.io&type=CNAME HTTP/1.1\n", - template.request().toString()); + assertThat(template) + .hasUrl("https://host/v1.0/1234/domains/1001/records") + .hasQueries( + entry("provider", asList("foo")), + entry("name", asList("denominator.io")), + entry("type", asList("CNAME")) + ); } @Test public void resolveTemplateWithBodyTemplateSetsBodyAndContentLength() { @@ -151,28 +135,19 @@ public class RequestTemplateTest { .bodyTemplate("%7B\"customer_name\": \"{customer_name}\", \"user_name\": \"{user_name}\", " + "\"password\": \"{password}\"%7D"); - template = template.resolve(ImmutableMap.builder()// - .put("customer_name", "netflix")// - .put("user_name", "denominator")// - .put("password", "password")// - .build() + template = template.resolve( + mapOf( + "customer_name", "netflix", + "user_name", "denominator", + "password", "password" + ) ); - assertEquals(""// - + "POST HTTP/1.1\n"// - + "Content-Length: 80\n"// - + "\n"// - + "{\"customer_name\": \"netflix\", \"user_name\": \"denominator\", \"password\": \"password\"}", - template.toString()); - - template.insert(0, "https://api2.dynect.net/REST"); - - assertEquals(""// - + "POST https://api2.dynect.net/REST HTTP/1.1\n" // - + "Content-Length: 80\n" // - + "\n" // - + "{\"customer_name\": \"netflix\", \"user_name\": \"denominator\", \"password\": \"password\"}", - template.request().toString()); + assertThat(template) + .hasBody("{\"customer_name\": \"netflix\", \"user_name\": \"denominator\", \"password\": \"password\"}") + .hasHeaders( + entry("Content-Length", asList(String.valueOf(template.body().length))) + ); } @Test public void skipUnresolvedQueries() throws Exception { @@ -181,14 +156,17 @@ public class RequestTemplateTest { .query("optional", "{optional}")// .query("name", "{nameVariable}"); - template = template.resolve(ImmutableMap.builder()// - .put("domainId", 1001)// - .put("nameVariable", "denominator.io")// - .build() + template = template.resolve(mapOf( + "domainId", 1001, + "nameVariable", "denominator.io" + ) ); - assertEquals("GET /domains/1001/records?name=denominator.io HTTP/1.1\n", - template.toString()); + assertThat(template) + .hasUrl("/domains/1001/records") + .hasQueries( + entry("name", asList("denominator.io")) + ); } @Test public void allQueriesUnresolvable() throws Exception { @@ -197,11 +175,29 @@ public class RequestTemplateTest { .query("optional", "{optional}")// .query("optional2", "{optional2}"); - template = template.resolve(ImmutableMap.builder()// - .put("domainId", 1001)// - .build() - ); + template = template.resolve(mapOf("domainId", 1001)); + + assertThat(template) + .hasUrl("/domains/1001/records") + .hasQueries(); + } + + /** Avoid depending on guava solely for map literals. */ + private static Map mapOf(String key, Object val) { + Map result = new LinkedHashMap(); + result.put(key, val); + return result; + } + + private static Map mapOf(String k1, Object v1, String k2, Object v2) { + Map result = mapOf(k1, v1); + result.put(k2, v2); + return result; + } - assertEquals("GET /domains/1001/records HTTP/1.1\n", template.toString()); + private static Map mapOf(String k1, Object v1, String k2, Object v2, String k3, Object v3) { + Map result = mapOf(k1, v1, k2, v2); + result.put(k3, v3); + return result; } } diff --git a/core/src/test/java/feign/TrustingSSLSocketFactory.java b/core/src/test/java/feign/TrustingSSLSocketFactory.java index 15d3eae6..98723a1c 100644 --- a/core/src/test/java/feign/TrustingSSLSocketFactory.java +++ b/core/src/test/java/feign/TrustingSSLSocketFactory.java @@ -15,13 +15,6 @@ */ package feign; -import com.google.common.cache.CacheBuilder; -import com.google.common.cache.CacheLoader; -import com.google.common.cache.LoadingCache; -import com.google.common.io.Closer; -import com.google.common.io.InputSupplier; -import com.google.common.io.Resources; - import java.io.IOException; import java.io.InputStream; import java.net.InetAddress; @@ -33,8 +26,8 @@ import java.security.SecureRandom; import java.security.cert.Certificate; import java.security.cert.X509Certificate; import java.util.Arrays; - -import javax.inject.Provider; +import java.util.LinkedHashMap; +import java.util.Map; import javax.net.ssl.KeyManager; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLSocket; @@ -50,20 +43,17 @@ import static com.google.common.base.Throwables.propagate; */ final class TrustingSSLSocketFactory extends SSLSocketFactory implements X509TrustManager, X509KeyManager { - private static LoadingCache sslSocketFactories = - CacheBuilder.newBuilder().build(new CacheLoader() { - @Override - public SSLSocketFactory load(String serverAlias) throws Exception { - return new TrustingSSLSocketFactory(serverAlias); - } - }); + private static final Map sslSocketFactories = new LinkedHashMap(); public static SSLSocketFactory get() { return get(""); } - public static SSLSocketFactory get(String serverAlias) { - return sslSocketFactories.getUnchecked(serverAlias); + public synchronized static SSLSocketFactory get(String serverAlias) { + if (!sslSocketFactories.containsKey(serverAlias)) { + sslSocketFactories.put(serverAlias, new TrustingSSLSocketFactory(serverAlias)); + } + return sslSocketFactories.get(serverAlias); } private static final char[] KEYSTORE_PASSWORD = "password".toCharArray(); @@ -87,7 +77,7 @@ final class TrustingSSLSocketFactory extends SSLSocketFactory implements X509Tru this.certificateChain = null; } else { try { - KeyStore keyStore = loadKeyStore(Resources.newInputStreamSupplier(Resources.getResource("keystore.jks"))); + KeyStore keyStore = loadKeyStore(TrustingSSLSocketFactory.class.getResourceAsStream("/keystore.jks")); this.privateKey = (PrivateKey) keyStore.getKey(serverAlias, KEYSTORE_PASSWORD); Certificate[] rawChain = keyStore.getCertificateChain(serverAlias); this.certificateChain = Arrays.copyOf(rawChain, rawChain.length, X509Certificate[].class); @@ -175,17 +165,15 @@ final class TrustingSSLSocketFactory extends SSLSocketFactory implements X509Tru return privateKey; } - private static KeyStore loadKeyStore(InputSupplier inputStreamSupplier) throws IOException { - Closer closer = Closer.create(); + private static KeyStore loadKeyStore(InputStream inputStream) throws IOException { try { - InputStream inputStream = closer.register(inputStreamSupplier.getInput()); KeyStore keyStore = KeyStore.getInstance("JKS"); keyStore.load(inputStream, KEYSTORE_PASSWORD); return keyStore; - } catch (Throwable e) { - throw closer.rethrow(e); + } catch (Exception e) { + throw new RuntimeException(e); } finally { - closer.close(); + inputStream.close(); } } diff --git a/core/src/test/java/feign/assertj/FeignAssertions.java b/core/src/test/java/feign/assertj/FeignAssertions.java new file mode 100644 index 00000000..821a80b8 --- /dev/null +++ b/core/src/test/java/feign/assertj/FeignAssertions.java @@ -0,0 +1,10 @@ +package feign.assertj; + +import feign.RequestTemplate; +import org.assertj.core.api.Assertions; + +public class FeignAssertions extends Assertions { + public static RequestTemplateAssert assertThat(RequestTemplate actual) { + return new RequestTemplateAssert(actual); + } +} diff --git a/core/src/test/java/feign/assertj/MockWebServerAssertions.java b/core/src/test/java/feign/assertj/MockWebServerAssertions.java new file mode 100644 index 00000000..e6cbb1c1 --- /dev/null +++ b/core/src/test/java/feign/assertj/MockWebServerAssertions.java @@ -0,0 +1,10 @@ +package feign.assertj; + +import com.squareup.okhttp.mockwebserver.RecordedRequest; +import org.assertj.core.api.Assertions; + +public class MockWebServerAssertions extends Assertions { + public static RecordedRequestAssert assertThat(RecordedRequest actual) { + return new RecordedRequestAssert(actual); + } +} diff --git a/core/src/test/java/feign/assertj/RecordedRequestAssert.java b/core/src/test/java/feign/assertj/RecordedRequestAssert.java new file mode 100644 index 00000000..54a2c3a6 --- /dev/null +++ b/core/src/test/java/feign/assertj/RecordedRequestAssert.java @@ -0,0 +1,87 @@ +package feign.assertj; + +import com.squareup.okhttp.mockwebserver.RecordedRequest; +import feign.Util; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.util.LinkedHashSet; +import java.util.Set; +import java.util.zip.GZIPInputStream; +import org.assertj.core.api.AbstractAssert; +import org.assertj.core.internal.ByteArrays; +import org.assertj.core.internal.Failures; +import org.assertj.core.internal.Iterables; +import org.assertj.core.internal.Objects; + +import static org.assertj.core.error.ShouldNotContain.shouldNotContain; + +public final class RecordedRequestAssert extends AbstractAssert { + + ByteArrays arrays = ByteArrays.instance(); + Objects objects = Objects.instance(); + Iterables iterables = Iterables.instance(); + Failures failures = Failures.instance(); + + public RecordedRequestAssert(RecordedRequest actual) { + super(actual, RecordedRequestAssert.class); + } + + public RecordedRequestAssert hasMethod(String expected) { + isNotNull(); + objects.assertEqual(info, actual.getMethod(), expected); + return this; + } + + public RecordedRequestAssert hasPath(String expected) { + isNotNull(); + objects.assertEqual(info, actual.getPath(), expected); + return this; + } + + public RecordedRequestAssert hasBody(String utf8Expected) { + isNotNull(); + objects.assertEqual(info, actual.getUtf8Body(), utf8Expected); + return this; + } + + public RecordedRequestAssert hasGzippedBody(byte[] expectedUncompressed) { + isNotNull(); + byte[] compressedBody = actual.getBody(); + byte[] uncompressedBody; + try { + uncompressedBody = Util.toByteArray(new GZIPInputStream(new ByteArrayInputStream(compressedBody))); + } catch (IOException e) { + throw new RuntimeException(e); + } + arrays.assertContains(info, uncompressedBody, expectedUncompressed); + return this; + } + + public RecordedRequestAssert hasBody(byte[] expected) { + isNotNull(); + arrays.assertContains(info, actual.getBody(), expected); + return this; + } + + public RecordedRequestAssert hasHeaders(String... headers) { + isNotNull(); + iterables.assertContainsSubsequence(info, actual.getHeaders(), headers); + return this; + } + + public RecordedRequestAssert hasNoHeaderNamed(final String... names) { + isNotNull(); + Set found = new LinkedHashSet(); + for (String header : actual.getHeaders()) { + for (String name : names) { + if (header.toLowerCase().startsWith(name.toLowerCase() + ":")) { + found.add(header); + } + } + } + if (found.isEmpty()) { + return this; + } + throw failures.failure(info, shouldNotContain(actual.getHeaders(), names, found)); + } +} diff --git a/core/src/test/java/feign/assertj/RequestTemplateAssert.java b/core/src/test/java/feign/assertj/RequestTemplateAssert.java new file mode 100644 index 00000000..d2e9a26a --- /dev/null +++ b/core/src/test/java/feign/assertj/RequestTemplateAssert.java @@ -0,0 +1,67 @@ +package feign.assertj; + +import feign.RequestTemplate; +import org.assertj.core.api.AbstractAssert; +import org.assertj.core.data.MapEntry; +import org.assertj.core.internal.ByteArrays; +import org.assertj.core.internal.Maps; +import org.assertj.core.internal.Objects; + +import static feign.Util.UTF_8; + +public final class RequestTemplateAssert extends AbstractAssert { + + ByteArrays arrays = ByteArrays.instance(); + Objects objects = Objects.instance(); + Maps maps = Maps.instance(); + + public RequestTemplateAssert(RequestTemplate actual) { + super(actual, RequestTemplateAssert.class); + } + + public RequestTemplateAssert hasMethod(String expected) { + isNotNull(); + objects.assertEqual(info, actual.method(), expected); + return this; + } + + public RequestTemplateAssert hasUrl(String expected) { + isNotNull(); + objects.assertEqual(info, actual.url(), expected); + return this; + } + + public RequestTemplateAssert hasBody(String utf8Expected) { + return hasBody(utf8Expected.getBytes(UTF_8)); + } + + public RequestTemplateAssert hasBody(byte[] expected) { + isNotNull(); + if (actual.bodyTemplate() != null) { + failWithMessage("\nExpecting bodyTemplate to be null, but was:<%s>", actual.bodyTemplate()); + } + arrays.assertContains(info, actual.body(), expected); + return this; + } + + public RequestTemplateAssert hasBodyTemplate(String expected) { + isNotNull(); + if (actual.body() != null) { + failWithMessage("\nExpecting body to be null, but was:<%s>", actual.bodyTemplate()); + } + objects.assertEqual(info, actual.bodyTemplate(), expected); + return this; + } + + public RequestTemplateAssert hasQueries(MapEntry... entries) { + isNotNull(); + maps.assertContainsExactly(info, actual.queries(), entries); + return this; + } + + public RequestTemplateAssert hasHeaders(MapEntry... entries) { + isNotNull(); + maps.assertContainsExactly(info, actual.headers(), entries); + return this; + } +} diff --git a/core/src/test/java/feign/auth/BasicAuthRequestInterceptorTest.java b/core/src/test/java/feign/auth/BasicAuthRequestInterceptorTest.java index 56745b0c..ab332951 100644 --- a/core/src/test/java/feign/auth/BasicAuthRequestInterceptorTest.java +++ b/core/src/test/java/feign/auth/BasicAuthRequestInterceptorTest.java @@ -19,33 +19,33 @@ import feign.RequestTemplate; import java.util.Collections; import org.junit.Test; +import static feign.assertj.FeignAssertions.assertThat; +import static java.util.Arrays.asList; +import static org.assertj.core.data.MapEntry.entry; import static org.junit.Assert.assertEquals; -/** - * Tests for {@link BasicAuthRequestInterceptor}. - */ public class BasicAuthRequestInterceptorTest { - /** - * Tests that request headers are added as expected. - */ - @Test public void testAuthentication() { + + @Test public void addsAuthorizationHeader() { RequestTemplate template = new RequestTemplate(); BasicAuthRequestInterceptor interceptor = new BasicAuthRequestInterceptor("Aladdin", "open sesame"); interceptor.apply(template); - assertEquals(Collections.singletonList("Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ=="), - template.headers().get("Authorization")); + + assertThat(template) + .hasHeaders( + entry("Authorization", asList("Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==")) + ); } - /** - * Tests that requests headers are added as expected when user and pass are too long - */ - @Test public void testAuthenticationWhenUserPassAreTooLong() { + @Test public void addsAuthorizationHeader_longUserAndPassword() { RequestTemplate template = new RequestTemplate(); BasicAuthRequestInterceptor interceptor = new BasicAuthRequestInterceptor("IOIOIOIOIOIOIOIOIOIOIOIOIOIOIOIOIOIOIO", "101010101010101010101010101010101010101010"); interceptor.apply(template); - assertEquals(Collections.singletonList( - "Basic SU9JT0lPSU9JT0lPSU9JT0lPSU9JT0lPSU9JT0lPSU9JT0lPSU86MTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEw"), - template.headers().get("Authorization")); + + assertThat(template) + .hasHeaders( + entry("Authorization", asList("Basic SU9JT0lPSU9JT0lPSU9JT0lPSU9JT0lPSU9JT0lPSU9JT0lPSU86MTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEw")) + ); } } diff --git a/core/src/test/java/feign/codec/DefaultErrorDecoderTest.java b/core/src/test/java/feign/codec/DefaultErrorDecoderTest.java index f3814389..d78b022e 100644 --- a/core/src/test/java/feign/codec/DefaultErrorDecoderTest.java +++ b/core/src/test/java/feign/codec/DefaultErrorDecoderTest.java @@ -15,11 +15,12 @@ */ package feign.codec; -import com.google.common.collect.ImmutableMap; -import com.google.common.collect.ImmutableMultimap; import feign.FeignException; import feign.Response; +import java.util.Arrays; import java.util.Collection; +import java.util.LinkedHashMap; +import java.util.Map; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; @@ -32,12 +33,13 @@ public class DefaultErrorDecoderTest { ErrorDecoder errorDecoder = new ErrorDecoder.Default(); + Map> headers = new LinkedHashMap>(); + @Test public void throwsFeignException() throws Throwable { thrown.expect(FeignException.class); thrown.expectMessage("status 500 reading Service#foo()"); - - Response response = Response.create(500, "Internal server error", ImmutableMap.>of(), - null); + + Response response = Response.create(500, "Internal server error", headers, null); throw errorDecoder.decode("Service#foo()", response); } @@ -46,8 +48,7 @@ public class DefaultErrorDecoderTest { thrown.expect(FeignException.class); thrown.expectMessage("status 500 reading Service#foo(); content:\nhello world"); - Response response = Response.create(500, "Internal server error", ImmutableMap.>of(), - "hello world", UTF_8); + Response response = Response.create(500, "Internal server error", headers, "hello world", UTF_8); throw errorDecoder.decode("Service#foo()", response); } @@ -56,8 +57,8 @@ public class DefaultErrorDecoderTest { thrown.expect(FeignException.class); thrown.expectMessage("status 503 reading Service#foo()"); - Response response = Response.create(503, "Service Unavailable", - ImmutableMultimap.of(RETRY_AFTER, "Sat, 1 Jan 2000 00:00:00 GMT").asMap(), null); + headers.put(RETRY_AFTER, Arrays.asList("Sat, 1 Jan 2000 00:00:00 GMT")); + Response response = Response.create(503, "Service Unavailable", headers, null); throw errorDecoder.decode("Service#foo()", response); } diff --git a/gson/build.gradle b/gson/build.gradle index c0a064ac..836ea53c 100644 --- a/gson/build.gradle +++ b/gson/build.gradle @@ -6,4 +6,6 @@ dependencies { compile project(':feign-core') compile 'com.google.code.gson:gson:2.2.4' testCompile 'junit:junit:4.12' + testCompile 'org.assertj:assertj-core:1.7.1' + testCompile project(':feign-core').sourceSets.test.output // for assertions } diff --git a/gson/src/test/java/feign/gson/GsonModuleTest.java b/gson/src/test/java/feign/gson/GsonModuleTest.java index 341a4c35..fdf95cf0 100644 --- a/gson/src/test/java/feign/gson/GsonModuleTest.java +++ b/gson/src/test/java/feign/gson/GsonModuleTest.java @@ -38,6 +38,7 @@ import javax.inject.Inject; import org.junit.Test; import static feign.Util.UTF_8; +import static feign.assertj.FeignAssertions.assertThat; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; @@ -62,11 +63,6 @@ public class GsonModuleTest { } @Test public void encodesMapObjectNumericalValuesAsInteger() throws Exception { - String expectedBody = "" - + "{\n" - + " \"foo\": 1\n" - + "}"; - EncoderBindings bindings = new EncoderBindings(); ObjectGraph.create(bindings).inject(bindings); @@ -75,18 +71,14 @@ public class GsonModuleTest { RequestTemplate template = new RequestTemplate(); bindings.encoder.encode(map, template); - assertEquals(expectedBody, new String(template.body(), UTF_8)); + + assertThat(template).hasBody("" // + + "{\n" // + + " \"foo\": 1\n" // + + "}"); } @Test public void encodesFormParams() throws Exception { - String expectedBody = ""// - + "{\n" // - + " \"foo\": 1,\n" // - + " \"bar\": [\n" // - + " 2,\n" // - + " 3\n" // - + " ]\n" // - + "}"; EncoderBindings bindings = new EncoderBindings(); ObjectGraph.create(bindings).inject(bindings); @@ -97,7 +89,15 @@ public class GsonModuleTest { RequestTemplate template = new RequestTemplate(); bindings.encoder.encode(form, template); - assertEquals(expectedBody, new String(template.body(), UTF_8)); + + assertThat(template).hasBody("" // + + "{\n" // + + " \"foo\": 1,\n" // + + " \"bar\": [\n" // + + " 2,\n" // + + " 3\n" // + + " ]\n" // + + "}"); } static class Zone extends LinkedHashMap { diff --git a/jackson/build.gradle b/jackson/build.gradle index ca2414ca..d8b7ea9f 100644 --- a/jackson/build.gradle +++ b/jackson/build.gradle @@ -6,5 +6,6 @@ dependencies { compile project(':feign-core') compile 'com.fasterxml.jackson.core:jackson-databind:2.2.2' testCompile 'junit:junit:4.12' - testCompile 'com.google.guava:guava:14.0.1' + testCompile 'org.assertj:assertj-core:1.7.1' + testCompile project(':feign-core').sourceSets.test.output // for assertions } diff --git a/jackson/src/test/java/feign/jackson/JacksonModuleTest.java b/jackson/src/test/java/feign/jackson/JacksonModuleTest.java index 22bcb225..2aa8f9f0 100644 --- a/jackson/src/test/java/feign/jackson/JacksonModuleTest.java +++ b/jackson/src/test/java/feign/jackson/JacksonModuleTest.java @@ -2,10 +2,10 @@ package feign.jackson; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonToken; +import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.deser.std.StdDeserializer; import com.fasterxml.jackson.databind.module.SimpleModule; -import com.google.common.reflect.TypeToken; import dagger.Module; import dagger.ObjectGraph; import dagger.Provides; @@ -25,6 +25,7 @@ import javax.inject.Inject; import org.junit.Test; import static feign.Util.UTF_8; +import static feign.assertj.FeignAssertions.assertThat; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; @@ -60,10 +61,11 @@ public class JacksonModuleTest { RequestTemplate template = new RequestTemplate(); bindings.encoder.encode(map, template); - assertEquals(""// + + assertThat(template).hasBody(""// + "{\n" // + " \"foo\" : 1\n" // - + "}", new String(template.body(), UTF_8)); + + "}"); } @Test public void encodesFormParams() throws Exception { @@ -76,11 +78,12 @@ public class JacksonModuleTest { RequestTemplate template = new RequestTemplate(); bindings.encoder.encode(form, template); - assertEquals(""// + + assertThat(template).hasBody(""// + "{\n" // + " \"foo\" : 1,\n" // + " \"bar\" : [ 2, 3 ]\n" // - + "}", new String(template.body(), UTF_8)); + + "}"); } static class Zone extends LinkedHashMap { @@ -117,7 +120,7 @@ public class JacksonModuleTest { Response response = Response.create(200, "OK", Collections.>emptyMap(), zonesJson, UTF_8); - assertEquals(zones, bindings.decoder.decode(response, new TypeToken>() { + assertEquals(zones, bindings.decoder.decode(response, new TypeReference>() { }.getType())); } @@ -186,7 +189,7 @@ public class JacksonModuleTest { Response response = Response.create(200, "OK", Collections.>emptyMap(), zonesJson, UTF_8); - assertEquals(zones, bindings.decoder.decode(response, new TypeToken>() { + assertEquals(zones, bindings.decoder.decode(response, new TypeReference>() { }.getType())); } } diff --git a/jaxb/build.gradle b/jaxb/build.gradle index 4dda750f..fda54f4a 100644 --- a/jaxb/build.gradle +++ b/jaxb/build.gradle @@ -3,5 +3,6 @@ apply plugin: 'java' dependencies { compile project(':feign-core') testCompile 'junit:junit:4.12' - testCompile 'com.google.guava:guava:14.0.1' + testCompile 'org.assertj:assertj-core:1.7.1' + testCompile project(':feign-core').sourceSets.test.output // for assertions } diff --git a/jaxb/src/test/java/feign/jaxb/JAXBModuleTest.java b/jaxb/src/test/java/feign/jaxb/JAXBModuleTest.java index ec40f104..bc0ed745 100644 --- a/jaxb/src/test/java/feign/jaxb/JAXBModuleTest.java +++ b/jaxb/src/test/java/feign/jaxb/JAXBModuleTest.java @@ -15,24 +15,23 @@ */ package feign.jaxb; -import com.google.common.reflect.TypeToken; import dagger.Module; import dagger.ObjectGraph; import feign.RequestTemplate; import feign.Response; import feign.codec.Decoder; import feign.codec.Encoder; - +import java.util.Collection; +import java.util.Collections; import javax.inject.Inject; import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlRootElement; -import java.util.Collection; -import java.util.Collections; import org.junit.Test; import static feign.Util.UTF_8; +import static feign.assertj.FeignAssertions.assertThat; import static org.junit.Assert.assertEquals; public class JAXBModuleTest { @@ -71,24 +70,13 @@ public class JAXBModuleTest { @XmlElement private String value; - public String getValue() { - return value; - } - - public void setValue(String value) { - this.value = value; - } - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - MockObject that = (MockObject) o; - - if (value != null ? !value.equals(that.value) : that.value != null) return false; - - return true; + public boolean equals(Object obj) { + if (obj instanceof MockObject) { + MockObject other = (MockObject) obj; + return value.equals(other.value); + } + return false; } @Override @@ -103,14 +91,13 @@ public class JAXBModuleTest { ObjectGraph.create(bindings).inject(bindings); MockObject mock = new MockObject(); - mock.setValue("Test"); + mock.value = "Test"; RequestTemplate template = new RequestTemplate(); bindings.encoder.encode(mock, template); - assertEquals( - "Test", - new String(template.body(), UTF_8)); + assertThat(template).hasBody( + "Test"); } @Test @@ -123,14 +110,13 @@ public class JAXBModuleTest { Encoder encoder = jaxbModule.encoder(new JAXBEncoder(jaxbContextFactory)); MockObject mock = new MockObject(); - mock.setValue("Test"); + mock.value = "Test"; RequestTemplate template = new RequestTemplate(); encoder.encode(mock, template); - assertEquals("Test", - new String(template.body(), UTF_8)); + assertThat(template).hasBody("Test"); } @Test @@ -143,15 +129,15 @@ public class JAXBModuleTest { Encoder encoder = jaxbModule.encoder(new JAXBEncoder(jaxbContextFactory)); MockObject mock = new MockObject(); - mock.setValue("Test"); + mock.value = "Test"; RequestTemplate template = new RequestTemplate(); encoder.encode(mock, template); - assertEquals("" + - "Test", new String(template.body(), UTF_8)); + assertThat(template).hasBody("" + + "Test"); } @Test @@ -164,15 +150,15 @@ public class JAXBModuleTest { Encoder encoder = jaxbModule.encoder(new JAXBEncoder(jaxbContextFactory)); MockObject mock = new MockObject(); - mock.setValue("Test"); + mock.value = "Test"; RequestTemplate template = new RequestTemplate(); encoder.encode(mock, template); - assertEquals("" + - "Test", new String(template.body(), UTF_8)); + assertThat(template).hasBody("" + + "Test"); } @Test @@ -185,20 +171,18 @@ public class JAXBModuleTest { Encoder encoder = jaxbModule.encoder(new JAXBEncoder(jaxbContextFactory)); MockObject mock = new MockObject(); - mock.setValue("Test"); + mock.value = "Test"; RequestTemplate template = new RequestTemplate(); encoder.encode(mock, template); String NEWLINE = System.getProperty("line.separator"); - StringBuilder expectedXml = new StringBuilder(); - expectedXml.append("").append(NEWLINE) + assertThat(template).hasBody(new StringBuilder() + .append("").append(NEWLINE) .append("").append(NEWLINE) .append(" Test").append(NEWLINE) - .append("").append(NEWLINE); - - assertEquals(expectedXml.toString(), new String(template.body(), UTF_8)); + .append("").append(NEWLINE).toString()); } @Test @@ -207,7 +191,7 @@ public class JAXBModuleTest { ObjectGraph.create(bindings).inject(bindings); MockObject mock = new MockObject(); - mock.setValue("Test"); + mock.value = "Test"; String mockXml = "" + "Test"; @@ -215,6 +199,6 @@ public class JAXBModuleTest { Response response = Response.create(200, "OK", Collections.>emptyMap(), mockXml, UTF_8); - assertEquals(mock, bindings.decoder.decode(response, new TypeToken() {}.getType())); + assertEquals(mock, bindings.decoder.decode(response, MockObject.class)); } } diff --git a/jaxb/src/test/java/feign/jaxb/examples/AWSSignatureVersion4.java b/jaxb/src/test/java/feign/jaxb/examples/AWSSignatureVersion4.java index 0d9e3b84..aaacbe71 100644 --- a/jaxb/src/test/java/feign/jaxb/examples/AWSSignatureVersion4.java +++ b/jaxb/src/test/java/feign/jaxb/examples/AWSSignatureVersion4.java @@ -1,5 +1,5 @@ /* - * Copyright 2014 Netflix, Inc. + * Copyright 2013 Netflix, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,30 +15,20 @@ */ package feign.jaxb.examples; -import com.google.common.base.Function; -import com.google.common.base.Joiner; -import com.google.common.collect.Multimap; -import com.google.common.collect.TreeMultimap; import feign.Request; import feign.RequestTemplate; - -import javax.crypto.Mac; -import javax.crypto.spec.SecretKeySpec; import java.net.URI; +import java.security.MessageDigest; import java.text.SimpleDateFormat; -import java.util.Collection; import java.util.Date; -import java.util.Map.Entry; import java.util.TimeZone; +import javax.crypto.Mac; +import javax.crypto.spec.SecretKeySpec; -import static com.google.common.base.Throwables.propagate; -import static com.google.common.collect.Iterables.transform; -import static com.google.common.hash.Hashing.sha256; -import static com.google.common.io.BaseEncoding.base16; import static feign.Util.UTF_8; // http://docs.aws.amazon.com/general/latest/gr/signature-version-4.html -public class AWSSignatureVersion4 implements Function { +public class AWSSignatureVersion4 { String region = "us-east-1"; String service = "iam"; @@ -50,31 +40,30 @@ public class AWSSignatureVersion4 implements Function this.secretKey = secretKey; } - @Override public Request apply(RequestTemplate input) { - input.header("Host", URI.create(input.url()).getHost()); - TreeMultimap sortedLowercaseHeaders = TreeMultimap.create(); - for (String key : input.headers().keySet()) { - sortedLowercaseHeaders.putAll(trimToLowercase.apply(key), - transform(input.headers().get(key), trimToLowercase)); - } + public Request apply(RequestTemplate input) { + if (!input.headers().isEmpty()) throw new UnsupportedOperationException("headers not supported"); + if (input.body() != null) throw new UnsupportedOperationException("body not supported"); + + String host = URI.create(input.url()).getHost(); String timestamp; synchronized (iso8601) { timestamp = iso8601.format(new Date()); } - String credentialScope = Joiner.on('/').join(timestamp.substring(0, 8), region, service, "aws4_request"); + String credentialScope = String.format("%s/%s/%s/%s", timestamp.substring(0, 8), region, service, "aws4_request"); input.query("X-Amz-Algorithm", "AWS4-HMAC-SHA256"); input.query("X-Amz-Credential", accessKey + "/" + credentialScope); input.query("X-Amz-Date", timestamp); - input.query("X-Amz-SignedHeaders", Joiner.on(';').join(sortedLowercaseHeaders.keySet())); + input.query("X-Amz-SignedHeaders", "host"); + input.header("Host", host); - String canonicalString = canonicalString(input, sortedLowercaseHeaders); + String canonicalString = canonicalString(input, host); String toSign = toSign(timestamp, credentialScope, canonicalString); byte[] signatureKey = signatureKey(secretKey, timestamp); - String signature = base16().lowerCase().encode(hmacSHA256(toSign, signatureKey)); + String signature = hex(hmacSHA256(toSign, signatureKey)); input.query("X-Amz-Signature", signature); @@ -97,13 +86,13 @@ public class AWSSignatureVersion4 implements Function mac.init(new SecretKeySpec(key, algorithm)); return mac.doFinal(data.getBytes(UTF_8)); } catch (Exception e) { - throw propagate(e); + throw new RuntimeException(e); } } private static final String EMPTY_STRING_HASH = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"; - private String canonicalString(RequestTemplate input, Multimap sortedLowercaseHeaders) { + private static String canonicalString(RequestTemplate input, String host) { StringBuilder canonicalRequest = new StringBuilder(); // HTTPRequestMethod + '\n' + canonicalRequest.append(input.method()).append('\n'); @@ -116,33 +105,25 @@ public class AWSSignatureVersion4 implements Function canonicalRequest.append('\n'); // CanonicalHeaders + '\n' + - for (Entry> entry : sortedLowercaseHeaders.asMap().entrySet()) { - canonicalRequest.append(entry.getKey()).append(':').append(Joiner.on(',').join(entry.getValue())) - .append('\n'); - } + canonicalRequest.append("host:").append(host).append('\n'); + canonicalRequest.append('\n'); // SignedHeaders + '\n' + - canonicalRequest.append(Joiner.on(',').join(sortedLowercaseHeaders.keySet())).append('\n'); + canonicalRequest.append("host").append('\n'); // HexEncode(Hash(Payload)) String bodyText = - input.charset() != null && input.body() != null ? new String(input.body(), input.charset()) : null; + input.charset() != null && input.body() != null ? new String(input.body(), input.charset()) : null; if (bodyText != null) { - canonicalRequest.append(base16().lowerCase().encode(sha256().hashString(bodyText, UTF_8).asBytes())); + canonicalRequest.append(hex(sha256(bodyText))); } else { canonicalRequest.append(EMPTY_STRING_HASH); } return canonicalRequest.toString(); } - private static final Function trimToLowercase = new Function() { - public String apply(String in) { - return in == null ? null : in.toLowerCase().trim(); - } - }; - - private String toSign(String timestamp, String credentialScope, String canonicalRequest) { + private static String toSign(String timestamp, String credentialScope, String canonicalRequest) { StringBuilder toSign = new StringBuilder(); // Algorithm + '\n' + toSign.append("AWS4-HMAC-SHA256").append('\n'); @@ -151,10 +132,28 @@ public class AWSSignatureVersion4 implements Function // CredentialScope + '\n' + toSign.append(credentialScope).append('\n'); // HexEncode(Hash(CanonicalRequest)) - toSign.append(base16().lowerCase().encode(sha256().hashString(canonicalRequest, UTF_8).asBytes())); + toSign.append(hex(sha256(canonicalRequest))); return toSign.toString(); } + + private static String hex(byte[] data) { + StringBuilder result = new StringBuilder(data.length * 2); + for (byte b : data) { + result.append(String.format("%02x", b & 0xff)); + } + return result.toString(); + } + + static byte[] sha256(String data) { + try { + MessageDigest digest = MessageDigest.getInstance("SHA-256"); + return digest.digest(data.getBytes(UTF_8)); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + private static final SimpleDateFormat iso8601 = new SimpleDateFormat("yyyyMMdd'T'HHmmss'Z'"); static { diff --git a/jaxb/src/test/java/feign/jaxb/examples/IAMExample.java b/jaxb/src/test/java/feign/jaxb/examples/IAMExample.java index dd661017..cdf64245 100644 --- a/jaxb/src/test/java/feign/jaxb/examples/IAMExample.java +++ b/jaxb/src/test/java/feign/jaxb/examples/IAMExample.java @@ -22,7 +22,6 @@ import feign.RequestTemplate; import feign.Target; import feign.jaxb.JAXBContextFactory; import feign.jaxb.JAXBDecoder; - import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlElement; @@ -41,8 +40,7 @@ public class IAMExample { .target(new IAMTarget(args[0], args[1])); GetUserResponse response = iam.userResponse(); - System.out.println("UserId: " + response.getUserResult().getUser().getUserId()); - System.out.println("UserName: " + response.getUserResult().getUser().getUsername()); + System.out.println("UserId: " + response.result.user.id); } static class IAMTarget extends AWSSignatureVersion4 implements Target { @@ -73,43 +71,7 @@ public class IAMExample { @XmlAccessorType(XmlAccessType.FIELD) static class GetUserResponse { @XmlElement(name = "GetUserResult") - private GetUserResult userResult; - - @XmlElement(name = "ResponseMetadata") - private ResponseMetadata responseMetadata; - - public GetUserResult getUserResult() { - return userResult; - } - - public void setUserResult(GetUserResult userResult) { - this.userResult = userResult; - } - - public ResponseMetadata getResponseMetadata() { - return responseMetadata; - } - - public void setResponseMetadata(ResponseMetadata responseMetadata) { - this.responseMetadata = responseMetadata; - } - } - - @XmlAccessorType(XmlAccessType.FIELD) - @XmlType(name = "ResponseMetadata") - static class ResponseMetadata { - @XmlElement(name = "RequestId") - private String requestId; - - public ResponseMetadata() {} - - public String getRequestId() { - return requestId; - } - - public void setRequestId(String requestId) { - this.requestId = requestId; - } + private GetUserResult result; } @XmlAccessorType(XmlAccessType.FIELD) @@ -117,76 +79,12 @@ public class IAMExample { static class GetUserResult { @XmlElement(name = "User") private User user; - - public GetUserResult() {} - - public User getUser() { - return user; - } - - public void setUser(User user) { - this.user = user; - } } @XmlAccessorType(XmlAccessType.FIELD) @XmlType(name = "User") static class User { @XmlElement(name = "UserId") - private String userId; - - @XmlElement(name = "Path") - private String path; - - @XmlElement(name = "UserName") - private String username; - - @XmlElement(name = "Arn") - private String arn; - - @XmlElement(name = "CreateDate") - private String createDate; - - public User() {} - - public String getUserId() { - return userId; - } - - public void setUserId(String userId) { - this.userId = userId; - } - - public String getPath() { - return path; - } - - public void setPath(String path) { - this.path = path; - } - - public String getUsername() { - return username; - } - - public void setUsername(String username) { - this.username = username; - } - - public String getArn() { - return arn; - } - - public void setArn(String arn) { - this.arn = arn; - } - - public String getCreateDate() { - return createDate; - } - - public void setCreateDate(String createDate) { - this.createDate = createDate; - } + private String id; } } diff --git a/jaxrs/build.gradle b/jaxrs/build.gradle index fdb8f2da..fc5995bb 100644 --- a/jaxrs/build.gradle +++ b/jaxrs/build.gradle @@ -5,7 +5,8 @@ sourceCompatibility = 1.6 dependencies { compile project(':feign-core') compile 'javax.ws.rs:jsr311-api:1.1.1' - testCompile project(':feign-gson') - testCompile 'com.google.guava:guava:14.0.1' testCompile 'junit:junit:4.12' + testCompile 'org.assertj:assertj-core:1.7.1' + testCompile project(':feign-core').sourceSets.test.output // for assertions + testCompile project(':feign-gson') // for github example } diff --git a/jaxrs/src/test/java/feign/jaxrs/JAXRSContractTest.java b/jaxrs/src/test/java/feign/jaxrs/JAXRSContractTest.java index c9bd9878..a88fcb55 100644 --- a/jaxrs/src/test/java/feign/jaxrs/JAXRSContractTest.java +++ b/jaxrs/src/test/java/feign/jaxrs/JAXRSContractTest.java @@ -15,7 +15,6 @@ */ package feign.jaxrs; -import com.google.gson.reflect.TypeToken; import feign.MethodMetadata; import feign.Response; import java.lang.annotation.ElementType; @@ -23,7 +22,6 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.net.URI; -import java.util.Arrays; import java.util.List; import javax.ws.rs.Consumes; import javax.ws.rs.DELETE; @@ -41,17 +39,9 @@ import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; -import static feign.jaxrs.JAXRSModule.ACCEPT; -import static feign.jaxrs.JAXRSModule.CONTENT_TYPE; -import static javax.ws.rs.HttpMethod.DELETE; -import static javax.ws.rs.HttpMethod.GET; -import static javax.ws.rs.HttpMethod.POST; -import static javax.ws.rs.HttpMethod.PUT; -import static javax.ws.rs.core.MediaType.APPLICATION_JSON; -import static javax.ws.rs.core.MediaType.APPLICATION_XML; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; +import static feign.assertj.FeignAssertions.assertThat; +import static java.util.Arrays.asList; +import static org.assertj.core.data.MapEntry.entry; /** * Tests interfaces defined per {@link feign.jaxrs.JAXRSModule.JAXRSContract} are interpreted into expected {@link feign @@ -74,32 +64,33 @@ public class JAXRSContractTest { } @Test public void httpMethods() throws Exception { - assertEquals(POST, contract.parseAndValidatateMetadata(Methods.class.getDeclaredMethod("post")).template().method()); - assertEquals(PUT, contract.parseAndValidatateMetadata(Methods.class.getDeclaredMethod("put")).template().method()); - assertEquals(GET, contract.parseAndValidatateMetadata(Methods.class.getDeclaredMethod("get")).template().method()); - assertEquals(DELETE, contract.parseAndValidatateMetadata(Methods.class.getDeclaredMethod("delete")).template().method()); + assertThat(contract.parseAndValidatateMetadata(Methods.class.getDeclaredMethod("post")).template()) + .hasMethod("POST"); + + assertThat(contract.parseAndValidatateMetadata(Methods.class.getDeclaredMethod("put")).template()) + .hasMethod("PUT"); + + assertThat(contract.parseAndValidatateMetadata(Methods.class.getDeclaredMethod("get")).template()) + .hasMethod("GET"); + + assertThat(contract.parseAndValidatateMetadata(Methods.class.getDeclaredMethod("delete")).template()) + .hasMethod("DELETE"); } - interface CustomMethodAndURIParam { + interface CustomMethod { @Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @HttpMethod("PATCH") public @interface PATCH { } - @PATCH Response patch(URI nextLink); + @PATCH Response patch(); } - @Test public void requestLineOnlyRequiresMethod() throws Exception { - MethodMetadata md = contract.parseAndValidatateMetadata(CustomMethodAndURIParam.class.getDeclaredMethod("patch", - URI.class)); - assertEquals("PATCH", md.template().method()); - assertEquals("", md.template().url()); - assertTrue(md.template().queries().isEmpty()); - assertTrue(md.template().headers().isEmpty()); - assertNull(md.template().body()); - assertNull(md.template().bodyTemplate()); - assertEquals(Integer.valueOf(0), md.urlIndex()); + @Test public void customMethodWithoutPath() throws Exception { + assertThat(contract.parseAndValidatateMetadata(CustomMethod.class.getDeclaredMethod("patch")).template()) + .hasMethod("PATCH") + .hasUrl(""); } interface WithQueryParamsInPath { @@ -115,51 +106,48 @@ public class JAXRSContractTest { } @Test public void queryParamsInPathExtract() throws Exception { - { - MethodMetadata md = contract.parseAndValidatateMetadata(WithQueryParamsInPath.class.getDeclaredMethod("none")); - assertEquals("/", md.template().url()); - assertTrue(md.template().queries().isEmpty()); - assertEquals("GET / HTTP/1.1\n", md.template().toString()); - } - { - MethodMetadata md = contract.parseAndValidatateMetadata(WithQueryParamsInPath.class.getDeclaredMethod("one")); - assertEquals("/", md.template().url()); - assertEquals(Arrays.asList("GetUser"), md.template().queries().get("Action")); - assertEquals("GET /?Action=GetUser HTTP/1.1\n", md.template().toString()); - } - { - MethodMetadata md = contract.parseAndValidatateMetadata(WithQueryParamsInPath.class.getDeclaredMethod("two")); - assertEquals("/", md.template().url()); - assertEquals(Arrays.asList("GetUser"), md.template().queries().get("Action")); - assertEquals(Arrays.asList("2010-05-08"), md.template().queries().get("Version")); - assertEquals("GET /?Action=GetUser&Version=2010-05-08 HTTP/1.1\n", md.template().toString()); - } - { - MethodMetadata md = contract.parseAndValidatateMetadata(WithQueryParamsInPath.class.getDeclaredMethod("three")); - assertEquals("/", md.template().url()); - assertEquals(Arrays.asList("GetUser"), md.template().queries().get("Action")); - assertEquals(Arrays.asList("2010-05-08"), md.template().queries().get("Version")); - assertEquals(Arrays.asList("1"), md.template().queries().get("limit")); - assertEquals("GET /?Action=GetUser&Version=2010-05-08&limit=1 HTTP/1.1\n", md.template().toString()); - } - { - MethodMetadata md = contract.parseAndValidatateMetadata(WithQueryParamsInPath.class.getDeclaredMethod("empty")); - assertEquals("/", md.template().url()); - assertTrue(md.template().queries().containsKey("flag")); - assertEquals(Arrays.asList("GetUser"), md.template().queries().get("Action")); - assertEquals(Arrays.asList("2010-05-08"), md.template().queries().get("Version")); - assertEquals("GET /?flag&Action=GetUser&Version=2010-05-08 HTTP/1.1\n", md.template().toString()); - } + assertThat(contract.parseAndValidatateMetadata(WithQueryParamsInPath.class.getDeclaredMethod("none")).template()) + .hasUrl("/") + .hasQueries(); + + assertThat(contract.parseAndValidatateMetadata(WithQueryParamsInPath.class.getDeclaredMethod("one")).template()) + .hasUrl("/") + .hasQueries( + entry("Action", asList("GetUser")) + ); + + assertThat(contract.parseAndValidatateMetadata(WithQueryParamsInPath.class.getDeclaredMethod("two")).template()) + .hasUrl("/") + .hasQueries( + entry("Action", asList("GetUser")), + entry("Version", asList("2010-05-08")) + ); + + assertThat(contract.parseAndValidatateMetadata(WithQueryParamsInPath.class.getDeclaredMethod("three")).template()) + .hasUrl("/") + .hasQueries( + entry("Action", asList("GetUser")), + entry("Version", asList("2010-05-08")), + entry("limit", asList("1")) + ); + + assertThat(contract.parseAndValidatateMetadata(WithQueryParamsInPath.class.getDeclaredMethod("empty")).template()) + .hasUrl("/") + .hasQueries( + entry("flag", asList(new String[] { null })), + entry("Action", asList("GetUser")), + entry("Version", asList("2010-05-08")) + ); } interface ProducesAndConsumes { - @GET @Produces(APPLICATION_XML) Response produces(); + @GET @Produces("application/xml") Response produces(); @GET @Produces({}) Response producesNada(); @GET @Produces({""}) Response producesEmpty(); - @POST @Consumes(APPLICATION_JSON) Response consumes(); + @POST @Consumes("application/xml") Response consumes(); @POST @Consumes({}) Response consumesNada(); @@ -168,7 +156,9 @@ public class JAXRSContractTest { @Test public void producesAddsAcceptHeader() throws Exception { MethodMetadata md = contract.parseAndValidatateMetadata(ProducesAndConsumes.class.getDeclaredMethod("produces")); - assertEquals(Arrays.asList(APPLICATION_XML), md.template().headers().get(ACCEPT)); + + assertThat(md.template()) + .hasHeaders(entry("Accept", asList("application/xml"))); } @Test public void producesNada() throws Exception { @@ -187,7 +177,9 @@ public class JAXRSContractTest { @Test public void consumesAddsContentTypeHeader() throws Exception { MethodMetadata md = contract.parseAndValidatateMetadata(ProducesAndConsumes.class.getDeclaredMethod("consumes")); - assertEquals(Arrays.asList(APPLICATION_JSON), md.template().headers().get(CONTENT_TYPE)); + + assertThat(md.template()) + .hasHeaders(entry("Content-Type", asList("application/xml"))); } @Test public void consumesNada() throws Exception { @@ -210,15 +202,16 @@ public class JAXRSContractTest { @POST Response tooMany(List body, List body2); } + private static final List STRING_LIST = null; + @Test public void bodyParamIsGeneric() throws Exception { MethodMetadata md = contract.parseAndValidatateMetadata(BodyParams.class.getDeclaredMethod("post", List.class)); - assertNull(md.template().body()); - assertNull(md.template().bodyTemplate()); - assertNull(md.urlIndex()); - assertEquals(Integer.valueOf(0), md.bodyIndex()); - assertEquals(new TypeToken>() { - }.getType(), md.bodyType()); + + assertThat(md.bodyIndex()) + .isEqualTo(0); + assertThat(md.bodyType()) + .isEqualTo(getClass().getDeclaredField("STRING_LIST").getGenericType()); } @Test public void tooManyBodies() throws Exception { @@ -249,43 +242,47 @@ public class JAXRSContractTest { @GET @Path("/{param}") Response emptyPathParam(@PathParam("") String empty); } - @Test public void pathOnType() throws Exception { - MethodMetadata md = contract.parseAndValidatateMetadata(PathOnType.class.getDeclaredMethod("base")); - assertEquals("/base", md.template().url()); - md = contract.parseAndValidatateMetadata(PathOnType.class.getDeclaredMethod("get")); - assertEquals("/base/specific", md.template().url()); + private MethodMetadata parsePathOnTypeMethod(String name) throws NoSuchMethodException { + return contract.parseAndValidatateMetadata(PathOnType.class.getDeclaredMethod(name)); + } + + @Test public void parsePathMethod() throws Exception { + assertThat(parsePathOnTypeMethod("base").template()) + .hasUrl("/base"); + + assertThat(parsePathOnTypeMethod("get").template()) + .hasUrl("/base/specific"); } @Test public void emptyPathOnMethod() throws Exception { thrown.expect(IllegalStateException.class); thrown.expectMessage("Path.value() was empty on method emptyPath"); - contract.parseAndValidatateMetadata(PathOnType.class.getDeclaredMethod("emptyPath")); + parsePathOnTypeMethod("emptyPath"); } @Test public void emptyPathParam() throws Exception { thrown.expect(IllegalStateException.class); thrown.expectMessage("PathParam.value() was empty on parameter 0"); - contract.parseAndValidatateMetadata(PathOnType.class.getDeclaredMethod("emptyPathParam", String.class)); + contract.parseAndValidatateMetadata( + PathOnType.class.getDeclaredMethod("emptyPathParam", String.class)); } interface WithURIParam { @GET @Path("/{1}/{2}") Response uriParam(@PathParam("1") String one, URI endpoint, @PathParam("2") String two); } - @Test public void methodCanHaveUriParam() throws Exception { - MethodMetadata md = contract.parseAndValidatateMetadata(WithURIParam.class.getDeclaredMethod("uriParam", String.class, - URI.class, String.class)); - assertEquals(Integer.valueOf(1), md.urlIndex()); - } + @Test public void withPathAndURIParams() throws Exception { + MethodMetadata md = contract.parseAndValidatateMetadata( + WithURIParam.class.getDeclaredMethod("uriParam", String.class, URI.class, String.class)); + + assertThat(md.indexToName()).containsExactly( + entry(0, asList("1")), + // Skips 1 as it is a url index! + entry(2, asList("2"))); - @Test public void pathParamsParseIntoIndexToName() throws Exception { - MethodMetadata md = contract.parseAndValidatateMetadata(WithURIParam.class.getDeclaredMethod("uriParam", String.class, - URI.class, String.class)); - assertEquals("/{1}/{2}", md.template().url()); - assertEquals(Arrays.asList("1"), md.indexToName().get(0)); - assertEquals(Arrays.asList("2"), md.indexToName().get(2)); + assertThat(md.urlIndex()).isEqualTo(1); } interface WithPathAndQueryParams { @@ -293,29 +290,25 @@ public class JAXRSContractTest { Response recordsByNameAndType(@PathParam("domainId") int id, @QueryParam("name") String nameFilter, @QueryParam("type") String typeFilter); - @GET Response emptyQueryParam(@QueryParam("") String empty); + @GET Response empty(@QueryParam("") String empty); } - @Test public void mixedRequestLineParams() throws Exception { + @Test public void pathAndQueryParams() throws Exception { MethodMetadata md = contract.parseAndValidatateMetadata(WithPathAndQueryParams.class.getDeclaredMethod ("recordsByNameAndType", int.class, String.class, String.class)); - assertNull(md.template().body()); - assertNull(md.template().bodyTemplate()); - assertTrue(md.template().headers().isEmpty()); - assertEquals("/domains/{domainId}/records", md.template().url()); - assertEquals(Arrays.asList("{name}"), md.template().queries().get("name")); - assertEquals(Arrays.asList("{type}"), md.template().queries().get("type")); - assertEquals(Arrays.asList("domainId"), md.indexToName().get(0)); - assertEquals(Arrays.asList("name"), md.indexToName().get(1)); - assertEquals(Arrays.asList("type"), md.indexToName().get(2)); - assertEquals("GET /domains/{domainId}/records?name={name}&type={type} HTTP/1.1\n", md.template().toString()); + + assertThat(md.template()) + .hasQueries(entry("name", asList("{name}")), entry("type", asList("{type}"))); + + assertThat(md.indexToName()).containsExactly(entry(0, asList("domainId")), + entry(1, asList("name")), entry(2, asList("type"))); } @Test public void emptyQueryParam() throws Exception { thrown.expect(IllegalStateException.class); thrown.expectMessage("QueryParam.value() was empty on parameter 0"); - contract.parseAndValidatateMetadata(WithPathAndQueryParams.class.getDeclaredMethod("emptyQueryParam", String.class)); + contract.parseAndValidatateMetadata(WithPathAndQueryParams.class.getDeclaredMethod("empty", String.class)); } interface FormParams { @@ -330,12 +323,14 @@ public class JAXRSContractTest { MethodMetadata md = contract.parseAndValidatateMetadata(FormParams.class.getDeclaredMethod("login", String.class, String.class, String.class)); - assertNull(md.template().body()); - assertNull(md.template().bodyTemplate()); - assertEquals(Arrays.asList("customer_name", "user_name", "password"), md.formParams()); - assertEquals(Arrays.asList("customer_name"), md.indexToName().get(0)); - assertEquals(Arrays.asList("user_name"), md.indexToName().get(1)); - assertEquals(Arrays.asList("password"), md.indexToName().get(2)); + assertThat(md.formParams()) + .containsExactly("customer_name", "user_name", "password"); + + assertThat(md.indexToName()).containsExactly( + entry(0, asList("customer_name")), + entry(1, asList("user_name")), + entry(2, asList("password")) + ); } @Test public void emptyFormParam() throws Exception { @@ -352,10 +347,14 @@ public class JAXRSContractTest { } @Test public void headerParamsParseIntoIndexToName() throws Exception { - MethodMetadata md = contract.parseAndValidatateMetadata(HeaderParams.class.getDeclaredMethod("logout", String.class)); + MethodMetadata md = + contract.parseAndValidatateMetadata(HeaderParams.class.getDeclaredMethod("logout", String.class)); - assertEquals(Arrays.asList("{Auth-Token}"), md.template().headers().get("Auth-Token")); - assertEquals(Arrays.asList("Auth-Token"), md.indexToName().get(0)); + assertThat(md.template()) + .hasHeaders(entry("Auth-Token", asList("{Auth-Token}"))); + + assertThat(md.indexToName()) + .containsExactly(entry(0, asList("Auth-Token"))); } @Test public void emptyHeaderParam() throws Exception { @@ -371,8 +370,8 @@ public class JAXRSContractTest { } @Test public void pathsWithoutSlashesParseCorrectly() throws Exception { - MethodMetadata md = contract.parseAndValidatateMetadata(PathsWithoutAnySlashes.class.getDeclaredMethod("get")); - assertEquals("/base/specific", md.template().url()); + assertThat(contract.parseAndValidatateMetadata(PathsWithoutAnySlashes.class.getDeclaredMethod("get")).template()) + .hasUrl("/base/specific"); } @Path("/base") @@ -381,8 +380,8 @@ public class JAXRSContractTest { } @Test public void pathsWithSomeSlashesParseCorrectly() throws Exception { - MethodMetadata md = contract.parseAndValidatateMetadata(PathsWithSomeSlashes.class.getDeclaredMethod("get")); - assertEquals("/base/specific", md.template().url()); + assertThat(contract.parseAndValidatateMetadata(PathsWithSomeSlashes.class.getDeclaredMethod("get")).template()) + .hasUrl("/base/specific"); } @Path("base") @@ -391,7 +390,8 @@ public class JAXRSContractTest { } @Test public void pathsWithSomeOtherSlashesParseCorrectly() throws Exception { - MethodMetadata md = contract.parseAndValidatateMetadata(PathsWithSomeOtherSlashes.class.getDeclaredMethod("get")); - assertEquals("/base/specific", md.template().url()); + assertThat(contract.parseAndValidatateMetadata(PathsWithSomeOtherSlashes.class.getDeclaredMethod("get")).template()) + .hasUrl("/base/specific"); + } } diff --git a/ribbon/build.gradle b/ribbon/build.gradle index 862cae5e..05b2c6b7 100644 --- a/ribbon/build.gradle +++ b/ribbon/build.gradle @@ -6,5 +6,6 @@ dependencies { compile project(':feign-core') compile 'com.netflix.ribbon:ribbon-loadbalancer:2.0-RC5' testCompile 'junit:junit:4.12' + testCompile 'org.assertj:assertj-core:1.7.1' testCompile 'com.squareup.okhttp:mockwebserver:2.2.0' } diff --git a/ribbon/src/main/java/feign/ribbon/RibbonClient.java b/ribbon/src/main/java/feign/ribbon/RibbonClient.java index cfa74cfe..1535c24f 100644 --- a/ribbon/src/main/java/feign/ribbon/RibbonClient.java +++ b/ribbon/src/main/java/feign/ribbon/RibbonClient.java @@ -1,6 +1,5 @@ package feign.ribbon; -import com.google.common.base.Throwables; import com.netflix.client.ClientException; import com.netflix.client.ClientFactory; import com.netflix.client.config.IClientConfig; @@ -61,7 +60,7 @@ public class RibbonClient implements Client { if (e.getCause() instanceof IOException) { throw IOException.class.cast(e.getCause()); } - throw Throwables.propagate(e); + throw new RuntimeException(e); } } diff --git a/ribbon/src/test/java/feign/ribbon/RibbonClientTest.java b/ribbon/src/test/java/feign/ribbon/RibbonClientTest.java index d4471169..ca15b9d9 100644 --- a/ribbon/src/test/java/feign/ribbon/RibbonClientTest.java +++ b/ribbon/src/test/java/feign/ribbon/RibbonClientTest.java @@ -31,10 +31,13 @@ import static com.netflix.config.ConfigurationManager.getConfigInstance; import static org.junit.Assert.assertEquals; import javax.inject.Named; +import org.junit.After; import org.junit.Rule; import org.junit.Test; +import org.junit.rules.TestName; public class RibbonClientTest { + @Rule public final TestName testName = new TestName(); @Rule public final MockWebServerRule server1 = new MockWebServerRule(); @Rule public final MockWebServerRule server2 = new MockWebServerRule(); @@ -54,52 +57,37 @@ public class RibbonClientTest { } } - @Test - public void loadBalancingDefaultPolicyRoundRobin() throws IOException, InterruptedException { - String client = "RibbonClientTest-loadBalancingDefaultPolicyRoundRobin"; - String serverListKey = client + ".ribbon.listOfServers"; - + @Test public void loadBalancingDefaultPolicyRoundRobin() throws IOException, InterruptedException { server1.enqueue(new MockResponse().setBody("success!")); server2.enqueue(new MockResponse().setBody("success!")); - getConfigInstance().setProperty(serverListKey, hostAndPort(server1.getUrl("")) + "," + hostAndPort(server2.getUrl(""))); + getConfigInstance().setProperty(serverListKey(), hostAndPort(server1.getUrl("")) + "," + hostAndPort(server2.getUrl(""))); - try { - TestInterface api = Feign.create(TestInterface.class, "http://" + client, new TestInterface.Module(), new RibbonModule()); + TestInterface api = Feign.create(TestInterface.class, "http://" + client(), new TestInterface.Module(), new RibbonModule()); - api.post(); - api.post(); + api.post(); + api.post(); - assertEquals(1, server1.getRequestCount()); - assertEquals(1, server2.getRequestCount()); - // TODO: verify ribbon stats match - // assertEquals(target.lb().getLoadBalancerStats().getSingleServerStat()) - } finally { - getConfigInstance().clearProperty(serverListKey); - } + assertEquals(1, server1.getRequestCount()); + assertEquals(1, server2.getRequestCount()); + // TODO: verify ribbon stats match + // assertEquals(target.lb().getLoadBalancerStats().getSingleServerStat()) } - @Test - public void ioExceptionRetry() throws IOException, InterruptedException { - String client = "RibbonClientTest-ioExceptionRetry"; - String serverListKey = client + ".ribbon.listOfServers"; - + @Test public void ioExceptionRetry() throws IOException, InterruptedException { server1.enqueue(new MockResponse().setSocketPolicy(SocketPolicy.DISCONNECT_AT_START)); server1.enqueue(new MockResponse().setBody("success!")); - getConfigInstance().setProperty(serverListKey, hostAndPort(server1.getUrl(""))); + getConfigInstance().setProperty(serverListKey(), hostAndPort(server1.getUrl(""))); - try { - TestInterface api = Feign.create(TestInterface.class, "http://" + client, new TestInterface.Module(), new RibbonModule()); - api.post(); + TestInterface api = Feign.create(TestInterface.class, "http://" + client(), new TestInterface.Module(), new RibbonModule()); - assertEquals(2, server1.getRequestCount()); - // TODO: verify ribbon stats match - // assertEquals(target.lb().getLoadBalancerStats().getSingleServerStat()) - } finally { - getConfigInstance().clearProperty(serverListKey); - } + api.post(); + + assertEquals(2, server1.getRequestCount()); + // TODO: verify ribbon stats match + // assertEquals(target.lb().getLoadBalancerStats().getSingleServerStat()) } /* @@ -109,61 +97,54 @@ public class RibbonClientTest { invalid characters (ex. space). */ @Test public void urlEncodeQueryStringParameters () throws IOException, InterruptedException { - String client = "RibbonClientTest-urlEncodeQueryStringParameters"; - String serverListKey = client + ".ribbon.listOfServers"; - String queryStringValue = "some string with space"; String expectedQueryStringValue = "some+string+with+space"; String expectedRequestLine = String.format("GET /?a=%s HTTP/1.1", expectedQueryStringValue); server1.enqueue(new MockResponse().setBody("success!")); - getConfigInstance().setProperty(serverListKey, hostAndPort(server1.getUrl(""))); + getConfigInstance().setProperty(serverListKey(), hostAndPort(server1.getUrl(""))); - try { + TestInterface api = Feign.create(TestInterface.class, "http://" + client(), new TestInterface.Module(), new RibbonModule()); - TestInterface api = Feign.create(TestInterface.class, "http://" + client, new TestInterface.Module(), new RibbonModule()); + api.getWithQueryParameters(queryStringValue); - api.getWithQueryParameters(queryStringValue); + final String recordedRequestLine = server1.takeRequest().getRequestLine(); - final String recordedRequestLine = server1.takeRequest().getRequestLine(); - - assertEquals(recordedRequestLine, expectedRequestLine); - } finally { - getConfigInstance().clearProperty(serverListKey); - } + assertEquals(recordedRequestLine, expectedRequestLine); } + @Test public void ioExceptionRetryWithBuilder() throws IOException, InterruptedException { + server1.enqueue(new MockResponse().setSocketPolicy(SocketPolicy.DISCONNECT_AT_START)); + server1.enqueue(new MockResponse().setBody("success!")); - @Test - public void ioExceptionRetryWithBuilder() throws IOException, InterruptedException { - String client = "RibbonClientTest-ioExceptionRetryWithBuilder"; - String serverListKey = client + ".ribbon.listOfServers"; - - server1.enqueue(new MockResponse().setSocketPolicy(SocketPolicy.DISCONNECT_AT_START)); - server1.enqueue(new MockResponse().setBody("success!")); - - getConfigInstance().setProperty(serverListKey, hostAndPort(server1.getUrl(""))); - - try { - - TestInterface api = Feign.builder(). - client(new RibbonClient()). - target(TestInterface.class, "http://" + client); + getConfigInstance().setProperty(serverListKey(), hostAndPort(server1.getUrl(""))); - api.post(); + TestInterface api = Feign.builder(). + client(new RibbonClient()). + target(TestInterface.class, "http://" + client()); - assertEquals(server1.getRequestCount(), 2); - // TODO: verify ribbon stats match - // assertEquals(target.lb().getLoadBalancerStats().getSingleServerStat()) - } finally { - getConfigInstance().clearProperty(serverListKey); - } - } + api.post(); + assertEquals(server1.getRequestCount(), 2); + // TODO: verify ribbon stats match + // assertEquals(target.lb().getLoadBalancerStats().getSingleServerStat()) + } static String hostAndPort(URL url) { // our build slaves have underscores in their hostnames which aren't permitted by ribbon return "localhost:" + url.getPort(); } + + private String client() { + return testName.getMethodName(); + } + + private String serverListKey() { + return client() + ".ribbon.listOfServers"; + } + + @After public void clearServerList() { + getConfigInstance().clearProperty(serverListKey()); + } } diff --git a/sax/build.gradle b/sax/build.gradle index b50c1802..7d9b05db 100644 --- a/sax/build.gradle +++ b/sax/build.gradle @@ -4,6 +4,6 @@ sourceCompatibility = 1.6 dependencies { compile project(':feign-core') - testCompile 'com.google.guava:guava:14.0.1' testCompile 'junit:junit:4.12' + testCompile 'org.assertj:assertj-core:1.7.1' } diff --git a/sax/src/test/java/feign/sax/examples/AWSSignatureVersion4.java b/sax/src/test/java/feign/sax/examples/AWSSignatureVersion4.java index c229587b..53b2671f 100644 --- a/sax/src/test/java/feign/sax/examples/AWSSignatureVersion4.java +++ b/sax/src/test/java/feign/sax/examples/AWSSignatureVersion4.java @@ -15,32 +15,20 @@ */ package feign.sax.examples; -import com.google.common.base.Function; -import com.google.common.base.Joiner; -import com.google.common.collect.Multimap; -import com.google.common.collect.TreeMultimap; - +import feign.Request; +import feign.RequestTemplate; import java.net.URI; +import java.security.MessageDigest; import java.text.SimpleDateFormat; -import java.util.Collection; import java.util.Date; -import java.util.Map.Entry; import java.util.TimeZone; - import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; -import feign.Request; -import feign.RequestTemplate; - -import static com.google.common.base.Throwables.propagate; -import static com.google.common.collect.Iterables.transform; -import static com.google.common.hash.Hashing.sha256; -import static com.google.common.io.BaseEncoding.base16; import static feign.Util.UTF_8; // http://docs.aws.amazon.com/general/latest/gr/signature-version-4.html -public class AWSSignatureVersion4 implements Function { +public class AWSSignatureVersion4 { String region = "us-east-1"; String service = "iam"; @@ -52,31 +40,30 @@ public class AWSSignatureVersion4 implements Function this.secretKey = secretKey; } - @Override public Request apply(RequestTemplate input) { - input.header("Host", URI.create(input.url()).getHost()); - TreeMultimap sortedLowercaseHeaders = TreeMultimap.create(); - for (String key : input.headers().keySet()) { - sortedLowercaseHeaders.putAll(trimToLowercase.apply(key), - transform(input.headers().get(key), trimToLowercase)); - } + public Request apply(RequestTemplate input) { + if (!input.headers().isEmpty()) throw new UnsupportedOperationException("headers not supported"); + if (input.body() != null) throw new UnsupportedOperationException("body not supported"); + + String host = URI.create(input.url()).getHost(); String timestamp; synchronized (iso8601) { timestamp = iso8601.format(new Date()); } - String credentialScope = Joiner.on('/').join(timestamp.substring(0, 8), region, service, "aws4_request"); + String credentialScope = String.format("%s/%s/%s/%s", timestamp.substring(0, 8), region, service, "aws4_request"); input.query("X-Amz-Algorithm", "AWS4-HMAC-SHA256"); input.query("X-Amz-Credential", accessKey + "/" + credentialScope); input.query("X-Amz-Date", timestamp); - input.query("X-Amz-SignedHeaders", Joiner.on(';').join(sortedLowercaseHeaders.keySet())); + input.query("X-Amz-SignedHeaders", "host"); + input.header("Host", host); - String canonicalString = canonicalString(input, sortedLowercaseHeaders); + String canonicalString = canonicalString(input, host); String toSign = toSign(timestamp, credentialScope, canonicalString); byte[] signatureKey = signatureKey(secretKey, timestamp); - String signature = base16().lowerCase().encode(hmacSHA256(toSign, signatureKey)); + String signature = hex(hmacSHA256(toSign, signatureKey)); input.query("X-Amz-Signature", signature); @@ -99,13 +86,13 @@ public class AWSSignatureVersion4 implements Function mac.init(new SecretKeySpec(key, algorithm)); return mac.doFinal(data.getBytes(UTF_8)); } catch (Exception e) { - throw propagate(e); + throw new RuntimeException(e); } } private static final String EMPTY_STRING_HASH = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"; - private String canonicalString(RequestTemplate input, Multimap sortedLowercaseHeaders) { + private static String canonicalString(RequestTemplate input, String host) { StringBuilder canonicalRequest = new StringBuilder(); // HTTPRequestMethod + '\n' + canonicalRequest.append(input.method()).append('\n'); @@ -118,33 +105,25 @@ public class AWSSignatureVersion4 implements Function canonicalRequest.append('\n'); // CanonicalHeaders + '\n' + - for (Entry> entry : sortedLowercaseHeaders.asMap().entrySet()) { - canonicalRequest.append(entry.getKey()).append(':').append(Joiner.on(',').join(entry.getValue())) - .append('\n'); - } + canonicalRequest.append("host:").append(host).append('\n'); + canonicalRequest.append('\n'); // SignedHeaders + '\n' + - canonicalRequest.append(Joiner.on(',').join(sortedLowercaseHeaders.keySet())).append('\n'); + canonicalRequest.append("host").append('\n'); // HexEncode(Hash(Payload)) String bodyText = input.charset() != null && input.body() != null ? new String(input.body(), input.charset()) : null; if (bodyText != null) { - canonicalRequest.append(base16().lowerCase().encode(sha256().hashString(bodyText, UTF_8).asBytes())); + canonicalRequest.append(hex(sha256(bodyText))); } else { canonicalRequest.append(EMPTY_STRING_HASH); } return canonicalRequest.toString(); } - private static final Function trimToLowercase = new Function() { - public String apply(String in) { - return in == null ? null : in.toLowerCase().trim(); - } - }; - - private String toSign(String timestamp, String credentialScope, String canonicalRequest) { + private static String toSign(String timestamp, String credentialScope, String canonicalRequest) { StringBuilder toSign = new StringBuilder(); // Algorithm + '\n' + toSign.append("AWS4-HMAC-SHA256").append('\n'); @@ -153,10 +132,28 @@ public class AWSSignatureVersion4 implements Function // CredentialScope + '\n' + toSign.append(credentialScope).append('\n'); // HexEncode(Hash(CanonicalRequest)) - toSign.append(base16().lowerCase().encode(sha256().hashString(canonicalRequest, UTF_8).asBytes())); + toSign.append(hex(sha256(canonicalRequest))); return toSign.toString(); } + + private static String hex(byte[] data) { + StringBuilder result = new StringBuilder(data.length * 2); + for (byte b : data) { + result.append(String.format("%02x", b & 0xff)); + } + return result.toString(); + } + + static byte[] sha256(String data) { + try { + MessageDigest digest = MessageDigest.getInstance("SHA-256"); + return digest.digest(data.getBytes(UTF_8)); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + private static final SimpleDateFormat iso8601 = new SimpleDateFormat("yyyyMMdd'T'HHmmss'Z'"); static { diff --git a/slf4j/build.gradle b/slf4j/build.gradle index 144e0400..07c7fc78 100644 --- a/slf4j/build.gradle +++ b/slf4j/build.gradle @@ -4,5 +4,6 @@ dependencies { compile project(':feign-core') compile 'org.slf4j:slf4j-api:1.7.5' testCompile 'junit:junit:4.12' + testCompile 'org.assertj:assertj-core:1.7.1' testCompile 'org.slf4j:slf4j-simple:1.7.5' }