Browse Source

Removes guava test dependency in favor of AssertJ

AssertJ has more powerful test assertions and does not run the risk of
interfering with the classpath of main code, such as guava does. This
removes guava from test and example code and adjusts using AssertJ in
some cases.
pull/144/head
Adrian Cole 10 years ago
parent
commit
3fc385a112
  1. 2
      core/build.gradle
  2. 26
      core/src/test/java/feign/AcceptAllHostnameVerifier.java
  3. 215
      core/src/test/java/feign/DefaultContractTest.java
  4. 22
      core/src/test/java/feign/FeignBuilderTest.java
  5. 116
      core/src/test/java/feign/FeignTest.java
  6. 41
      core/src/test/java/feign/GZIPStreams.java
  7. 18
      core/src/test/java/feign/LoggerTest.java
  8. 200
      core/src/test/java/feign/RequestTemplateTest.java
  9. 38
      core/src/test/java/feign/TrustingSSLSocketFactory.java
  10. 10
      core/src/test/java/feign/assertj/FeignAssertions.java
  11. 10
      core/src/test/java/feign/assertj/MockWebServerAssertions.java
  12. 87
      core/src/test/java/feign/assertj/RecordedRequestAssert.java
  13. 67
      core/src/test/java/feign/assertj/RequestTemplateAssert.java
  14. 32
      core/src/test/java/feign/auth/BasicAuthRequestInterceptorTest.java
  15. 19
      core/src/test/java/feign/codec/DefaultErrorDecoderTest.java
  16. 2
      gson/build.gradle
  17. 30
      gson/src/test/java/feign/gson/GsonModuleTest.java
  18. 3
      jackson/build.gradle
  19. 17
      jackson/src/test/java/feign/jackson/JacksonModuleTest.java
  20. 3
      jaxb/build.gradle
  21. 78
      jaxb/src/test/java/feign/jaxb/JAXBModuleTest.java
  22. 85
      jaxb/src/test/java/feign/jaxb/examples/AWSSignatureVersion4.java
  23. 108
      jaxb/src/test/java/feign/jaxb/examples/IAMExample.java
  24. 5
      jaxrs/build.gradle
  25. 240
      jaxrs/src/test/java/feign/jaxrs/JAXRSContractTest.java
  26. 1
      ribbon/build.gradle
  27. 3
      ribbon/src/main/java/feign/ribbon/RibbonClient.java
  28. 117
      ribbon/src/test/java/feign/ribbon/RibbonClientTest.java
  29. 2
      sax/build.gradle
  30. 83
      sax/src/test/java/feign/sax/examples/AWSSignatureVersion4.java
  31. 1
      slf4j/build.gradle

2
core/build.gradle

@ -3,9 +3,9 @@ apply plugin: 'java' @@ -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'
}

26
core/src/test/java/feign/AcceptAllHostnameVerifier.java

@ -1,26 +0,0 @@ @@ -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;
}
}

215
core/src/test/java/feign/DefaultContractTest.java

