/*
* 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.collect.ImmutableMap;
import com.google.common.reflect.TypeToken;
import com.google.mockwebserver.MockResponse;
import com.google.mockwebserver.MockWebServer;
import com.google.mockwebserver.SocketPolicy;
import org.testng.annotations.Test;
import java.io.IOException;
import java.io.Reader;
import java.net.URI;
import java.util.Map;
import javax.inject.Singleton;
import javax.net.ssl.SSLSocketFactory;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import dagger.Module;
import dagger.Provides;
import feign.codec.Decoder;
import feign.codec.ErrorDecoder;
import feign.codec.ToStringDecoder;
import static org.testng.Assert.assertEquals;
@Test
public class FeignTest {
interface TestInterface {
@POST String post();
@GET @Path("/{1}/{2}") Response uriParam(@PathParam("1") String one, URI endpoint, @PathParam("2") String two);
@dagger.Module(overrides = true, library = true)
static class Module {
// until dagger supports real map binding, we need to recreate the
// entire map, as opposed to overriding a single entry.
@Provides @Singleton Map decoders() {
return ImmutableMap.of("TestInterface", new ToStringDecoder());
}
}
}
@Test public void toKeyMethodFormatsAsExpected() throws Exception {
assertEquals(Feign.configKey(TestInterface.class.getDeclaredMethod("post")), "TestInterface#post()");
assertEquals(Feign.configKey(TestInterface.class.getDeclaredMethod("uriParam", String.class, URI.class,
String.class)), "TestInterface#uriParam(String,URI,String)");
}
@Test(expectedExceptions = IllegalArgumentException.class, expectedExceptionsMessageRegExp = "zone not found")
public void canOverrideErrorDecoderOnMethod() throws IOException, InterruptedException {
@dagger.Module(overrides = true, includes = TestInterface.Module.class) class Overrides {
@Provides @Singleton Map decoders() {
return ImmutableMap.of("TestInterface#post()", new ErrorDecoder() {
@Override
public Object decode(String methodKey, Response response, TypeToken> type) throws Throwable {
if (response.status() == 404)
throw new IllegalArgumentException("zone not found");
return ErrorDecoder.DEFAULT.decode(methodKey, response, type);
}
});
}
}
final MockWebServer server = new MockWebServer();
server.enqueue(new MockResponse().setResponseCode(404).setBody("foo"));
server.play();
try {
TestInterface api = Feign.create(TestInterface.class, server.getUrl("").toString(), new Overrides());
api.post();
} finally {
server.shutdown();
}
}
@Test public void retriesLostConnectionBeforeRead() throws IOException, InterruptedException {
MockWebServer server = new MockWebServer();
server.enqueue(new MockResponse().setSocketPolicy(SocketPolicy.DISCONNECT_AT_START));
server.enqueue(new MockResponse().setResponseCode(200).setBody("success!".getBytes()));
server.play();
try {
TestInterface api = Feign.create(TestInterface.class, server.getUrl("").toString(),
new TestInterface.Module());
api.post();
assertEquals(server.getRequestCount(), 2);
} finally {
server.shutdown();
}
}
@Test(expectedExceptions = FeignException.class, expectedExceptionsMessageRegExp = "error reading response POST http://.*")
public void doesntRetryAfterResponseIsSent() throws IOException, InterruptedException {
MockWebServer server = new MockWebServer();
server.enqueue(new MockResponse().setResponseCode(200).setBody("success!".getBytes()));
server.play();
try {
@dagger.Module(overrides = true) class Overrides {
@Provides @Singleton Map decoders() {
return ImmutableMap.of("TestInterface", new Decoder() {
@Override
public Object decode(String methodKey, Reader reader, TypeToken> type) throws Throwable {
throw new IOException("error reading response");
}
});
}
}
TestInterface api = Feign.create(TestInterface.class, server.getUrl("").toString(), new Overrides());
api.post();
} finally {
server.shutdown();
assertEquals(server.getRequestCount(), 1);
}
}
@Module(injects = Client.Default.class, overrides = true)
static class TrustSSLSockets {
@Provides SSLSocketFactory trustingSSLSocketFactory() {
return TrustingSSLSocketFactory.get();
}
}
@Test public void canOverrideSSLSocketFactory() throws IOException, InterruptedException {
MockWebServer server = new MockWebServer();
server.useHttps(TrustingSSLSocketFactory.get(), false);
server.enqueue(new MockResponse().setResponseCode(200).setBody("success!".getBytes()));
server.play();
try {
TestInterface api = Feign.create(TestInterface.class, server.getUrl("").toString(),
new TestInterface.Module(), new TrustSSLSockets());
api.post();
} finally {
server.shutdown();
}
}
@Test public void retriesFailedHandshake() throws IOException, InterruptedException {
MockWebServer server = new MockWebServer();
server.useHttps(TrustingSSLSocketFactory.get(), false);
server.enqueue(new MockResponse().setSocketPolicy(SocketPolicy.FAIL_HANDSHAKE));
server.enqueue(new MockResponse().setResponseCode(200).setBody("success!".getBytes()));
server.play();
try {
TestInterface api = Feign.create(TestInterface.class, server.getUrl("").toString(),
new TestInterface.Module(), new TrustSSLSockets());
api.post();
assertEquals(server.getRequestCount(), 2);
} finally {
server.shutdown();
}
}
}