blob: 3630379b0b8158d7c49440bf4babe2ff2caa5f91 [file] [log] [blame]
/*
* Copyright (C) 2011 The Android Open Source Project
*
* 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 com.android.volley.toolbox;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
import static org.mockito.MockitoAnnotations.initMocks;
import com.android.volley.AuthFailureError;
import com.android.volley.Cache.Entry;
import com.android.volley.Header;
import com.android.volley.NetworkResponse;
import com.android.volley.NoConnectionError;
import com.android.volley.Request;
import com.android.volley.Response;
import com.android.volley.RetryPolicy;
import com.android.volley.ServerError;
import com.android.volley.TimeoutError;
import com.android.volley.VolleyError;
import com.android.volley.mock.MockHttpStack;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.SocketTimeoutException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.robolectric.RobolectricTestRunner;
@RunWith(RobolectricTestRunner.class)
public class BasicNetworkTest {
@Mock private Request<String> mMockRequest;
@Mock private RetryPolicy mMockRetryPolicy;
@Before
public void setUp() throws Exception {
initMocks(this);
}
@Test
public void headersAndPostParams() throws Exception {
MockHttpStack mockHttpStack = new MockHttpStack();
InputStream responseStream =
new ByteArrayInputStream("foobar".getBytes(StandardCharsets.UTF_8));
HttpResponse fakeResponse =
new HttpResponse(200, Collections.<Header>emptyList(), 6, responseStream);
mockHttpStack.setResponseToReturn(fakeResponse);
BasicNetwork httpNetwork = new BasicNetwork(mockHttpStack);
Request<String> request = buildRequest();
Entry entry = new Entry();
entry.etag = "foobar";
entry.lastModified = 1503102002000L;
request.setCacheEntry(entry);
httpNetwork.performRequest(request);
assertEquals("foo", mockHttpStack.getLastHeaders().get("requestheader"));
assertEquals("foobar", mockHttpStack.getLastHeaders().get("If-None-Match"));
assertEquals(
"Sat, 19 Aug 2017 00:20:02 GMT",
mockHttpStack.getLastHeaders().get("If-Modified-Since"));
assertEquals(
"requestpost=foo&",
new String(mockHttpStack.getLastPostBody(), StandardCharsets.UTF_8));
}
@Test
public void notModified() throws Exception {
MockHttpStack mockHttpStack = new MockHttpStack();
List<Header> headers = new ArrayList<>();
headers.add(new Header("ServerKeyA", "ServerValueA"));
headers.add(new Header("ServerKeyB", "ServerValueB"));
headers.add(new Header("SharedKey", "ServerValueShared"));
headers.add(new Header("sharedcaseinsensitivekey", "ServerValueShared1"));
headers.add(new Header("SharedCaseInsensitiveKey", "ServerValueShared2"));
HttpResponse fakeResponse = new HttpResponse(HttpURLConnection.HTTP_NOT_MODIFIED, headers);
mockHttpStack.setResponseToReturn(fakeResponse);
BasicNetwork httpNetwork = new BasicNetwork(mockHttpStack);
Request<String> request = buildRequest();
Entry entry = new Entry();
entry.allResponseHeaders = new ArrayList<>();
entry.allResponseHeaders.add(new Header("CachedKeyA", "CachedValueA"));
entry.allResponseHeaders.add(new Header("CachedKeyB", "CachedValueB"));
entry.allResponseHeaders.add(new Header("SharedKey", "CachedValueShared"));
entry.allResponseHeaders.add(new Header("SHAREDCASEINSENSITIVEKEY", "CachedValueShared1"));
entry.allResponseHeaders.add(new Header("shAREDcaSEinSENSITIVEkeY", "CachedValueShared2"));
request.setCacheEntry(entry);
NetworkResponse response = httpNetwork.performRequest(request);
List<Header> expectedHeaders = new ArrayList<>();
// Should have all server headers + cache headers that didn't show up in server response.
expectedHeaders.add(new Header("ServerKeyA", "ServerValueA"));
expectedHeaders.add(new Header("ServerKeyB", "ServerValueB"));
expectedHeaders.add(new Header("SharedKey", "ServerValueShared"));
expectedHeaders.add(new Header("sharedcaseinsensitivekey", "ServerValueShared1"));
expectedHeaders.add(new Header("SharedCaseInsensitiveKey", "ServerValueShared2"));
expectedHeaders.add(new Header("CachedKeyA", "CachedValueA"));
expectedHeaders.add(new Header("CachedKeyB", "CachedValueB"));
assertThat(expectedHeaders, containsInAnyOrder(response.allHeaders.toArray(new Header[0])));
}
@Test
public void notModified_legacyCache() throws Exception {
MockHttpStack mockHttpStack = new MockHttpStack();
List<Header> headers = new ArrayList<>();
headers.add(new Header("ServerKeyA", "ServerValueA"));
headers.add(new Header("ServerKeyB", "ServerValueB"));
headers.add(new Header("SharedKey", "ServerValueShared"));
headers.add(new Header("sharedcaseinsensitivekey", "ServerValueShared1"));
headers.add(new Header("SharedCaseInsensitiveKey", "ServerValueShared2"));
HttpResponse fakeResponse = new HttpResponse(HttpURLConnection.HTTP_NOT_MODIFIED, headers);
mockHttpStack.setResponseToReturn(fakeResponse);
BasicNetwork httpNetwork = new BasicNetwork(mockHttpStack);
Request<String> request = buildRequest();
Entry entry = new Entry();
entry.responseHeaders = new HashMap<>();
entry.responseHeaders.put("CachedKeyA", "CachedValueA");
entry.responseHeaders.put("CachedKeyB", "CachedValueB");
entry.responseHeaders.put("SharedKey", "CachedValueShared");
entry.responseHeaders.put("SHAREDCASEINSENSITIVEKEY", "CachedValueShared1");
entry.responseHeaders.put("shAREDcaSEinSENSITIVEkeY", "CachedValueShared2");
request.setCacheEntry(entry);
NetworkResponse response = httpNetwork.performRequest(request);
List<Header> expectedHeaders = new ArrayList<>();
// Should have all server headers + cache headers that didn't show up in server response.
expectedHeaders.add(new Header("ServerKeyA", "ServerValueA"));
expectedHeaders.add(new Header("ServerKeyB", "ServerValueB"));
expectedHeaders.add(new Header("SharedKey", "ServerValueShared"));
expectedHeaders.add(new Header("sharedcaseinsensitivekey", "ServerValueShared1"));
expectedHeaders.add(new Header("SharedCaseInsensitiveKey", "ServerValueShared2"));
expectedHeaders.add(new Header("CachedKeyA", "CachedValueA"));
expectedHeaders.add(new Header("CachedKeyB", "CachedValueB"));
assertThat(expectedHeaders, containsInAnyOrder(response.allHeaders.toArray(new Header[0])));
}
@Test
public void socketTimeout() throws Exception {
MockHttpStack mockHttpStack = new MockHttpStack();
mockHttpStack.setExceptionToThrow(new SocketTimeoutException());
BasicNetwork httpNetwork = new BasicNetwork(mockHttpStack);
Request<String> request = buildRequest();
request.setRetryPolicy(mMockRetryPolicy);
doThrow(new VolleyError()).when(mMockRetryPolicy).retry(any(VolleyError.class));
try {
httpNetwork.performRequest(request);
} catch (VolleyError e) {
// expected
}
// should retry socket timeouts
verify(mMockRetryPolicy).retry(any(TimeoutError.class));
}
@Test
public void noConnectionDefault() throws Exception {
MockHttpStack mockHttpStack = new MockHttpStack();
mockHttpStack.setExceptionToThrow(new IOException());
BasicNetwork httpNetwork = new BasicNetwork(mockHttpStack);
Request<String> request = buildRequest();
request.setRetryPolicy(mMockRetryPolicy);
doThrow(new VolleyError()).when(mMockRetryPolicy).retry(any(VolleyError.class));
try {
httpNetwork.performRequest(request);
} catch (VolleyError e) {
// expected
}
// should not retry when there is no connection
verify(mMockRetryPolicy, never()).retry(any(VolleyError.class));
}
@Test
public void noConnectionRetry() throws Exception {
MockHttpStack mockHttpStack = new MockHttpStack();
mockHttpStack.setExceptionToThrow(new IOException());
BasicNetwork httpNetwork = new BasicNetwork(mockHttpStack);
Request<String> request = buildRequest();
request.setRetryPolicy(mMockRetryPolicy);
request.setShouldRetryConnectionErrors(true);
doThrow(new VolleyError()).when(mMockRetryPolicy).retry(any(VolleyError.class));
try {
httpNetwork.performRequest(request);
} catch (VolleyError e) {
// expected
}
// should retry when there is no connection
verify(mMockRetryPolicy).retry(any(NoConnectionError.class));
reset(mMockRetryPolicy);
}
@Test
public void noConnectionNoRetry() throws Exception {
MockHttpStack mockHttpStack = new MockHttpStack();
mockHttpStack.setExceptionToThrow(new IOException());
BasicNetwork httpNetwork = new BasicNetwork(mockHttpStack);
Request<String> request = buildRequest();
request.setRetryPolicy(mMockRetryPolicy);
request.setShouldRetryConnectionErrors(false);
doThrow(new VolleyError()).when(mMockRetryPolicy).retry(any(VolleyError.class));
try {
httpNetwork.performRequest(request);
} catch (VolleyError e) {
// expected
}
// should not retry when there is no connection
verify(mMockRetryPolicy, never()).retry(any(VolleyError.class));
}
@Test
public void unauthorized() throws Exception {
MockHttpStack mockHttpStack = new MockHttpStack();
HttpResponse fakeResponse = new HttpResponse(401, Collections.<Header>emptyList());
mockHttpStack.setResponseToReturn(fakeResponse);
BasicNetwork httpNetwork = new BasicNetwork(mockHttpStack);
Request<String> request = buildRequest();
request.setRetryPolicy(mMockRetryPolicy);
doThrow(new VolleyError()).when(mMockRetryPolicy).retry(any(VolleyError.class));
try {
httpNetwork.performRequest(request);
} catch (VolleyError e) {
// expected
}
// should retry in case it's an auth failure.
verify(mMockRetryPolicy).retry(any(AuthFailureError.class));
}
@Test
public void forbidden() throws Exception {
MockHttpStack mockHttpStack = new MockHttpStack();
HttpResponse fakeResponse = new HttpResponse(403, Collections.<Header>emptyList());
mockHttpStack.setResponseToReturn(fakeResponse);
BasicNetwork httpNetwork = new BasicNetwork(mockHttpStack);
Request<String> request = buildRequest();
request.setRetryPolicy(mMockRetryPolicy);
doThrow(new VolleyError()).when(mMockRetryPolicy).retry(any(VolleyError.class));
try {
httpNetwork.performRequest(request);
} catch (VolleyError e) {
// expected
}
// should retry in case it's an auth failure.
verify(mMockRetryPolicy).retry(any(AuthFailureError.class));
}
@Test
public void redirect() throws Exception {
for (int i = 300; i <= 399; i++) {
MockHttpStack mockHttpStack = new MockHttpStack();
HttpResponse fakeResponse = new HttpResponse(i, Collections.<Header>emptyList());
mockHttpStack.setResponseToReturn(fakeResponse);
BasicNetwork httpNetwork = new BasicNetwork(mockHttpStack);
Request<String> request = buildRequest();
request.setRetryPolicy(mMockRetryPolicy);
doThrow(new VolleyError()).when(mMockRetryPolicy).retry(any(VolleyError.class));
try {
httpNetwork.performRequest(request);
} catch (VolleyError e) {
// expected
}
// should not retry 300 responses.
verify(mMockRetryPolicy, never()).retry(any(VolleyError.class));
reset(mMockRetryPolicy);
}
}
@Test
public void otherClientError() throws Exception {
for (int i = 400; i <= 499; i++) {
if (i == 401 || i == 403) {
// covered above.
continue;
}
MockHttpStack mockHttpStack = new MockHttpStack();
HttpResponse fakeResponse = new HttpResponse(i, Collections.<Header>emptyList());
mockHttpStack.setResponseToReturn(fakeResponse);
BasicNetwork httpNetwork = new BasicNetwork(mockHttpStack);
Request<String> request = buildRequest();
request.setRetryPolicy(mMockRetryPolicy);
doThrow(new VolleyError()).when(mMockRetryPolicy).retry(any(VolleyError.class));
try {
httpNetwork.performRequest(request);
} catch (VolleyError e) {
// expected
}
// should not retry other 400 errors.
verify(mMockRetryPolicy, never()).retry(any(VolleyError.class));
reset(mMockRetryPolicy);
}
}
@Test
public void serverError_enableRetries() throws Exception {
for (int i = 500; i <= 599; i++) {
MockHttpStack mockHttpStack = new MockHttpStack();
HttpResponse fakeResponse = new HttpResponse(i, Collections.<Header>emptyList());
mockHttpStack.setResponseToReturn(fakeResponse);
BasicNetwork httpNetwork = new BasicNetwork(mockHttpStack, new ByteArrayPool(4096));
Request<String> request = buildRequest();
request.setRetryPolicy(mMockRetryPolicy);
request.setShouldRetryServerErrors(true);
doThrow(new VolleyError()).when(mMockRetryPolicy).retry(any(VolleyError.class));
try {
httpNetwork.performRequest(request);
} catch (VolleyError e) {
// expected
}
// should retry all 500 errors
verify(mMockRetryPolicy).retry(any(ServerError.class));
reset(mMockRetryPolicy);
}
}
@Test
public void serverError_disableRetries() throws Exception {
for (int i = 500; i <= 599; i++) {
MockHttpStack mockHttpStack = new MockHttpStack();
HttpResponse fakeResponse = new HttpResponse(i, Collections.<Header>emptyList());
mockHttpStack.setResponseToReturn(fakeResponse);
BasicNetwork httpNetwork = new BasicNetwork(mockHttpStack);
Request<String> request = buildRequest();
request.setRetryPolicy(mMockRetryPolicy);
doThrow(new VolleyError()).when(mMockRetryPolicy).retry(any(VolleyError.class));
try {
httpNetwork.performRequest(request);
} catch (VolleyError e) {
// expected
}
// should not retry any 500 error w/ HTTP 500 retries turned off (the default).
verify(mMockRetryPolicy, never()).retry(any(VolleyError.class));
reset(mMockRetryPolicy);
}
}
private static Request<String> buildRequest() {
return new Request<String>(Request.Method.GET, "http://foo", null) {
@Override
protected Response<String> parseNetworkResponse(NetworkResponse response) {
return null;
}
@Override
protected void deliverResponse(String response) {}
@Override
public Map<String, String> getHeaders() {
Map<String, String> result = new HashMap<String, String>();
result.put("requestheader", "foo");
return result;
}
@Override
public Map<String, String> getParams() {
Map<String, String> result = new HashMap<String, String>();
result.put("requestpost", "foo");
return result;
}
};
}
}