@ -15,21 +15,17 @@ @@ -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 { @@ -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 { @@ -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<List<String>>() {
}.getType());
MethodMetadata md = contract.parseAndValidatateMetadata(BodyParams.class.getDeclaredMethod("post", List.class));
assertThat(md.bodyIndex())
.isEqualTo(0);
assertThat(md.bodyType())
.isEqualTo(new TypeToken<List<String>>(){}.getType());
}
@Test public void tooManyBodies() throws Exception {
@ -86,20 +83,14 @@ public class DefaultContractTest { @@ -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 { @@ -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 { @@ -160,33 +148,37 @@ public class DefaultContractTest {
@Test public void bodyWithoutParameters() throws Exception {
MethodMetadata md = contract.parseAndValidatateMetadata(BodyWithoutParameters.class.getDeclaredMethod("post"));
assertEquals("<v01:getAccountsListOfUser/>", new String(md.template().body(), UTF_8));
assertFalse(md.template().bodyTemplate() != null);
assertTrue(md.formParams().isEmpty());
assertTrue(md.indexToName().isEmpty());
assertThat(md.template())
.hasBody("<v01:getAccountsListOfUser/>");
}
@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 { @@ -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 { @@ -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 { @@ -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")));
}
}

22
core/src/test/java/feign/FeignBuilderTest.java

@ -16,7 +16,6 @@ @@ -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; @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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");
}
}

116
core/src/test/java/feign/FeignTest.java

@ -15,12 +15,9 @@ @@ -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; @@ -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; @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -278,6 +279,7 @@ public class FeignTest {
new TestInterface.Module());
api.post();
assertEquals(2, server.getRequestCount());
}
@ -293,14 +295,13 @@ public class FeignTest { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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);
}
}

41
core/src/test/java/feign/GZIPStreams.java

@ -1,41 +0,0 @@ @@ -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<GZIPInputStream> newInputStreamSupplier(InputSupplier<? extends InputStream> supplier) {
return new GZIPInputStreamSupplier(supplier);
}
private static class GZIPInputStreamSupplier implements InputSupplier<GZIPInputStream> {
private final InputSupplier<? extends InputStream> supplier;
GZIPInputStreamSupplier(InputSupplier<? extends InputStream> supplier) {
this.supplier = supplier;
}
@Override
public GZIPInputStream getInput() throws IOException {
return new GZIPInputStream(supplier.getInput());
}
}
}

18
core/src/test/java/feign/LoggerTest.java

@ -15,7 +15,6 @@ @@ -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; @@ -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; @@ -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 { @@ -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 { @@ -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 { @@ -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();
}
};
}

200
core/src/test/java/feign/RequestTemplateTest.java

@ -15,89 +15,84 @@ @@ -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.<String, String> 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 { @@ -106,44 +101,33 @@ public class RequestTemplateTest {
.query("name", "{name}")//
.query("type", "{type}");
template = template.resolve(ImmutableMap.<String, Object>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.<String, Object>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 { @@ -151,28 +135,19 @@ public class RequestTemplateTest {
.bodyTemplate("%7B\"customer_name\": \"{customer_name}\", \"user_name\": \"{user_name}\", " +
"\"password\": \"{password}\"%7D");
template = template.resolve(ImmutableMap.<String, Object>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 { @@ -181,14 +156,17 @@ public class RequestTemplateTest {
.query("optional", "{optional}")//
.query("name", "{nameVariable}");
template = template.resolve(ImmutableMap.<String, Object>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 { @@ -197,11 +175,29 @@ public class RequestTemplateTest {
.query("optional", "{optional}")//
.query("optional2", "{optional2}");
template = template.resolve(ImmutableMap.<String, Object>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<String, Object> mapOf(String key, Object val) {
Map<String, Object> result = new LinkedHashMap<String, Object>();
result.put(key, val);
return result;
}
private static Map<String, Object> mapOf(String k1, Object v1, String k2, Object v2) {
Map<String, Object> result = mapOf(k1, v1);
result.put(k2, v2);
return result;
}
assertEquals("GET /domains/1001/records HTTP/1.1\n", template.toString());
private static Map<String, Object> mapOf(String k1, Object v1, String k2, Object v2, String k3, Object v3) {
Map<String, Object> result = mapOf(k1, v1, k2, v2);
result.put(k3, v3);
return result;
}
}

38
core/src/test/java/feign/TrustingSSLSocketFactory.java

@ -15,13 +15,6 @@ @@ -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; @@ -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; @@ -50,20 +43,17 @@ import static com.google.common.base.Throwables.propagate;
*/
final class TrustingSSLSocketFactory extends SSLSocketFactory implements X509TrustManager, X509KeyManager {
private static LoadingCache<String, SSLSocketFactory> sslSocketFactories =
CacheBuilder.newBuilder().build(new CacheLoader<String, SSLSocketFactory>() {
@Override
public SSLSocketFactory load(String serverAlias) throws Exception {
return new TrustingSSLSocketFactory(serverAlias);
}
});
private static final Map<String, SSLSocketFactory> sslSocketFactories = new LinkedHashMap<String, SSLSocketFactory>();
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 @@ -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 @@ -175,17 +165,15 @@ final class TrustingSSLSocketFactory extends SSLSocketFactory implements X509Tru
return privateKey;
}
private static KeyStore loadKeyStore(InputSupplier<InputStream> 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();
}
}

10
core/src/test/java/feign/assertj/FeignAssertions.java

@ -0,0 +1,10 @@ @@ -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);
}
}

10
core/src/test/java/feign/assertj/MockWebServerAssertions.java

@ -0,0 +1,10 @@ @@ -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);
}
}

87
core/src/test/java/feign/assertj/RecordedRequestAssert.java

@ -0,0 +1,87 @@ @@ -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<RecordedRequestAssert, RecordedRequest> {
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<String> found = new LinkedHashSet<String>();
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));
}
}

67
core/src/test/java/feign/assertj/RequestTemplateAssert.java

@ -0,0 +1,67 @@ @@ -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<RequestTemplateAssert, RequestTemplate> {
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;
}
}

