// Copyright 2013 Square, Inc. package com.github.lpezet.antiope2.retrofitted; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; import org.apache.commons.io.IOUtils; import org.junit.Before; import org.junit.Ignore; import org.junit.Test; import org.mockito.Mockito; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; import rx.Observable; import rx.functions.Action1; import com.github.lpezet.antiope2.dao.http.HttpResponse; import com.github.lpezet.antiope2.dao.http.IHttpNetworkIO; import com.github.lpezet.antiope2.dao.http.IHttpRequest; import com.github.lpezet.antiope2.dao.http.IHttpResponse; import com.github.lpezet.antiope2.retrofitted.AntiopeError; import com.github.lpezet.antiope2.retrofitted.Callback; import com.github.lpezet.antiope2.retrofitted.RestAdapter; import com.github.lpezet.antiope2.retrofitted.annotation.http.Body; import com.github.lpezet.antiope2.retrofitted.annotation.http.GET; import com.github.lpezet.antiope2.retrofitted.annotation.http.POST; import com.github.lpezet.antiope2.retrofitted.annotation.http.Streaming; import com.github.lpezet.antiope2.retrofitted.converter.Converter; import com.github.lpezet.antiope2.retrofitted.converter.GsonConverter; import com.google.gson.JsonParseException; public class RestAdapterTest { private static class SimpleHttpResponse extends HttpResponse { public SimpleHttpResponse(IHttpRequest pRequest, int pStatusCode, String pContent) { super(pRequest); setStatusCode(pStatusCode); setContent(new ByteArrayInputStream(pContent.getBytes())); } public SimpleHttpResponse(IHttpRequest pRequest, int pStatusCode, String pStatusText, String pContent) { super(pRequest); setStatusCode(pStatusCode); setStatusText(pStatusText); if (pContent != null) { setContent(new ByteArrayInputStream(pContent.getBytes())); addHeader("Content-Length", "" + pContent.getBytes().length); } } } private interface Example { @com.github.lpezet.antiope2.retrofitted.annotation.http.Headers("Foo: Bar") @GET("/") String something(); @com.github.lpezet.antiope2.retrofitted.annotation.http.Headers("Foo: Bar") @GET("/") void something(Callback<String> callback); @GET("/") IHttpResponse direct(); @GET("/") void direct(Callback<IHttpResponse> callback); @GET("/") @Streaming IHttpResponse streaming(); @POST("/") Observable<String> observable(@Body String body); } private interface InvalidExample extends Example { } // @Rule public final MockWebServerRule server = new MockWebServerRule(); private Example example; private Converter converter; private IHttpNetworkIO<IHttpRequest, IHttpResponse> client; @Before public void setUp() { // HttpClient oHttpClient = HttpClients.createDefault(); // ApacheHttpClientNetworkIO client = new ApacheHttpClientNetworkIO(oHttpClient); client = mock(IHttpNetworkIO.class); converter = spy(new GsonConverter()); example = new RestAdapter.Builder() // .client(client) /* .callbackExecutor(new Executor() { @Override public void execute(Runnable pCommand) { pCommand.run(); } }) */ .executorService(Executors.newCachedThreadPool()) .endpoint("http://example.com") .converter(converter) .build() .create(Example.class); } @Test public void objectMethodsStillWork() { assertThat(example.hashCode()).isNotZero(); assertThat(example.equals(this)).isFalse(); assertThat(example.toString()).isNotEmpty(); } @Test public void interfaceWithExtendIsNotSupported() { try { new RestAdapter.Builder().endpoint("http://foo/").build().create(InvalidExample.class); fail(); } catch (IllegalArgumentException e) { assertThat(e).hasMessage("Interface definitions must not extend other interfaces."); } } @Test public void http204SkipsConverter() throws Exception { onHttpRequestReturn(204, "Nothin", null); // server.enqueue(new MockResponse().setStatus("HTTP/1.1 204 Nothin")); assertThat(example.something()).isNull(); verifyNoMoreInteractions(converter); } private void onHttpRequestReturn(final int pStatusCode, final String pStatusText, final String pContent) throws Exception { when(client.perform(Mockito.any(IHttpRequest.class))).then(new Answer<IHttpResponse>() { @Override public IHttpResponse answer(InvocationOnMock pInvocation) throws Throwable { IHttpRequest oRequest = (IHttpRequest) pInvocation.getArguments()[0]; return new SimpleHttpResponse(oRequest, pStatusCode, pStatusText, pContent); } }); } private void onHttpRequestReturn(String pContent) throws Exception { onHttpRequestReturn(200, "Ok", pContent); } @Test public void http204Response() throws Exception { onHttpRequestReturn(204, "Nothin", null); IHttpResponse response = example.direct(); assertThat(response.getStatusCode()).isEqualTo(204); } @Test public void http204WithBodyThrows() throws Exception { onHttpRequestReturn(204, "Nothin", "Hey"); try { example.something(); fail(); } catch (AntiopeError e) { assertThat(e).hasMessage("204 response must not include body."); Throwable cause = e.getCause(); assertThat(cause).isInstanceOf(IllegalStateException.class); assertThat(cause).hasMessage("204 response must not include body."); } } @Test public void http205SkipsConverter() throws Exception { onHttpRequestReturn(204, "Nothin", null); assertThat(example.something()).isNull(); verifyNoMoreInteractions(converter); } @Test public void http205Response() throws Exception { onHttpRequestReturn(205, "Nothin", null); IHttpResponse response = example.direct(); assertThat(response.getStatusCode()).isEqualTo(205); } @Test public void http205WithBodyThrows() throws Exception { onHttpRequestReturn(205, "Nothin", "Hey"); try { example.something(); fail(); } catch (AntiopeError e) { assertThat(e).hasMessage("205 response must not include body."); Throwable cause = e.getCause(); assertThat(cause).isInstanceOf(IllegalStateException.class); assertThat(cause).hasMessage("205 response must not include body."); } } @Test public void successfulRequestResponseWhenMimeTypeMissing() throws Exception { onHttpRequestReturn("Hi"); String string = example.something(); assertThat(string).isEqualTo("Hi"); } @Test public void malformedResponseThrowsConversionException() throws Exception { onHttpRequestReturn("{"); try { example.something(); fail(); } catch (AntiopeError e) { assertThat(e.getKind()).isEqualTo(AntiopeError.Kind.UNEXPECTED); assertThat(e.getResponse().getStatusCode()).isEqualTo(200); assertThat(e.getCause()).isInstanceOf(JsonParseException.class); //TODO: WHY???? //assertThat(e.getResponse().getContent()).isNull(); } } @Test public void errorResponseThrowsHttpError() throws Exception { onHttpRequestReturn(500, "Broken", null); // server.enqueue(new MockResponse().setStatus("HTTP/1.1 500 Broken")); try { example.something(); fail(); } catch (AntiopeError e) { assertThat(e.getKind()).isEqualTo(AntiopeError.Kind.HTTP); assertThat(e.getResponse().getStatusCode()).isEqualTo(500); assertThat(e.getSuccessType()).isEqualTo(String.class); } } // TODO: How???? @Ignore @Test public void clientExceptionThrowsNetworkError() throws Exception { // server.enqueue(new MockResponse().setBody("Hi").setSocketPolicy(DISCONNECT_AT_START)); // onHttpRequestReturn(500, "Broken", null); try { example.something(); fail(); } catch (AntiopeError e) { assertThat(e.getKind()).isEqualTo(AntiopeError.Kind.NETWORK); } } private static void assertBody(InputStream body, String expected) { assertThat(body).isNotNull(); try { String oBody = IOUtils.toString(body); assertThat(oBody).isEqualTo(expected); } catch (IOException e) { throw new RuntimeException(e); } } @Test public void getResponseDirectly() throws Exception { // server.enqueue(new MockResponse().setBody("Hey")); onHttpRequestReturn("Hey"); IHttpResponse response = example.direct(); assertBody(response.getContent(), "Hey"); } // TODO: How???? @Ignore @Test public void streamingResponse() throws Exception { // server.enqueue(new MockResponse().setBody("Hey").setBodyDelay(500, MILLISECONDS)); onHttpRequestReturn("Hey"); IHttpResponse response = example.streaming(); long startNs = System.nanoTime(); // response.body().string(); long tookNs = System.nanoTime() - startNs; assertThat(tookNs).isGreaterThanOrEqualTo(500); } @Test public void getResponseDirectlyAsync() throws Exception { // server.enqueue(new MockResponse().setBody("Hey")); onHttpRequestReturn("Hey"); final AtomicReference<IHttpResponse> responseRef = new AtomicReference<IHttpResponse>(); final CountDownLatch latch = new CountDownLatch(1); example.direct(new Callback<IHttpResponse>() { @Override public void success(IHttpResponse response, IHttpResponse response2) { responseRef.set(response); latch.countDown(); } @Override public void failure(AntiopeError error) { throw new AssertionError(); } }); assertTrue(latch.await(1, TimeUnit.SECONDS)); assertBody(responseRef.get().getContent(), "Hey"); } @Test public void getAsync() throws Exception { // server.enqueue(new MockResponse().setBody("Hey")); onHttpRequestReturn("Hey"); final AtomicReference<String> bodyRef = new AtomicReference<String>(); final CountDownLatch latch = new CountDownLatch(1); example.something(new Callback<String>() { @Override public void success(String body, IHttpResponse response) { bodyRef.set(body); latch.countDown(); } @Override public void failure(AntiopeError error) { throw new AssertionError(); } }); assertTrue(latch.await(1, TimeUnit.SECONDS)); assertThat(bodyRef.get()).isEqualTo("Hey"); } @Test public void errorAsync() throws Exception { // server.enqueue(new MockResponse().setStatus("HTTP/1.1 500 Broken!").setBody("Hey")); onHttpRequestReturn(500, "Broken!", "Hey"); final AtomicReference<AntiopeError> errorRef = new AtomicReference<AntiopeError>(); final CountDownLatch latch = new CountDownLatch(1); example.something(new Callback<String>() { @Override public void success(String s, IHttpResponse response) { throw new AssertionError(); } @Override public void failure(AntiopeError error) { errorRef.set(error); latch.countDown(); } }); assertTrue(latch.await(1, TimeUnit.SECONDS)); AntiopeError error = errorRef.get(); assertThat(error.getResponse().getStatusCode()).isEqualTo(500); assertThat(error.getResponse().getStatusText()).isEqualTo("Broken!"); assertThat(error.getSuccessType()).isEqualTo(String.class); assertThat(error.getBody()).isEqualTo("Hey"); } @Test public void observableCallsOnNext() throws Exception { // server.enqueue(new MockResponse().setBody("hello")); onHttpRequestReturn("hello"); final AtomicReference<String> bodyRef = new AtomicReference<String>(); final CountDownLatch latch = new CountDownLatch(1); example.observable("Howdy").subscribe(new Action1<String>() { @Override public void call(String body) { bodyRef.set(body); latch.countDown(); } }); assertTrue(latch.await(1, TimeUnit.SECONDS)); assertThat(bodyRef.get()).isEqualTo("hello"); } @Test public void observableCallsOnError() throws Exception { // server.enqueue(new MockResponse().setResponseCode(500)); onHttpRequestReturn(500, null, null); final AtomicReference<Throwable> errorRef = new AtomicReference<Throwable>(); final CountDownLatch latch = new CountDownLatch(1); example.observable("Howdy").subscribe(new Action1<String>() { @Override public void call(String s) { throw new AssertionError(); } }, new Action1<Throwable>() { @Override public void call(Throwable throwable) { errorRef.set(throwable); latch.countDown(); } }); assertTrue(latch.await(1, TimeUnit.SECONDS)); AntiopeError error = (AntiopeError) errorRef.get(); assertThat(error.getResponse().getStatusCode()).isEqualTo(500); assertThat(error.getSuccessType()).isEqualTo(String.class); } }