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' }