package com.bumptech.glide.load.data;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Matchers.isA;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import com.bumptech.glide.Priority;
import com.bumptech.glide.load.model.GlideUrl;
import com.bumptech.glide.load.model.Headers;
import com.bumptech.glide.testutil.TestUtil;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import okhttp3.mockwebserver.MockResponse;
import okhttp3.mockwebserver.MockWebServer;
import okhttp3.mockwebserver.RecordedRequest;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
/**
* Tests {@link com.bumptech.glide.load.data.HttpUrlFetcher} against server responses. Tests for
* behavior (connection/disconnection/options) should go in
* {@link com.bumptech.glide.load.data.HttpUrlFetcherTest}, response handling should go here.
*/
@RunWith(RobolectricTestRunner.class)
@Config(manifest = Config.NONE, sdk = 18)
public class HttpUrlFetcherServerTest {
private static final String DEFAULT_PATH = "/fakepath";
private static final int TIMEOUT_TIME_MS = 300;
@Mock DataFetcher.DataCallback<InputStream> callback;
private MockWebServer mockWebServer;
private boolean defaultFollowRedirects;
private ArgumentCaptor<InputStream> streamCaptor;
@Before
public void setUp() throws IOException {
MockitoAnnotations.initMocks(this);
defaultFollowRedirects = HttpURLConnection.getFollowRedirects();
HttpURLConnection.setFollowRedirects(false);
mockWebServer = new MockWebServer();
mockWebServer.start();
streamCaptor = ArgumentCaptor.forClass(InputStream.class);
}
@After
public void tearDown() throws IOException {
HttpURLConnection.setFollowRedirects(defaultFollowRedirects);
mockWebServer.shutdown();
}
@Test
public void testReturnsInputStreamOnStatusOk() throws Exception {
String expected = "fakedata";
mockWebServer.enqueue(new MockResponse().setBody(expected).setResponseCode(200));
HttpUrlFetcher fetcher = getFetcher();
fetcher.loadData(Priority.HIGH, callback);
verify(callback).onDataReady(streamCaptor.capture());
TestUtil.assertStreamOf(expected, streamCaptor.getValue());
}
@Test
public void testHandlesRedirect301s() throws Exception {
String expected = "fakedata";
mockWebServer.enqueue(new MockResponse().setResponseCode(301)
.setHeader("Location", mockWebServer.url("/redirect").toString()));
mockWebServer.enqueue(new MockResponse().setResponseCode(200).setBody(expected));
getFetcher().loadData(Priority.LOW, callback);
verify(callback).onDataReady(streamCaptor.capture());
TestUtil.assertStreamOf(expected, streamCaptor.getValue());
}
@Test
public void testHandlesRedirect302s() throws Exception {
String expected = "fakedata";
mockWebServer.enqueue(new MockResponse().setResponseCode(302)
.setHeader("Location", mockWebServer.url("/redirect").toString()));
mockWebServer.enqueue(new MockResponse().setResponseCode(200).setBody(expected));
getFetcher().loadData(Priority.LOW, callback);
verify(callback).onDataReady(streamCaptor.capture());
TestUtil.assertStreamOf(expected, streamCaptor.getValue());
}
@Test
public void testHandlesRelativeRedirects() throws Exception {
String expected = "fakedata";
mockWebServer
.enqueue(new MockResponse().setResponseCode(301).setHeader("Location", "/redirect"));
mockWebServer.enqueue(new MockResponse().setResponseCode(200).setBody(expected));
getFetcher().loadData(Priority.NORMAL, callback);
verify(callback).onDataReady(streamCaptor.capture());
TestUtil.assertStreamOf(expected, streamCaptor.getValue());
mockWebServer.takeRequest();
RecordedRequest second = mockWebServer.takeRequest();
assertThat(second.getPath()).endsWith("/redirect");
}
@Test
public void testHandlesUpToFiveRedirects() throws Exception {
int numRedirects = 4;
String expected = "redirectedData";
String redirectBase = "/redirect";
for (int i = 0; i < numRedirects; i++) {
mockWebServer.enqueue(new MockResponse().setResponseCode(301)
.setHeader("Location", mockWebServer.url(redirectBase + i).toString()));
}
mockWebServer.enqueue(new MockResponse().setResponseCode(200).setBody(expected));
getFetcher().loadData(Priority.NORMAL, callback);
verify(callback).onDataReady(streamCaptor.capture());
TestUtil.assertStreamOf(expected, streamCaptor.getValue());
assertThat(mockWebServer.takeRequest().getPath()).contains(DEFAULT_PATH);
for (int i = 0; i < numRedirects; i++) {
assertThat(mockWebServer.takeRequest().getPath()).contains(redirectBase + i);
}
}
@Test
public void testFailsOnRedirectLoops() throws Exception {
mockWebServer.enqueue(new MockResponse().setResponseCode(301)
.setHeader("Location", mockWebServer.url("/redirect").toString()));
mockWebServer.enqueue(new MockResponse().setResponseCode(301)
.setHeader("Location", mockWebServer.url("/redirect").toString()));
getFetcher().loadData(Priority.IMMEDIATE, callback);
verify(callback).onLoadFailed(isA(IOException.class));
}
@Test
public void testFailsIfRedirectLocationIsNotPresent() throws Exception {
mockWebServer.enqueue(new MockResponse().setResponseCode(301));
getFetcher().loadData(Priority.NORMAL, callback);
verify(callback).onLoadFailed(isA(IOException.class));
}
@Test
public void testFailsIfRedirectLocationIsPresentAndEmpty() throws Exception {
mockWebServer.enqueue(new MockResponse().setResponseCode(301).setHeader("Location", ""));
getFetcher().loadData(Priority.NORMAL, callback);
verify(callback).onLoadFailed(isA(IOException.class));
}
@Test
public void testFailsIfStatusCodeIsNegativeOne() throws Exception {
mockWebServer.enqueue(new MockResponse().setResponseCode(-1));
getFetcher().loadData(Priority.LOW, callback);
verify(callback).onLoadFailed(isA(IOException.class));
}
@Test
public void testFailsAfterTooManyRedirects() throws Exception {
for (int i = 0; i < 10; i++) {
mockWebServer.enqueue(new MockResponse().setResponseCode(301)
.setHeader("Location", mockWebServer.url("/redirect" + i).toString()));
}
getFetcher().loadData(Priority.NORMAL, callback);
verify(callback).onLoadFailed(isA(IOException.class));
}
@Test
public void testFailsIfStatusCodeIs500() throws Exception {
mockWebServer.enqueue(new MockResponse().setResponseCode(500));
getFetcher().loadData(Priority.NORMAL, callback);
verify(callback).onLoadFailed(isA(IOException.class));
}
@Test
public void testFailsIfStatusCodeIs400() throws Exception {
mockWebServer.enqueue(new MockResponse().setResponseCode(400));
getFetcher().loadData(Priority.LOW, callback);
verify(callback).onLoadFailed(isA(IOException.class));
}
@Test
public void testSetsReadTimeout() throws Exception {
MockWebServer tempWebServer = new MockWebServer();
tempWebServer.enqueue(
new MockResponse().setBody("test").throttleBody(1, TIMEOUT_TIME_MS, TimeUnit.MILLISECONDS));
tempWebServer.start();
try {
getFetcher().loadData(Priority.HIGH, callback);
} finally {
tempWebServer.shutdown();
// shutdown() called before any enqueue() blocks until it times out.
mockWebServer.enqueue(new MockResponse().setResponseCode(200));
}
verify(callback).onLoadFailed(isA(IOException.class));
}
@Test
public void testAppliesHeadersInGlideUrl() throws Exception {
mockWebServer.enqueue(new MockResponse().setResponseCode(200));
String headerField = "field";
String headerValue = "value";
Map<String, String> headersMap = new HashMap<>();
headersMap.put(headerField, headerValue);
Headers headers = mock(Headers.class);
when(headers.getHeaders()).thenReturn(headersMap);
getFetcher(headers).loadData(Priority.HIGH, callback);
assertThat(mockWebServer.takeRequest().getHeader(headerField)).isEqualTo(headerValue);
}
private HttpUrlFetcher getFetcher() {
return getFetcher(Headers.DEFAULT);
}
private HttpUrlFetcher getFetcher(Headers headers) {
URL url = mockWebServer.url(DEFAULT_PATH).url();
return new HttpUrlFetcher(new GlideUrl(url, headers), TIMEOUT_TIME_MS,
HttpUrlFetcher.DEFAULT_CONNECTION_FACTORY);
}
}