package com.apollographql.apollo;
import android.support.annotation.NonNull;
import com.apollographql.android.impl.normalizer.EpisodeHeroName;
import com.apollographql.android.impl.normalizer.type.Episode;
import com.apollographql.apollo.api.Operation;
import com.apollographql.apollo.api.Response;
import com.apollographql.apollo.cache.normalized.Record;
import com.apollographql.apollo.exception.ApolloException;
import com.apollographql.apollo.exception.ApolloParseException;
import com.apollographql.apollo.interceptor.ApolloInterceptor;
import com.apollographql.apollo.interceptor.ApolloInterceptorChain;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import java.io.IOException;
import java.util.Collections;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import javax.annotation.Nonnull;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Protocol;
import okhttp3.Request;
import okhttp3.ResponseBody;
import okhttp3.mockwebserver.MockResponse;
import okhttp3.mockwebserver.MockWebServer;
import static com.apollographql.apollo.interceptor.ApolloInterceptor.InterceptorResponse;
import static com.google.common.truth.Truth.assertThat;
public class ApolloInterceptorTest {
private ApolloClient client;
private MockWebServer mockWebServer;
private OkHttpClient okHttpClient;
private static final String FILE_EPISODE_HERO_NAME_WITH_ID = "EpisodeHeroNameResponseWithId.json";
private static final String FILE_EPISODE_HERO_NAME_CHANGE = "EpisodeHeroNameResponseNameChange.json";
private static final int TIMEOUT_SECONDS = 3;
@Before
public void setup() {
mockWebServer = new MockWebServer();
okHttpClient = new OkHttpClient.Builder().build();
}
@After
public void tearDown() {
try {
mockWebServer.shutdown();
} catch (IOException e) {
}
}
@Test
public void syncApplicationInterceptorCanShortCircuitResponses() throws IOException, ApolloException {
mockWebServer.shutdown();
EpisodeHeroName query = createHeroNameQuery();
final InterceptorResponse expectedResponse = prepareInterceptorResponse(query);
ApolloInterceptor interceptor = new ApolloInterceptor() {
@Nonnull @Override
public InterceptorResponse intercept(Operation operation, ApolloInterceptorChain chain) throws ApolloException {
return expectedResponse;
}
@Override
public void interceptAsync(@Nonnull Operation operation, @Nonnull ApolloInterceptorChain chain,
@Nonnull ExecutorService dispatcher, @Nonnull CallBack callBack) {
//No op
}
@Override public void dispose() {
//No op
}
};
client = createApolloClient(interceptor);
Response<EpisodeHeroName.Data> actualResponse = client.query(query).execute();
assertThat(expectedResponse.parsedResponse.get()).isEqualTo(actualResponse);
}
@Test
public void asyncApplicationInterceptorCanShortCircuitResponses() throws IOException, TimeoutException, InterruptedException {
mockWebServer.shutdown();
final NamedCountDownLatch responseLatch = new NamedCountDownLatch("responseLatch", 1);
mockWebServer.enqueue(mockResponse(FILE_EPISODE_HERO_NAME_WITH_ID));
EpisodeHeroName query = createHeroNameQuery();
final InterceptorResponse expectedResponse = prepareInterceptorResponse(query);
ApolloInterceptor interceptor = new ApolloInterceptor() {
@Nonnull @Override
public InterceptorResponse intercept(Operation operation, ApolloInterceptorChain chain) throws ApolloException {
return null;
}
@Override
public void interceptAsync(@Nonnull Operation operation, @Nonnull ApolloInterceptorChain chain,
@Nonnull ExecutorService dispatcher, @Nonnull CallBack callBack) {
callBack.onResponse(expectedResponse);
}
@Override public void dispose() {
}
};
client = createApolloClient(interceptor);
client.query(query).enqueue(new ApolloCall.Callback<EpisodeHeroName.Data>() {
@Override public void onResponse(@Nonnull Response<EpisodeHeroName.Data> response) {
assertThat(expectedResponse.parsedResponse.get()).isEqualTo(response);
responseLatch.countDown();
}
@Override public void onFailure(@Nonnull ApolloException e) {
}
});
responseLatch.awaitOrThrowWithTimeout(TIMEOUT_SECONDS, TimeUnit.SECONDS);
}
@Test
public void syncApplicationInterceptorRewritesResponsesFromServer() throws IOException, ApolloException {
mockWebServer.enqueue(mockResponse(FILE_EPISODE_HERO_NAME_WITH_ID));
EpisodeHeroName query = createHeroNameQuery();
final InterceptorResponse rewrittenResponse = prepareInterceptorResponse(query);
ApolloInterceptor interceptor = new ApolloInterceptor() {
@Nonnull @Override
public InterceptorResponse intercept(Operation operation, ApolloInterceptorChain chain) throws ApolloException {
chain.proceed();
return rewrittenResponse;
}
@Override
public void interceptAsync(@Nonnull Operation operation, @Nonnull ApolloInterceptorChain chain,
@Nonnull ExecutorService dispatcher, @Nonnull CallBack callBack) {
}
@Override public void dispose() {
}
};
client = createApolloClient(interceptor);
Response<EpisodeHeroName.Data> actualResponse = client.query(query).execute();
assertThat(rewrittenResponse.parsedResponse.get()).isEqualTo(actualResponse);
}
@Test
public void asyncApplicationInterceptorRewritesResponsesFromServer() throws IOException, TimeoutException, InterruptedException {
final NamedCountDownLatch responseLatch = new NamedCountDownLatch("responseLatch", 1);
mockWebServer.enqueue(mockResponse(FILE_EPISODE_HERO_NAME_WITH_ID));
EpisodeHeroName query = createHeroNameQuery();
final InterceptorResponse rewrittenResponse = prepareInterceptorResponse(query);
ApolloInterceptor interceptor = new ApolloInterceptor() {
@Nonnull @Override
public InterceptorResponse intercept(Operation operation, ApolloInterceptorChain chain) throws ApolloException {
return null;
}
@Override
public void interceptAsync(@Nonnull Operation operation, @Nonnull ApolloInterceptorChain chain,
@Nonnull ExecutorService dispatcher, @Nonnull final CallBack callBack) {
chain.proceedAsync(dispatcher, new CallBack() {
@Override public void onResponse(@Nonnull InterceptorResponse response) {
callBack.onResponse(rewrittenResponse);
}
@Override public void onFailure(@Nonnull ApolloException e) {
}
});
}
@Override public void dispose() {
}
};
client = createApolloClient(interceptor);
client.query(query).enqueue(new ApolloCall.Callback<EpisodeHeroName.Data>() {
@Override public void onResponse(@Nonnull Response<EpisodeHeroName.Data> response) {
assertThat(rewrittenResponse.parsedResponse.get()).isEqualTo(response);
responseLatch.countDown();
}
@Override public void onFailure(@Nonnull ApolloException e) {
}
});
responseLatch.awaitOrThrowWithTimeout(TIMEOUT_SECONDS, TimeUnit.SECONDS);
}
@Test
public void syncApplicationInterceptorThrowsApolloException() {
final String apolloException = "ApolloException";
EpisodeHeroName query = createHeroNameQuery();
ApolloInterceptor interceptor = new ApolloInterceptor() {
@Nonnull @Override
public InterceptorResponse intercept(Operation operation, ApolloInterceptorChain chain) throws ApolloException {
throw new ApolloException(apolloException);
}
@Override
public void interceptAsync(@Nonnull Operation operation, @Nonnull ApolloInterceptorChain chain,
@Nonnull ExecutorService dispatcher, @Nonnull CallBack callBack) {
}
@Override public void dispose() {
}
};
client = createApolloClient(interceptor);
try {
client.query(query).execute();
} catch (Exception e) {
assertThat(e.getMessage()).isEqualTo("ApolloException");
assertThat(e).isInstanceOf(ApolloException.class);
}
}
@Test
public void asyncApplicationInterceptorThrowsApolloException() throws TimeoutException, InterruptedException {
final NamedCountDownLatch responseLatch = new NamedCountDownLatch("responseLatch", 1);
final String message = "ApolloException";
EpisodeHeroName query = createHeroNameQuery();
ApolloInterceptor interceptor = new ApolloInterceptor() {
@Nonnull @Override
public InterceptorResponse intercept(Operation operation, ApolloInterceptorChain chain) throws ApolloException {
return null;
}
@Override
public void interceptAsync(@Nonnull Operation operation, @Nonnull ApolloInterceptorChain chain,
@Nonnull ExecutorService dispatcher, @Nonnull CallBack callBack) {
ApolloException apolloException = new ApolloParseException(message);
callBack.onFailure(apolloException);
}
@Override public void dispose() {
}
};
client = createApolloClient(interceptor);
client.query(query)
.enqueue(new ApolloCall.Callback<EpisodeHeroName.Data>() {
@Override public void onResponse(@Nonnull Response<EpisodeHeroName.Data> response) {
}
@Override public void onFailure(@Nonnull ApolloException e) {
assertThat(e.getMessage()).isEqualTo(message);
assertThat(e).isInstanceOf(ApolloParseException.class);
responseLatch.countDown();
}
});
responseLatch.awaitOrThrowWithTimeout(TIMEOUT_SECONDS, TimeUnit.SECONDS);
}
@Test
public void syncApplicationInterceptorThrowsRuntimeException() {
EpisodeHeroName query = createHeroNameQuery();
ApolloInterceptor interceptor = new ApolloInterceptor() {
@Nonnull @Override
public InterceptorResponse intercept(Operation operation, ApolloInterceptorChain chain) throws ApolloException {
throw new RuntimeException("RuntimeException");
}
@Override
public void interceptAsync(@Nonnull Operation operation, @Nonnull ApolloInterceptorChain chain,
@Nonnull ExecutorService dispatcher, @Nonnull CallBack callBack) {
}
@Override public void dispose() {
}
};
client = createApolloClient(interceptor);
try {
client.query(query).execute();
} catch (Exception e) {
assertThat(e.getMessage()).isEqualTo("RuntimeException");
assertThat(e).isInstanceOf(RuntimeException.class);
}
}
@Test
public void asyncApplicationInterceptorThrowsRuntimeException() throws TimeoutException, InterruptedException {
NamedCountDownLatch latch = new NamedCountDownLatch("latch", 1);
final String message = "RuntimeException";
EpisodeHeroName query = createHeroNameQuery();
ApolloInterceptor interceptor = new ApolloInterceptor() {
@Nonnull @Override
public InterceptorResponse intercept(Operation operation, ApolloInterceptorChain chain) throws ApolloException {
return null;
}
@Override
public void interceptAsync(@Nonnull Operation operation, @Nonnull ApolloInterceptorChain chain,
@Nonnull ExecutorService dispatcher, @Nonnull CallBack callBack) {
dispatcher.execute(new Runnable() {
@Override public void run() {
throw new RuntimeException(message);
}
});
}
@Override public void dispose() {
}
};
client = ApolloClient.builder()
.serverUrl(mockWebServer.url("/"))
.okHttpClient(okHttpClient)
.addApplicationInterceptor(interceptor)
.dispatcher(new ExceptionHandlingExecutor(message, RuntimeException.class, latch))
.build();
client
.query(query)
.enqueue(new ApolloCall.Callback<EpisodeHeroName.Data>() {
@Override public void onResponse(@Nonnull Response<EpisodeHeroName.Data> response) {
}
@Override public void onFailure(@Nonnull ApolloException e) {
}
});
latch.awaitOrThrowWithTimeout(TIMEOUT_SECONDS, TimeUnit.SECONDS);
}
@Test
public void syncApplicationInterceptorReturnsNull() {
EpisodeHeroName query = createHeroNameQuery();
ApolloInterceptor interceptor = new ApolloInterceptor() {
@Nonnull @Override
public InterceptorResponse intercept(Operation operation, ApolloInterceptorChain chain) throws ApolloException {
return null;
}
@Override
public void interceptAsync(@Nonnull Operation operation, @Nonnull ApolloInterceptorChain chain,
@Nonnull ExecutorService dispatcher, @Nonnull CallBack callBack) {
}
@Override public void dispose() {
}
};
client = createApolloClient(interceptor);
try {
client.query(query).execute();
Assert.fail();
} catch (Exception e) {
assertThat(e).isInstanceOf(NullPointerException.class);
}
}
@Test
public void asyncApplicationInterceptorReturnsNull() throws TimeoutException, InterruptedException {
NamedCountDownLatch latch = new NamedCountDownLatch("first", 1);
EpisodeHeroName query = createHeroNameQuery();
ApolloInterceptor interceptor = new ApolloInterceptor() {
@Nonnull @Override
public InterceptorResponse intercept(Operation operation, ApolloInterceptorChain chain) throws ApolloException {
return null;
}
@Override
public void interceptAsync(@Nonnull Operation operation, @Nonnull ApolloInterceptorChain chain,
@Nonnull ExecutorService dispatcher, @Nonnull final CallBack callBack) {
dispatcher.execute(new Runnable() {
@Override public void run() {
callBack.onResponse(null);
}
});
}
@Override public void dispose() {
}
};
client = ApolloClient.builder()
.serverUrl(mockWebServer.url("/"))
.okHttpClient(okHttpClient)
.addApplicationInterceptor(interceptor)
.dispatcher(new ExceptionHandlingExecutor(null, NullPointerException.class, latch))
.build();
client.query(query).enqueue(new ApolloCall.Callback<EpisodeHeroName.Data>() {
@Override public void onResponse(@Nonnull Response<EpisodeHeroName.Data> response) {
}
@Override public void onFailure(@Nonnull ApolloException e) {
}
});
latch.awaitOrThrowWithTimeout(TIMEOUT_SECONDS, TimeUnit.SECONDS);
}
@Test
public void applicationInterceptorCanMakeMultipleRequestsToServer() throws IOException, ApolloException {
mockWebServer.enqueue(mockResponse(FILE_EPISODE_HERO_NAME_WITH_ID));
mockWebServer.enqueue(mockResponse(FILE_EPISODE_HERO_NAME_CHANGE));
EpisodeHeroName query = createHeroNameQuery();
ApolloInterceptor interceptor = new ApolloInterceptor() {
@Nonnull @Override
public InterceptorResponse intercept(Operation operation, ApolloInterceptorChain chain) throws ApolloException {
chain.proceed();
return chain.proceed();
}
@Override
public void interceptAsync(@Nonnull Operation operation, @Nonnull ApolloInterceptorChain chain,
@Nonnull ExecutorService dispatcher, @Nonnull CallBack callBack) {
}
@Override public void dispose() {
}
};
client = createApolloClient(interceptor);
Response<EpisodeHeroName.Data> actualResponse = client.query(query).execute();
assertThat(actualResponse.data().hero().name()).isEqualTo("Artoo");
}
@Test
public void onShortCircuitingResponseSubsequentInterceptorsAreNotCalled() throws IOException, ApolloException {
mockWebServer.shutdown();
EpisodeHeroName query = createHeroNameQuery();
final InterceptorResponse expectedResponse = prepareInterceptorResponse(query);
ApolloInterceptor firstInterceptor = new ApolloInterceptor() {
@Nonnull @Override
public InterceptorResponse intercept(Operation operation, ApolloInterceptorChain chain) throws ApolloException {
return expectedResponse;
}
@Override
public void interceptAsync(@Nonnull Operation operation, @Nonnull ApolloInterceptorChain chain,
@Nonnull ExecutorService dispatcher, @Nonnull CallBack callBack) {
}
@Override public void dispose() {
}
};
ApolloInterceptor secondInterceptor = new ApolloInterceptor() {
@Nonnull @Override
public InterceptorResponse intercept(Operation operation, ApolloInterceptorChain chain) throws ApolloException {
Assert.fail("Second interceptor called, although response has been short circuited");
return chain.proceed();
}
@Override
public void interceptAsync(@Nonnull Operation operation, @Nonnull ApolloInterceptorChain chain,
@Nonnull ExecutorService dispatcher, @Nonnull CallBack callBack) {
}
@Override public void dispose() {
}
};
client = ApolloClient.builder()
.serverUrl(mockWebServer.url("/"))
.okHttpClient(okHttpClient)
.addApplicationInterceptor(firstInterceptor)
.addApplicationInterceptor(secondInterceptor)
.build();
Response<EpisodeHeroName.Data> actualResponse = client.query(query).execute();
assertThat(expectedResponse.parsedResponse.get()).isEqualTo(actualResponse);
}
@Test
public void onApolloCallCanceledAsyncApolloInterceptorIsDisposed() throws ApolloException, TimeoutException, InterruptedException {
final NamedCountDownLatch latch = new NamedCountDownLatch("latch", 1);
EpisodeHeroName query = createHeroNameQuery();
final InterceptorResponse fakeResponse = prepareInterceptorResponse(query);
ApolloInterceptor interceptor = new ApolloInterceptor() {
@Nonnull @Override
public InterceptorResponse intercept(Operation operation, ApolloInterceptorChain chain) throws ApolloException {
return null;
}
@Override
public void interceptAsync(@Nonnull Operation operation, @Nonnull ApolloInterceptorChain chain,
@Nonnull ExecutorService dispatcher, @Nonnull final CallBack callBack) {
dispatcher.execute(new Runnable() {
@Override public void run() {
callBack.onResponse(fakeResponse);
}
});
}
@Override public void dispose() {
latch.countDown();
}
};
client = createApolloClient(interceptor);
ApolloCall<EpisodeHeroName.Data> apolloCall = client.query(query);
apolloCall.enqueue(new ApolloCall.Callback<EpisodeHeroName.Data>() {
@Override public void onResponse(@Nonnull Response<EpisodeHeroName.Data> response) {
Assert.fail("Received a response, even though the request has been canceled");
}
@Override public void onFailure(@Nonnull ApolloException e) {
Assert.fail("Received an apolloException, even though the request has been canceled");
}
});
apolloCall.cancel();
//Latch's count should go down to zero in interceptor's dispose,
//else timeout is reached which means the test fails.
latch.awaitOrThrowWithTimeout(TIMEOUT_SECONDS, TimeUnit.SECONDS);
}
@NonNull private EpisodeHeroName createHeroNameQuery() {
return EpisodeHeroName
.builder()
.episode(Episode.EMPIRE)
.build();
}
private ApolloClient createApolloClient(ApolloInterceptor interceptor) {
return ApolloClient.builder()
.serverUrl(mockWebServer.url("/"))
.okHttpClient(okHttpClient)
.addApplicationInterceptor(interceptor)
.build();
}
@NonNull private InterceptorResponse prepareInterceptorResponse(EpisodeHeroName query) {
Request request = new Request.Builder()
.url(mockWebServer.url("/"))
.build();
okhttp3.Response okHttpResponse = new okhttp3.Response.Builder()
.request(request)
.protocol(Protocol.HTTP_2)
.code(200)
.message("Intercepted")
.body(ResponseBody.create(MediaType.parse("text/plain; charset=utf-8"), "fakeResponse"))
.build();
Response<EpisodeHeroName.Data> apolloResponse = new Response<>(query);
return new InterceptorResponse(okHttpResponse,
apolloResponse, Collections.<Record>emptyList());
}
private MockResponse mockResponse(String fileName) throws IOException {
return new MockResponse().setChunkedBody(Utils.readFileToString(getClass(), "/" + fileName), 32);
}
private class ExceptionHandlingExecutor extends ThreadPoolExecutor {
private String message;
private Class<?> exceptionClass;
private NamedCountDownLatch latch;
private ExceptionHandlingExecutor(String message, Class<?> exceptionClass, NamedCountDownLatch latch) {
super(1, 1, 0, TimeUnit.SECONDS, new SynchronousQueue<Runnable>());
this.message = message;
this.exceptionClass = exceptionClass;
this.latch = latch;
}
@Override public void execute(final Runnable command) {
super.execute(new Runnable() {
@Override public void run() {
try {
command.run();
} catch (Exception e) {
assertThat(e.getMessage()).isEqualTo(message);
assertThat(e).isInstanceOf(exceptionClass);
latch.countDown();
}
}
});
}
}
}