32
core/src/test/java/feign/auth/BasicAuthRequestInterceptorTest.java

@ -19,33 +19,33 @@ import feign.RequestTemplate; @@ -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"))
);
}
}

19
core/src/test/java/feign/codec/DefaultErrorDecoderTest.java

@ -15,11 +15,12 @@ @@ -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 { @@ -32,12 +33,13 @@ public class DefaultErrorDecoderTest {
ErrorDecoder errorDecoder = new ErrorDecoder.Default();
Map<String, Collection<String>> headers = new LinkedHashMap<String, Collection<String>>();
@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.<String, Collection<String>>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 { @@ -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.<String, Collection<String>>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 { @@ -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);
}

2
gson/build.gradle

@ -6,4 +6,6 @@ dependencies { @@ -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
}

30
gson/src/test/java/feign/gson/GsonModuleTest.java

@ -38,6 +38,7 @@ import javax.inject.Inject; @@ -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 { @@ -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 { @@ -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 { @@ -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<String, Object> {

3
jackson/build.gradle

@ -6,5 +6,6 @@ dependencies { @@ -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
}

17
jackson/src/test/java/feign/jackson/JacksonModuleTest.java

@ -2,10 +2,10 @@ package feign.jackson; @@ -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; @@ -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 { @@ -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 { @@ -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<String, Object> {
@ -117,7 +120,7 @@ public class JacksonModuleTest { @@ -117,7 +120,7 @@ public class JacksonModuleTest {
Response response =
Response.create(200, "OK", Collections.<String, Collection<String>>emptyMap(), zonesJson, UTF_8);
assertEquals(zones, bindings.decoder.decode(response, new TypeToken<List<Zone>>() {
assertEquals(zones, bindings.decoder.decode(response, new TypeReference<List<Zone>>() {
}.getType()));
}
@ -186,7 +189,7 @@ public class JacksonModuleTest { @@ -186,7 +189,7 @@ public class JacksonModuleTest {
Response response =
Response.create(200, "OK", Collections.<String, Collection<String>>emptyMap(), zonesJson, UTF_8);
assertEquals(zones, bindings.decoder.decode(response, new TypeToken<List<Zone>>() {
assertEquals(zones, bindings.decoder.decode(response, new TypeReference<List<Zone>>() {
}.getType()));
}
}

3
jaxb/build.gradle

@ -3,5 +3,6 @@ apply plugin: 'java' @@ -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
}

78
jaxb/src/test/java/feign/jaxb/JAXBModuleTest.java

@ -15,24 +15,23 @@ @@ -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 { @@ -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 { @@ -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(
"<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?><mockObject><value>Test</value></mockObject>",
new String(template.body(), UTF_8));
assertThat(template).hasBody(
"<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?><mockObject><value>Test</value></mockObject>");
}
@Test
@ -123,14 +110,13 @@ public class JAXBModuleTest { @@ -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("<?xml version=\"1.0\" encoding=\"UTF-16\" " +
"standalone=\"yes\"?><mockObject><value>Test</value></mockObject>",
new String(template.body(), UTF_8));
assertThat(template).hasBody("<?xml version=\"1.0\" encoding=\"UTF-16\" "
+ "standalone=\"yes\"?><mockObject><value>Test</value></mockObject>");
}
@Test
@ -143,15 +129,15 @@ public class JAXBModuleTest { @@ -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("<?xml version=\"1.0\" encoding=\"UTF-8\" " +
"standalone=\"yes\"?><mockObject xsi:schemaLocation=\"http://apihost " +
"http://apihost/schema.xsd\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">" +
"<value>Test</value></mockObject>", new String(template.body(), UTF_8));
assertThat(template).hasBody("<?xml version=\"1.0\" encoding=\"UTF-8\" " +
"standalone=\"yes\"?><mockObject xsi:schemaLocation=\"http://apihost " +
"http://apihost/schema.xsd\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">" +
"<value>Test</value></mockObject>");
}
@Test
@ -164,15 +150,15 @@ public class JAXBModuleTest { @@ -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("<?xml version=\"1.0\" encoding=\"UTF-8\" " +
"standalone=\"yes\"?><mockObject xsi:noNamespaceSchemaLocation=\"http://apihost/schema.xsd\" " +
"xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">" +
"<value>Test</value></mockObject>", new String(template.body(), UTF_8));
assertThat(template).hasBody("<?xml version=\"1.0\" encoding=\"UTF-8\" " +
"standalone=\"yes\"?><mockObject xsi:noNamespaceSchemaLocation=\"http://apihost/schema.xsd\" " +
"xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">" +
"<value>Test</value></mockObject>");
}
@Test
@ -185,20 +171,18 @@ public class JAXBModuleTest { @@ -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("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>").append(NEWLINE)
assertThat(template).hasBody(new StringBuilder()
.append("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>").append(NEWLINE)
.append("<mockObject>").append(NEWLINE)
.append(" <value>Test</value>").append(NEWLINE)
.append("</mockObject>").append(NEWLINE);
assertEquals(expectedXml.toString(), new String(template.body(), UTF_8));
.append("</mockObject>").append(NEWLINE).toString());
}
@Test
@ -207,7 +191,7 @@ public class JAXBModuleTest { @@ -207,7 +191,7 @@ public class JAXBModuleTest {
ObjectGraph.create(bindings).inject(bindings);
MockObject mock = new MockObject();
mock.setValue("Test");
mock.value = "Test";
String mockXml = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?><mockObject>" +
"<value>Test</value></mockObject>";
@ -215,6 +199,6 @@ public class JAXBModuleTest { @@ -215,6 +199,6 @@ public class JAXBModuleTest {
Response response =
Response.create(200, "OK", Collections.<String, Collection<String>>emptyMap(), mockXml, UTF_8);
assertEquals(mock, bindings.decoder.decode(response, new TypeToken<MockObject>() {}.getType()));
assertEquals(mock, bindings.decoder.decode(response, MockObject.class));
}
}

85
jaxb/src/test/java/feign/jaxb/examples/AWSSignatureVersion4.java

@ -1,5 +1,5 @@ @@ -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 @@ @@ -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<RequestTemplate, Request> {
public class AWSSignatureVersion4 {
String region = "us-east-1";
String service = "iam";
@ -50,31 +40,30 @@ public class AWSSignatureVersion4 implements Function<RequestTemplate, Request> @@ -50,31 +40,30 @@ public class AWSSignatureVersion4 implements Function<RequestTemplate, Request>
this.secretKey = secretKey;
}
@Override public Request apply(RequestTemplate input) {
input.header("Host", URI.create(input.url()).getHost());
TreeMultimap<String, String> 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<RequestTemplate, Request> @@ -97,13 +86,13 @@ public class AWSSignatureVersion4 implements Function<RequestTemplate, Request>
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<String, String> 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<RequestTemplate, Request> @@ -116,33 +105,25 @@ public class AWSSignatureVersion4 implements Function<RequestTemplate, Request>
canonicalRequest.append('\n');
// CanonicalHeaders + '\n' +
for (Entry<String, Collection<String>> 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<String, String> trimToLowercase = new Function<String, String>() {
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<RequestTemplate, Request> @@ -151,10 +132,28 @@ public class AWSSignatureVersion4 implements Function<RequestTemplate, Request>
// 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 {

108
jaxb/src/test/java/feign/jaxb/examples/IAMExample.java

@ -22,7 +22,6 @@ import feign.RequestTemplate; @@ -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 { @@ -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<IAM> {
@ -73,43 +71,7 @@ public class IAMExample { @@ -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 { @@ -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;
}
}

5
jaxrs/build.gradle

@ -5,7 +5,8 @@ sourceCompatibility = 1.6 @@ -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
}

240
jaxrs/src/test/java/feign/jaxrs/JAXRSContractTest.java

@ -15,7 +15,6 @@ @@ -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; @@ -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; @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -210,15 +202,16 @@ public class JAXRSContractTest {
@POST Response tooMany(List<String> body, List<String> body2);
}
private static final List<String> 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<List<String>>() {
}.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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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");
}
}

1
ribbon/build.gradle

@ -6,5 +6,6 @@ dependencies { @@ -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'
}

3
ribbon/src/main/java/feign/ribbon/RibbonClient.java

@ -1,6 +1,5 @@ @@ -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 { @@ -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);
}
}

117
ribbon/src/test/java/feign/ribbon/RibbonClientTest.java

@ -31,10 +31,13 @@ import static com.netflix.config.ConfigurationManager.getConfigInstance; @@ -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 { @@ -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 { @@ -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());
}
}

2
sax/build.gradle

@ -4,6 +4,6 @@ sourceCompatibility = 1.6 @@ -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'
}

83
sax/src/test/java/feign/sax/examples/AWSSignatureVersion4.java

@ -15,32 +15,20 @@ @@ -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<RequestTemplate, Request> {
public class AWSSignatureVersion4 {
String region = "us-east-1";
String service = "iam";
@ -52,31 +40,30 @@ public class AWSSignatureVersion4 implements Function<RequestTemplate, Request> @@ -52,31 +40,30 @@ public class AWSSignatureVersion4 implements Function<RequestTemplate, Request>
this.secretKey = secretKey;
}
@Override public Request apply(RequestTemplate input) {
input.header("Host", URI.create(input.url()).getHost());
TreeMultimap<String, String> 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<RequestTemplate, Request> @@ -99,13 +86,13 @@ public class AWSSignatureVersion4 implements Function<RequestTemplate, Request>
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<String, String> 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<RequestTemplate, Request> @@ -118,33 +105,25 @@ public class AWSSignatureVersion4 implements Function<RequestTemplate, Request>
canonicalRequest.append('\n');
// CanonicalHeaders + '\n' +
for (Entry<String, Collection<String>> 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<String, String> trimToLowercase = new Function<String, String>() {
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<RequestTemplate, Request> @@ -153,10 +132,28 @@ public class AWSSignatureVersion4 implements Function<RequestTemplate, Request>
// 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 {

1
slf4j/build.gradle

@ -4,5 +4,6 @@ dependencies { @@ -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'
}

Loading…
Cancel
Save