package com.apollographql.apollo;
import com.apollographql.android.impl.httpcache.AllFilms;
import com.apollographql.android.impl.httpcache.AllPlanets;
import com.apollographql.android.impl.httpcache.DroidDetails;
import com.apollographql.android.impl.httpcache.type.CustomType;
import com.apollographql.apollo.api.Response;
import com.apollographql.apollo.cache.http.DiskLruHttpCacheStore;
import com.apollographql.apollo.cache.http.HttpCachePolicy;
import com.apollographql.apollo.exception.ApolloException;
import com.apollographql.apollo.exception.ApolloHttpException;
import com.apollographql.apollo.internal.cache.http.HttpCache;
import com.apollographql.apollo.internal.interceptor.ApolloServerInterceptor;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import java.io.File;
import java.io.IOException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.concurrent.TimeUnit;
import okhttp3.Interceptor;
import okhttp3.OkHttpClient;
import okhttp3.internal.io.FileSystem;
import okhttp3.internal.io.InMemoryFileSystem;
import okhttp3.mockwebserver.MockResponse;
import okhttp3.mockwebserver.MockWebServer;
import okio.Buffer;
import static com.google.common.truth.Truth.assertThat;
import static junit.framework.Assert.fail;
public class HttpCacheTest {
private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd", Locale.US);
private ApolloClient apolloClient;
private okhttp3.Request lastHttRequest;
private okhttp3.Response lastHttResponse;
private MockHttpCacheStore cacheStore;
private OkHttpClient okHttpClient;
@Rule public final MockWebServer server = new MockWebServer();
@Rule public InMemoryFileSystem inMemoryFileSystem = new InMemoryFileSystem();
@Before public void setUp() {
CustomTypeAdapter<Date> dateCustomTypeAdapter = new CustomTypeAdapter<Date>() {
@Override public Date decode(String value) {
try {
return DATE_FORMAT.parse(value);
} catch (ParseException e) {
throw new RuntimeException(e);
}
}
@Override public String encode(Date value) {
return DATE_FORMAT.format(value);
}
};
cacheStore = new MockHttpCacheStore();
cacheStore.delegate = new DiskLruHttpCacheStore(inMemoryFileSystem, new File("/cache/"), Integer.MAX_VALUE);
okHttpClient = new OkHttpClient.Builder()
.addInterceptor(new Interceptor() {
@Override public okhttp3.Response intercept(Chain chain) throws IOException {
lastHttRequest = chain.request();
lastHttResponse = chain.proceed(lastHttRequest);
return lastHttResponse;
}
})
.build();
apolloClient = ApolloClient.builder()
.serverUrl(server.url("/"))
.okHttpClient(okHttpClient)
.addCustomTypeAdapter(CustomType.DATE, dateCustomTypeAdapter)
.httpCacheStore(cacheStore)
.build();
}
@After public void tearDown() {
try {
apolloClient.clearHttpCache();
server.shutdown();
} catch (Exception ignore) {
}
}
@Test public void prematureDisconnect() throws Exception {
MockResponse mockResponse = mockResponse("/HttpCacheTestAllPlanets.json");
Buffer truncatedBody = new Buffer();
truncatedBody.write(mockResponse.getBody(), 16);
mockResponse.setBody(truncatedBody);
server.enqueue(mockResponse);
try {
Response<AllPlanets.Data> body = apolloClient.query(new AllPlanets())
.httpCachePolicy(HttpCachePolicy.NETWORK_ONLY).execute();
assertThat(body.hasErrors()).isFalse();
fail("expected ApolloException");
} catch (ApolloException expected) {
}
checkNoCachedResponse();
}
@Test public void cacheDefault() throws Exception {
enqueueResponse("/HttpCacheTestAllPlanets.json");
assertThat(apolloClient.query(new AllPlanets()).execute().hasErrors()).isFalse();
checkCachedResponse("/HttpCacheTestAllPlanets.json");
}
@Test public void cacheSeveralResponses() throws Exception {
enqueueResponse("/HttpCacheTestAllPlanets.json");
assertThat(apolloClient.query(new AllPlanets()).execute().hasErrors()).isFalse();
checkCachedResponse("/HttpCacheTestAllPlanets.json");
enqueueResponse("/HttpCacheTestDroidDetails.json");
assertThat(apolloClient.query(new DroidDetails()).execute().hasErrors()).isFalse();
checkCachedResponse("/HttpCacheTestDroidDetails.json");
enqueueResponse("/HttpCacheTestAllFilms.json");
assertThat(apolloClient.query(new AllFilms()).execute().hasErrors()).isFalse();
checkCachedResponse("/HttpCacheTestAllFilms.json");
}
@Test public void noCacheStore() throws Exception {
enqueueResponse("/HttpCacheTestAllPlanets.json");
ApolloClient apolloClient = ApolloClient.builder()
.serverUrl(server.url("/"))
.okHttpClient(okHttpClient)
.build();
assertThat(apolloClient.query(new AllPlanets()).execute().hasErrors()).isFalse();
checkNoCachedResponse();
}
@Test public void networkOnly() throws Exception {
enqueueResponse("/HttpCacheTestAllPlanets.json");
assertThat(apolloClient.query(new AllPlanets()).httpCachePolicy(HttpCachePolicy.NETWORK_ONLY)
.execute().hasErrors()).isFalse();
assertThat(server.getRequestCount()).isEqualTo(1);
assertThat(lastHttResponse.networkResponse()).isNotNull();
assertThat(lastHttResponse.cacheResponse()).isNull();
checkCachedResponse("/HttpCacheTestAllPlanets.json");
}
@Test public void networkOnly_responseWithGraphError_noCached() throws Exception {
enqueueResponse("/ResponseError.json");
assertThat(apolloClient.query(new AllPlanets()).httpCachePolicy(HttpCachePolicy.NETWORK_ONLY)
.execute().hasErrors()).isTrue();
assertThat(server.getRequestCount()).isEqualTo(1);
assertThat(lastHttResponse.networkResponse()).isNotNull();
assertThat(lastHttResponse.cacheResponse()).isNull();
checkNoCachedResponse();
}
@Test public void cacheOnlyHit() throws Exception {
enqueueResponse("/HttpCacheTestAllPlanets.json");
apolloClient.query(new AllPlanets()).execute();
assertThat(server.takeRequest()).isNotNull();
enqueueResponse("/HttpCacheTestAllPlanets.json");
apolloClient
.query(new AllPlanets())
.httpCachePolicy(HttpCachePolicy.CACHE_ONLY.expireAfter(2, TimeUnit.SECONDS))
.execute();
assertThat(server.getRequestCount()).isEqualTo(1);
assertThat(lastHttResponse.networkResponse()).isNull();
assertThat(lastHttResponse.cacheResponse()).isNotNull();
checkCachedResponse("/HttpCacheTestAllPlanets.json");
}
@Test(expected = ApolloHttpException.class) public void cacheOnlyMiss() throws Exception {
enqueueResponse("/HttpCacheTestAllPlanets.json");
apolloClient
.query(new AllPlanets())
.httpCachePolicy(HttpCachePolicy.CACHE_ONLY.expireAfter(2, TimeUnit.SECONDS))
.execute();
}
@Test public void cacheNonStale() throws Exception {
enqueueResponse("/HttpCacheTestAllPlanets.json");
apolloClient.query(new AllPlanets()).execute();
assertThat(server.takeRequest()).isNotNull();
checkCachedResponse("/HttpCacheTestAllPlanets.json");
enqueueResponse("/HttpCacheTestAllPlanets.json");
apolloClient
.query(new AllPlanets())
.httpCachePolicy(HttpCachePolicy.CACHE_FIRST.expireAfter(2, TimeUnit.SECONDS))
.execute();
assertThat(server.getRequestCount()).isEqualTo(1);
assertThat(lastHttResponse.networkResponse()).isNull();
assertThat(lastHttResponse.cacheResponse()).isNotNull();
}
@Test public void cacheStale() throws Exception {
enqueueResponse("/HttpCacheTestAllPlanets.json");
apolloClient.query(new AllPlanets()).execute();
assertThat(server.getRequestCount()).isEqualTo(1);
Thread.sleep(TimeUnit.SECONDS.toMillis(3));
enqueueResponse("/HttpCacheTestAllPlanets.json");
apolloClient.query(new AllPlanets()).execute();
assertThat(server.getRequestCount()).isEqualTo(2);
assertThat(lastHttResponse.networkResponse()).isNotNull();
assertThat(lastHttResponse.cacheResponse()).isNull();
checkCachedResponse("/HttpCacheTestAllPlanets.json");
}
@Test public void cacheStaleBeforeNetwork() throws Exception {
enqueueResponse("/HttpCacheTestAllPlanets.json");
apolloClient.query(new AllPlanets()).execute();
assertThat(server.getRequestCount()).isEqualTo(1);
Thread.sleep(TimeUnit.SECONDS.toMillis(3));
enqueueResponse("/HttpCacheTestAllPlanets.json");
apolloClient
.query(new AllPlanets())
.httpCachePolicy(HttpCachePolicy.NETWORK_FIRST)
.execute();
assertThat(server.getRequestCount()).isEqualTo(2);
assertThat(lastHttResponse.networkResponse()).isNotNull();
assertThat(lastHttResponse.cacheResponse()).isNull();
checkCachedResponse("/HttpCacheTestAllPlanets.json");
}
@Test public void cacheStaleBeforeNetworkError() throws Exception {
enqueueResponse("/HttpCacheTestAllPlanets.json");
apolloClient.query(new AllPlanets()).execute();
assertThat(server.getRequestCount()).isEqualTo(1);
Thread.sleep(TimeUnit.SECONDS.toMillis(3));
server.enqueue(new MockResponse().setResponseCode(504).setBody(""));
apolloClient
.query(new AllPlanets())
.httpCachePolicy(HttpCachePolicy.NETWORK_FIRST)
.execute();
assertThat(server.getRequestCount()).isEqualTo(2);
assertThat(lastHttResponse.networkResponse()).isNotNull();
assertThat(lastHttResponse.cacheResponse()).isNotNull();
checkCachedResponse("/HttpCacheTestAllPlanets.json");
}
@Test public void cacheUpdate() throws Exception {
enqueueResponse("/HttpCacheTestAllPlanets.json");
apolloClient.query(new AllPlanets()).execute();
assertThat(server.getRequestCount()).isEqualTo(1);
checkCachedResponse("/HttpCacheTestAllPlanets.json");
Thread.sleep(TimeUnit.SECONDS.toMillis(3));
enqueueResponse("/HttpCacheTestAllPlanets2.json");
apolloClient.query(new AllPlanets()).execute();
assertThat(server.getRequestCount()).isEqualTo(2);
checkCachedResponse("/HttpCacheTestAllPlanets2.json");
assertThat(lastHttResponse.networkResponse()).isNotNull();
assertThat(lastHttResponse.cacheResponse()).isNull();
enqueueResponse("/HttpCacheTestAllPlanets2.json");
apolloClient
.query(new AllPlanets())
.httpCachePolicy(HttpCachePolicy.CACHE_FIRST.expireAfter(2, TimeUnit.SECONDS))
.execute();
assertThat(server.getRequestCount()).isEqualTo(2);
assertThat(lastHttResponse.networkResponse()).isNull();
assertThat(lastHttResponse.cacheResponse()).isNotNull();
checkCachedResponse("/HttpCacheTestAllPlanets2.json");
}
@Test public void fileSystemUnavailable() throws IOException, ApolloException {
cacheStore.delegate = new DiskLruHttpCacheStore(new NoFileSystem(), new File("/cache/"), Integer.MAX_VALUE);
enqueueResponse("/HttpCacheTestAllPlanets.json");
assertThat(apolloClient.query(new AllPlanets()).execute().hasErrors()).isFalse();
checkNoCachedResponse();
}
@Test public void fileSystemWriteFailure() throws IOException, ApolloException {
FaultyHttpCacheStore faultyCacheStore = new FaultyHttpCacheStore(FileSystem.SYSTEM);
cacheStore.delegate = faultyCacheStore;
enqueueResponse("/HttpCacheTestAllPlanets.json");
faultyCacheStore.failStrategy(FaultyHttpCacheStore.FailStrategy.FAIL_HEADER_WRITE);
assertThat(apolloClient.query(new AllPlanets()).execute().hasErrors()).isFalse();
checkNoCachedResponse();
enqueueResponse("/HttpCacheTestAllPlanets.json");
faultyCacheStore.failStrategy(FaultyHttpCacheStore.FailStrategy.FAIL_BODY_WRITE);
assertThat(apolloClient.query(new AllPlanets()).execute().hasErrors()).isFalse();
checkNoCachedResponse();
}
@Test public void fileSystemReadFailure() throws IOException, ApolloException {
FaultyHttpCacheStore faultyCacheStore = new FaultyHttpCacheStore(inMemoryFileSystem);
cacheStore.delegate = faultyCacheStore;
enqueueResponse("/HttpCacheTestAllPlanets.json");
assertThat(apolloClient.query(new AllPlanets()).execute().hasErrors()).isFalse();
checkCachedResponse("/HttpCacheTestAllPlanets.json");
enqueueResponse("/HttpCacheTestAllPlanets.json");
faultyCacheStore.failStrategy(FaultyHttpCacheStore.FailStrategy.FAIL_HEADER_READ);
assertThat(apolloClient.query(new AllPlanets()).httpCachePolicy(HttpCachePolicy.CACHE_FIRST.expireAfter(2, TimeUnit.SECONDS))
.execute().hasErrors()).isFalse();
assertThat(server.getRequestCount()).isEqualTo(2);
enqueueResponse("/HttpCacheTestAllPlanets.json");
faultyCacheStore.failStrategy(FaultyHttpCacheStore.FailStrategy.FAIL_BODY_READ);
try {
apolloClient
.query(new AllPlanets())
.httpCachePolicy(HttpCachePolicy.CACHE_FIRST.expireAfter(2, TimeUnit.SECONDS))
.execute();
fail("expected exception");
} catch (Exception expected) {
}
assertThat(server.getRequestCount()).isEqualTo(2);
}
@Test public void expireAfterRead() throws IOException, ApolloException {
enqueueResponse("/HttpCacheTestAllPlanets.json");
assertThat(apolloClient.query(new AllPlanets()).execute().hasErrors()).isFalse();
checkCachedResponse("/HttpCacheTestAllPlanets.json");
assertThat(apolloClient
.query(new AllPlanets())
.httpCachePolicy(HttpCachePolicy.CACHE_ONLY.expireAfter(2, TimeUnit.SECONDS).expireAfterRead())
.execute().hasErrors()).isFalse();
checkNoCachedResponse();
try {
apolloClient
.query(new AllPlanets())
.httpCachePolicy(HttpCachePolicy.CACHE_ONLY.expireAfter(2, TimeUnit.SECONDS))
.execute();
fail("exception expected");
} catch (Exception expected) {
}
enqueueResponse("/HttpCacheTestAllPlanets.json");
assertThat(apolloClient.query(new AllPlanets()).execute().hasErrors()).isFalse();
checkCachedResponse("/HttpCacheTestAllPlanets.json");
}
@Test public void cacheNetworkError() throws IOException, ApolloException {
server.enqueue(new MockResponse().setResponseCode(504).setBody(""));
try {
apolloClient.query(new AllPlanets()).execute();
fail("exception expected");
} catch (Exception expected) {
}
checkNoCachedResponse();
enqueueResponse("/HttpCacheTestAllPlanets.json");
assertThat(apolloClient.query(new AllPlanets()).execute().hasErrors()).isFalse();
checkCachedResponse("/HttpCacheTestAllPlanets.json");
assertThat(apolloClient
.query(new AllPlanets())
.httpCachePolicy(HttpCachePolicy.CACHE_ONLY.expireAfter(2, TimeUnit.SECONDS))
.execute().hasErrors()).isFalse();
}
@Test public void networkFirst() throws Exception {
enqueueResponse("/HttpCacheTestAllPlanets.json");
assertThat(apolloClient.query(new AllPlanets()).execute().hasErrors()).isFalse();
assertThat(server.getRequestCount()).isEqualTo(1);
assertThat(lastHttResponse.networkResponse()).isNotNull();
assertThat(lastHttResponse.cacheResponse()).isNull();
checkCachedResponse("/HttpCacheTestAllPlanets.json");
enqueueResponse("/HttpCacheTestAllPlanets.json");
assertThat(apolloClient
.query(new AllPlanets())
.httpCachePolicy(HttpCachePolicy.NETWORK_FIRST.expireAfter(2, TimeUnit.SECONDS))
.execute().hasErrors()).isFalse();
assertThat(server.getRequestCount()).isEqualTo(2);
assertThat(lastHttResponse.networkResponse()).isNotNull();
assertThat(lastHttResponse.cacheResponse()).isNull();
checkCachedResponse("/HttpCacheTestAllPlanets.json");
}
private void enqueueResponse(String fileName) throws IOException {
server.enqueue(mockResponse(fileName));
}
private void checkCachedResponse(String fileName) throws IOException {
String cacheKey = ApolloServerInterceptor.cacheKey(lastHttRequest.body());
okhttp3.Response response = apolloClient.cachedHttpResponse(cacheKey);
assertThat(response).isNotNull();
assertThat(response.body().source().readUtf8()).isEqualTo(Utils.readFileToString(getClass(), fileName));
response.body().source().close();
}
private void checkNoCachedResponse() throws IOException {
String cacheKey = lastHttRequest.header(HttpCache.CACHE_KEY_HEADER);
okhttp3.Response cachedResponse = apolloClient.cachedHttpResponse(cacheKey);
assertThat(cachedResponse).isNull();
}
private MockResponse mockResponse(String fileName) throws IOException {
return new MockResponse().setChunkedBody(Utils.readFileToString(getClass(), fileName), 32);
}
}