aboutsummaryrefslogtreecommitdiff
path: root/src/test/java/com/android/volley
diff options
context:
space:
mode:
Diffstat (limited to 'src/test/java/com/android/volley')
-rw-r--r--src/test/java/com/android/volley/AsyncRequestQueueTest.java164
-rw-r--r--src/test/java/com/android/volley/cronet/CronetHttpStackTest.java381
-rw-r--r--src/test/java/com/android/volley/mock/MockAsyncStack.java86
-rw-r--r--src/test/java/com/android/volley/toolbox/BasicAsyncNetworkTest.java508
-rw-r--r--src/test/java/com/android/volley/toolbox/BasicNetworkTest.java40
-rw-r--r--src/test/java/com/android/volley/toolbox/DiskBasedCacheTest.java2
-rw-r--r--src/test/java/com/android/volley/utils/CacheTestUtils.java35
7 files changed, 1214 insertions, 2 deletions
diff --git a/src/test/java/com/android/volley/AsyncRequestQueueTest.java b/src/test/java/com/android/volley/AsyncRequestQueueTest.java
new file mode 100644
index 0000000..54ff0a1
--- /dev/null
+++ b/src/test/java/com/android/volley/AsyncRequestQueueTest.java
@@ -0,0 +1,164 @@
+/*
+ * 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;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+import static org.mockito.MockitoAnnotations.initMocks;
+
+import com.android.volley.mock.ShadowSystemClock;
+import com.android.volley.toolbox.NoAsyncCache;
+import com.android.volley.toolbox.StringRequest;
+import com.android.volley.utils.ImmediateResponseDelivery;
+import com.google.common.util.concurrent.MoreExecutors;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.ScheduledExecutorService;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.Config;
+
+/** Unit tests for AsyncRequestQueue, with all dependencies mocked out */
+@RunWith(RobolectricTestRunner.class)
+@Config(shadows = {ShadowSystemClock.class})
+public class AsyncRequestQueueTest {
+
+ @Mock private AsyncNetwork mMockNetwork;
+ @Mock private ScheduledExecutorService mMockScheduledExecutor;
+ private AsyncRequestQueue queue;
+
+ @Before
+ public void setUp() throws Exception {
+ ResponseDelivery mDelivery = new ImmediateResponseDelivery();
+ initMocks(this);
+ queue =
+ new AsyncRequestQueue.Builder(mMockNetwork)
+ .setAsyncCache(new NoAsyncCache())
+ .setResponseDelivery(mDelivery)
+ .setExecutorFactory(
+ new AsyncRequestQueue.ExecutorFactory() {
+ @Override
+ public ExecutorService createNonBlockingExecutor(
+ BlockingQueue<Runnable> taskQueue) {
+ return MoreExecutors.newDirectExecutorService();
+ }
+
+ @Override
+ public ExecutorService createBlockingExecutor(
+ BlockingQueue<Runnable> taskQueue) {
+ return MoreExecutors.newDirectExecutorService();
+ }
+
+ @Override
+ public ScheduledExecutorService
+ createNonBlockingScheduledExecutor() {
+ return mMockScheduledExecutor;
+ }
+ })
+ .build();
+ }
+
+ @Test
+ public void cancelAll_onlyCorrectTag() throws Exception {
+ queue.start();
+ Object tagA = new Object();
+ Object tagB = new Object();
+ StringRequest req1 = mock(StringRequest.class);
+ when(req1.getTag()).thenReturn(tagA);
+ StringRequest req2 = mock(StringRequest.class);
+ when(req2.getTag()).thenReturn(tagB);
+ StringRequest req3 = mock(StringRequest.class);
+ when(req3.getTag()).thenReturn(tagA);
+ StringRequest req4 = mock(StringRequest.class);
+ when(req4.getTag()).thenReturn(tagA);
+
+ queue.add(req1); // A
+ queue.add(req2); // B
+ queue.add(req3); // A
+ queue.cancelAll(tagA);
+ queue.add(req4); // A
+
+ verify(req1).cancel(); // A cancelled
+ verify(req3).cancel(); // A cancelled
+ verify(req2, never()).cancel(); // B not cancelled
+ verify(req4, never()).cancel(); // A added after cancel not cancelled
+ queue.stop();
+ }
+
+ @Test
+ public void add_notifiesListener() throws Exception {
+ RequestQueue.RequestEventListener listener = mock(RequestQueue.RequestEventListener.class);
+ queue.start();
+ queue.addRequestEventListener(listener);
+ StringRequest req = mock(StringRequest.class);
+
+ queue.add(req);
+
+ verify(listener).onRequestEvent(req, RequestQueue.RequestEvent.REQUEST_QUEUED);
+ verifyNoMoreInteractions(listener);
+ queue.stop();
+ }
+
+ @Test
+ public void finish_notifiesListener() throws Exception {
+ RequestQueue.RequestEventListener listener = mock(RequestQueue.RequestEventListener.class);
+ queue.start();
+ queue.addRequestEventListener(listener);
+ StringRequest req = mock(StringRequest.class);
+
+ queue.finish(req);
+
+ verify(listener).onRequestEvent(req, RequestQueue.RequestEvent.REQUEST_FINISHED);
+ verifyNoMoreInteractions(listener);
+ queue.stop();
+ }
+
+ @Test
+ public void sendRequestEvent_notifiesListener() throws Exception {
+ StringRequest req = mock(StringRequest.class);
+ RequestQueue.RequestEventListener listener = mock(RequestQueue.RequestEventListener.class);
+ queue.start();
+ queue.addRequestEventListener(listener);
+
+ queue.sendRequestEvent(req, RequestQueue.RequestEvent.REQUEST_NETWORK_DISPATCH_STARTED);
+
+ verify(listener)
+ .onRequestEvent(req, RequestQueue.RequestEvent.REQUEST_NETWORK_DISPATCH_STARTED);
+ verifyNoMoreInteractions(listener);
+ queue.stop();
+ }
+
+ @Test
+ public void removeRequestEventListener_removesListener() throws Exception {
+ StringRequest req = mock(StringRequest.class);
+ RequestQueue.RequestEventListener listener = mock(RequestQueue.RequestEventListener.class);
+ queue.start();
+ queue.addRequestEventListener(listener);
+ queue.removeRequestEventListener(listener);
+
+ queue.sendRequestEvent(req, RequestQueue.RequestEvent.REQUEST_NETWORK_DISPATCH_STARTED);
+
+ verifyNoMoreInteractions(listener);
+ queue.stop();
+ }
+}
diff --git a/src/test/java/com/android/volley/cronet/CronetHttpStackTest.java b/src/test/java/com/android/volley/cronet/CronetHttpStackTest.java
new file mode 100644
index 0000000..cedb6ff
--- /dev/null
+++ b/src/test/java/com/android/volley/cronet/CronetHttpStackTest.java
@@ -0,0 +1,381 @@
+/*
+ * Copyright (C) 2020 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.cronet;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import com.android.volley.Header;
+import com.android.volley.cronet.CronetHttpStack.CurlCommandLogger;
+import com.android.volley.mock.TestRequest;
+import com.android.volley.toolbox.AsyncHttpStack.OnRequestComplete;
+import com.android.volley.toolbox.UrlRewriter;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.util.concurrent.MoreExecutors;
+import java.io.UnsupportedEncodingException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Consumer;
+import org.chromium.net.CronetEngine;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Answers;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+
+@RunWith(RobolectricTestRunner.class)
+public class CronetHttpStackTest {
+ @Mock private CurlCommandLogger mMockCurlCommandLogger;
+ @Mock private OnRequestComplete mMockOnRequestComplete;
+ @Mock private UrlRewriter mMockUrlRewriter;
+
+ // A fake would be ideal here, but Cronet doesn't (yet) provide one, and at the moment we aren't
+ // exercising the full response flow.
+ @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+ private CronetEngine mMockCronetEngine;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ }
+
+ @Test
+ public void curlLogging_disabled() {
+ CronetHttpStack stack =
+ createStack(
+ new Consumer<CronetHttpStack.Builder>() {
+ @Override
+ public void accept(CronetHttpStack.Builder builder) {
+ // Default parameters should not enable cURL logging.
+ }
+ });
+
+ stack.executeRequest(
+ new TestRequest.Get(), ImmutableMap.<String, String>of(), mMockOnRequestComplete);
+
+ verify(mMockCurlCommandLogger, never()).logCurlCommand(anyString());
+ }
+
+ @Test
+ public void curlLogging_simpleTextRequest() {
+ CronetHttpStack stack =
+ createStack(
+ new Consumer<CronetHttpStack.Builder>() {
+ @Override
+ public void accept(CronetHttpStack.Builder builder) {
+ builder.setCurlLoggingEnabled(true);
+ }
+ });
+
+ stack.executeRequest(
+ new TestRequest.Get(), ImmutableMap.<String, String>of(), mMockOnRequestComplete);
+
+ ArgumentCaptor<String> curlCommandCaptor = ArgumentCaptor.forClass(String.class);
+ verify(mMockCurlCommandLogger).logCurlCommand(curlCommandCaptor.capture());
+ assertEquals("curl -X GET \"http://foo.com\"", curlCommandCaptor.getValue());
+ }
+
+ @Test
+ public void curlLogging_rewrittenUrl() {
+ CronetHttpStack stack =
+ createStack(
+ new Consumer<CronetHttpStack.Builder>() {
+ @Override
+ public void accept(CronetHttpStack.Builder builder) {
+ builder.setCurlLoggingEnabled(true)
+ .setUrlRewriter(mMockUrlRewriter);
+ }
+ });
+ when(mMockUrlRewriter.rewriteUrl("http://foo.com")).thenReturn("http://bar.com");
+
+ stack.executeRequest(
+ new TestRequest.Get(), ImmutableMap.<String, String>of(), mMockOnRequestComplete);
+
+ ArgumentCaptor<String> curlCommandCaptor = ArgumentCaptor.forClass(String.class);
+ verify(mMockCurlCommandLogger).logCurlCommand(curlCommandCaptor.capture());
+ assertEquals("curl -X GET \"http://bar.com\"", curlCommandCaptor.getValue());
+ }
+
+ @Test
+ public void curlLogging_headers_withoutTokens() {
+ CronetHttpStack stack =
+ createStack(
+ new Consumer<CronetHttpStack.Builder>() {
+ @Override
+ public void accept(CronetHttpStack.Builder builder) {
+ builder.setCurlLoggingEnabled(true);
+ }
+ });
+
+ stack.executeRequest(
+ new TestRequest.Delete() {
+ @Override
+ public Map<String, String> getHeaders() {
+ return ImmutableMap.of(
+ "SomeHeader", "SomeValue",
+ "Authorization", "SecretToken");
+ }
+ },
+ ImmutableMap.of("SomeOtherHeader", "SomeValue"),
+ mMockOnRequestComplete);
+
+ ArgumentCaptor<String> curlCommandCaptor = ArgumentCaptor.forClass(String.class);
+ verify(mMockCurlCommandLogger).logCurlCommand(curlCommandCaptor.capture());
+ // NOTE: Header order is stable because the implementation uses a TreeMap.
+ assertEquals(
+ "curl -X DELETE --header \"Authorization: [REDACTED]\" "
+ + "--header \"SomeHeader: SomeValue\" "
+ + "--header \"SomeOtherHeader: SomeValue\" \"http://foo.com\"",
+ curlCommandCaptor.getValue());
+ }
+
+ @Test
+ public void curlLogging_headers_withTokens() {
+ CronetHttpStack stack =
+ createStack(
+ new Consumer<CronetHttpStack.Builder>() {
+ @Override
+ public void accept(CronetHttpStack.Builder builder) {
+ builder.setCurlLoggingEnabled(true)
+ .setLogAuthTokensInCurlCommands(true);
+ }
+ });
+
+ stack.executeRequest(
+ new TestRequest.Delete() {
+ @Override
+ public Map<String, String> getHeaders() {
+ return ImmutableMap.of(
+ "SomeHeader", "SomeValue",
+ "Authorization", "SecretToken");
+ }
+ },
+ ImmutableMap.of("SomeOtherHeader", "SomeValue"),
+ mMockOnRequestComplete);
+
+ ArgumentCaptor<String> curlCommandCaptor = ArgumentCaptor.forClass(String.class);
+ verify(mMockCurlCommandLogger).logCurlCommand(curlCommandCaptor.capture());
+ // NOTE: Header order is stable because the implementation uses a TreeMap.
+ assertEquals(
+ "curl -X DELETE --header \"Authorization: SecretToken\" "
+ + "--header \"SomeHeader: SomeValue\" "
+ + "--header \"SomeOtherHeader: SomeValue\" \"http://foo.com\"",
+ curlCommandCaptor.getValue());
+ }
+
+ @Test
+ public void curlLogging_textRequest() {
+ CronetHttpStack stack =
+ createStack(
+ new Consumer<CronetHttpStack.Builder>() {
+ @Override
+ public void accept(CronetHttpStack.Builder builder) {
+ builder.setCurlLoggingEnabled(true);
+ }
+ });
+
+ stack.executeRequest(
+ new TestRequest.PostWithBody() {
+ @Override
+ public byte[] getBody() {
+ try {
+ return "hello".getBytes("UTF-8");
+ } catch (UnsupportedEncodingException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public String getBodyContentType() {
+ return "text/plain; charset=UTF-8";
+ }
+ },
+ ImmutableMap.<String, String>of(),
+ mMockOnRequestComplete);
+
+ ArgumentCaptor<String> curlCommandCaptor = ArgumentCaptor.forClass(String.class);
+ verify(mMockCurlCommandLogger).logCurlCommand(curlCommandCaptor.capture());
+ assertEquals(
+ "curl -X POST "
+ + "--header \"Content-Type: text/plain; charset=UTF-8\" \"http://foo.com\" "
+ + "--data-ascii \"hello\"",
+ curlCommandCaptor.getValue());
+ }
+
+ @Test
+ public void curlLogging_gzipTextRequest() {
+ CronetHttpStack stack =
+ createStack(
+ new Consumer<CronetHttpStack.Builder>() {
+ @Override
+ public void accept(CronetHttpStack.Builder builder) {
+ builder.setCurlLoggingEnabled(true);
+ }
+ });
+
+ stack.executeRequest(
+ new TestRequest.PostWithBody() {
+ @Override
+ public byte[] getBody() {
+ return new byte[] {1, 2, 3, 4, 5};
+ }
+
+ @Override
+ public String getBodyContentType() {
+ return "text/plain";
+ }
+
+ @Override
+ public Map<String, String> getHeaders() {
+ return ImmutableMap.of("Content-Encoding", "gzip, identity");
+ }
+ },
+ ImmutableMap.<String, String>of(),
+ mMockOnRequestComplete);
+
+ ArgumentCaptor<String> curlCommandCaptor = ArgumentCaptor.forClass(String.class);
+ verify(mMockCurlCommandLogger).logCurlCommand(curlCommandCaptor.capture());
+ assertEquals(
+ "echo 'AQIDBAU=' | base64 -d > /tmp/$$.bin; curl -X POST "
+ + "--header \"Content-Encoding: gzip, identity\" "
+ + "--header \"Content-Type: text/plain\" \"http://foo.com\" "
+ + "--data-binary @/tmp/$$.bin",
+ curlCommandCaptor.getValue());
+ }
+
+ @Test
+ public void curlLogging_binaryRequest() {
+ CronetHttpStack stack =
+ createStack(
+ new Consumer<CronetHttpStack.Builder>() {
+ @Override
+ public void accept(CronetHttpStack.Builder builder) {
+ builder.setCurlLoggingEnabled(true);
+ }
+ });
+
+ stack.executeRequest(
+ new TestRequest.PostWithBody() {
+ @Override
+ public byte[] getBody() {
+ return new byte[] {1, 2, 3, 4, 5};
+ }
+
+ @Override
+ public String getBodyContentType() {
+ return "application/octet-stream";
+ }
+ },
+ ImmutableMap.<String, String>of(),
+ mMockOnRequestComplete);
+
+ ArgumentCaptor<String> curlCommandCaptor = ArgumentCaptor.forClass(String.class);
+ verify(mMockCurlCommandLogger).logCurlCommand(curlCommandCaptor.capture());
+ assertEquals(
+ "echo 'AQIDBAU=' | base64 -d > /tmp/$$.bin; curl -X POST "
+ + "--header \"Content-Type: application/octet-stream\" \"http://foo.com\" "
+ + "--data-binary @/tmp/$$.bin",
+ curlCommandCaptor.getValue());
+ }
+
+ @Test
+ public void curlLogging_largeRequest() {
+ CronetHttpStack stack =
+ createStack(
+ new Consumer<CronetHttpStack.Builder>() {
+ @Override
+ public void accept(CronetHttpStack.Builder builder) {
+ builder.setCurlLoggingEnabled(true);
+ }
+ });
+
+ stack.executeRequest(
+ new TestRequest.PostWithBody() {
+ @Override
+ public byte[] getBody() {
+ return new byte[2048];
+ }
+
+ @Override
+ public String getBodyContentType() {
+ return "application/octet-stream";
+ }
+ },
+ ImmutableMap.<String, String>of(),
+ mMockOnRequestComplete);
+
+ ArgumentCaptor<String> curlCommandCaptor = ArgumentCaptor.forClass(String.class);
+ verify(mMockCurlCommandLogger).logCurlCommand(curlCommandCaptor.capture());
+ assertEquals(
+ "curl -X POST "
+ + "--header \"Content-Type: application/octet-stream\" \"http://foo.com\" "
+ + "[REQUEST BODY TOO LARGE TO INCLUDE]",
+ curlCommandCaptor.getValue());
+ }
+
+ @Test
+ public void getHeadersEmptyTest() {
+ List<Map.Entry<String, String>> list = new ArrayList<>();
+ List<Header> actual = CronetHttpStack.getHeaders(list);
+ List<Header> expected = new ArrayList<>();
+ assertEquals(expected, actual);
+ }
+
+ @Test
+ public void getHeadersNonEmptyTest() {
+ Map<String, String> headers = new HashMap<>();
+ for (int i = 1; i < 5; i++) {
+ headers.put("key" + i, "value" + i);
+ }
+ List<Map.Entry<String, String>> list = new ArrayList<>(headers.entrySet());
+ List<Header> actual = CronetHttpStack.getHeaders(list);
+ List<Header> expected = new ArrayList<>();
+ for (int i = 1; i < 5; i++) {
+ expected.add(new Header("key" + i, "value" + i));
+ }
+ assertHeaderListsEqual(expected, actual);
+ }
+
+ private void assertHeaderListsEqual(List<Header> expected, List<Header> actual) {
+ assertEquals(expected.size(), actual.size());
+ for (int i = 0; i < expected.size(); i++) {
+ assertEquals(expected.get(i).getName(), actual.get(i).getName());
+ assertEquals(expected.get(i).getValue(), actual.get(i).getValue());
+ }
+ }
+
+ private CronetHttpStack createStack(Consumer<CronetHttpStack.Builder> stackEditor) {
+ CronetHttpStack.Builder builder =
+ new CronetHttpStack.Builder(RuntimeEnvironment.application)
+ .setCronetEngine(mMockCronetEngine)
+ .setCurlCommandLogger(mMockCurlCommandLogger);
+ stackEditor.accept(builder);
+ CronetHttpStack stack = builder.build();
+ stack.setBlockingExecutor(MoreExecutors.newDirectExecutorService());
+ stack.setNonBlockingExecutor(MoreExecutors.newDirectExecutorService());
+ return stack;
+ }
+}
diff --git a/src/test/java/com/android/volley/mock/MockAsyncStack.java b/src/test/java/com/android/volley/mock/MockAsyncStack.java
new file mode 100644
index 0000000..5ea8343
--- /dev/null
+++ b/src/test/java/com/android/volley/mock/MockAsyncStack.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2020 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.mock;
+
+import com.android.volley.AuthFailureError;
+import com.android.volley.Request;
+import com.android.volley.toolbox.AsyncHttpStack;
+import com.android.volley.toolbox.HttpResponse;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+public class MockAsyncStack extends AsyncHttpStack {
+
+ private HttpResponse mResponseToReturn;
+
+ private IOException mExceptionToThrow;
+
+ private String mLastUrl;
+
+ private Map<String, String> mLastHeaders;
+
+ private byte[] mLastPostBody;
+
+ public String getLastUrl() {
+ return mLastUrl;
+ }
+
+ public Map<String, String> getLastHeaders() {
+ return mLastHeaders;
+ }
+
+ public byte[] getLastPostBody() {
+ return mLastPostBody;
+ }
+
+ public void setResponseToReturn(HttpResponse response) {
+ mResponseToReturn = response;
+ }
+
+ public void setExceptionToThrow(IOException exception) {
+ mExceptionToThrow = exception;
+ }
+
+ @Override
+ public void executeRequest(
+ Request<?> request, Map<String, String> additionalHeaders, OnRequestComplete callback) {
+ if (mExceptionToThrow != null) {
+ callback.onError(mExceptionToThrow);
+ return;
+ }
+ mLastUrl = request.getUrl();
+ mLastHeaders = new HashMap<>();
+ try {
+ if (request.getHeaders() != null) {
+ mLastHeaders.putAll(request.getHeaders());
+ }
+ } catch (AuthFailureError authFailureError) {
+ callback.onAuthError(authFailureError);
+ return;
+ }
+ if (additionalHeaders != null) {
+ mLastHeaders.putAll(additionalHeaders);
+ }
+ try {
+ mLastPostBody = request.getBody();
+ } catch (AuthFailureError e) {
+ mLastPostBody = null;
+ }
+ callback.onSuccess(mResponseToReturn);
+ }
+}
diff --git a/src/test/java/com/android/volley/toolbox/BasicAsyncNetworkTest.java b/src/test/java/com/android/volley/toolbox/BasicAsyncNetworkTest.java
new file mode 100644
index 0000000..91d4062
--- /dev/null
+++ b/src/test/java/com/android/volley/toolbox/BasicAsyncNetworkTest.java
@@ -0,0 +1,508 @@
+/*
+ * Copyright (C) 2020 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.*;
+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.AsyncNetwork;
+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.MockAsyncStack;
+import com.google.common.util.concurrent.MoreExecutors;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.net.HttpURLConnection;
+import java.net.MalformedURLException;
+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 java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+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 BasicAsyncNetworkTest {
+
+ @Mock private RetryPolicy mMockRetryPolicy;
+ @Mock private AsyncNetwork.OnRequestComplete mockCallback;
+ private ExecutorService executor = MoreExecutors.newDirectExecutorService();
+
+ @Before
+ public void setUp() throws Exception {
+ initMocks(this);
+ }
+
+ @Test
+ public void headersAndPostParams() throws Exception {
+ MockAsyncStack mockAsyncStack = new MockAsyncStack();
+ HttpResponse fakeResponse =
+ new HttpResponse(
+ 200,
+ Collections.<Header>emptyList(),
+ "foobar".getBytes(StandardCharsets.UTF_8));
+ mockAsyncStack.setResponseToReturn(fakeResponse);
+ BasicAsyncNetwork httpNetwork = new BasicAsyncNetwork.Builder(mockAsyncStack).build();
+ httpNetwork.setBlockingExecutor(executor);
+ Request<String> request = buildRequest();
+ Entry entry = new Entry();
+ entry.etag = "foobar";
+ entry.lastModified = 1503102002000L;
+ request.setCacheEntry(entry);
+ perform(request, httpNetwork).get();
+ assertEquals("foo", mockAsyncStack.getLastHeaders().get("requestheader"));
+ assertEquals("foobar", mockAsyncStack.getLastHeaders().get("If-None-Match"));
+ assertEquals(
+ "Sat, 19 Aug 2017 00:20:02 GMT",
+ mockAsyncStack.getLastHeaders().get("If-Modified-Since"));
+ assertEquals(
+ "requestpost=foo&",
+ new String(mockAsyncStack.getLastPostBody(), StandardCharsets.UTF_8));
+ }
+
+ @Test
+ public void headersAndPostParamsStream() throws Exception {
+ MockAsyncStack mockAsyncStack = new MockAsyncStack();
+ ByteArrayInputStream stream = new ByteArrayInputStream("foobar".getBytes("UTF-8"));
+ HttpResponse fakeResponse =
+ new HttpResponse(200, Collections.<Header>emptyList(), 6, stream);
+ mockAsyncStack.setResponseToReturn(fakeResponse);
+ BasicAsyncNetwork httpNetwork = new BasicAsyncNetwork.Builder(mockAsyncStack).build();
+ httpNetwork.setBlockingExecutor(executor);
+ Request<String> request = buildRequest();
+ Entry entry = new Entry();
+ entry.etag = "foobar";
+ entry.lastModified = 1503102002000L;
+ request.setCacheEntry(entry);
+ perform(request, httpNetwork).get();
+ assertEquals("foo", mockAsyncStack.getLastHeaders().get("requestheader"));
+ assertEquals("foobar", mockAsyncStack.getLastHeaders().get("If-None-Match"));
+ assertEquals(
+ "Sat, 19 Aug 2017 00:20:02 GMT",
+ mockAsyncStack.getLastHeaders().get("If-Modified-Since"));
+ assertEquals(
+ "requestpost=foo&",
+ new String(mockAsyncStack.getLastPostBody(), StandardCharsets.UTF_8));
+ }
+
+ @Test
+ public void notModified() throws Exception {
+ MockAsyncStack mockAsyncStack = new MockAsyncStack();
+ 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);
+ mockAsyncStack.setResponseToReturn(fakeResponse);
+ BasicAsyncNetwork httpNetwork = new BasicAsyncNetwork.Builder(mockAsyncStack).build();
+ httpNetwork.setBlockingExecutor(executor);
+ 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);
+ httpNetwork.performRequest(request, mockCallback);
+ NetworkResponse response = perform(request, httpNetwork).get();
+ 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 {
+ MockAsyncStack mockAsyncStack = new MockAsyncStack();
+ 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);
+ mockAsyncStack.setResponseToReturn(fakeResponse);
+ BasicAsyncNetwork httpNetwork = new BasicAsyncNetwork.Builder(mockAsyncStack).build();
+ httpNetwork.setBlockingExecutor(executor);
+ 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 = perform(request, httpNetwork).get();
+ 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 {
+ MockAsyncStack mockAsyncStack = new MockAsyncStack();
+ mockAsyncStack.setExceptionToThrow(new SocketTimeoutException());
+ BasicAsyncNetwork httpNetwork = new BasicAsyncNetwork.Builder(mockAsyncStack).build();
+ httpNetwork.setBlockingExecutor(executor);
+ Request<String> request = buildRequest();
+ request.setRetryPolicy(mMockRetryPolicy);
+ doThrow(new VolleyError()).when(mMockRetryPolicy).retry(any(VolleyError.class));
+ httpNetwork.performRequest(request, mockCallback);
+ verify(mockCallback, times(1)).onError(any(VolleyError.class));
+ verify(mockCallback, never()).onSuccess(any(NetworkResponse.class));
+ // should retry socket timeouts
+ verify(mMockRetryPolicy).retry(any(TimeoutError.class));
+ reset(mMockRetryPolicy, mockCallback);
+ }
+
+ @Test
+ public void noConnectionDefault() throws Exception {
+ MockAsyncStack mockAsyncStack = new MockAsyncStack();
+ mockAsyncStack.setExceptionToThrow(new IOException());
+ BasicAsyncNetwork httpNetwork = new BasicAsyncNetwork.Builder(mockAsyncStack).build();
+ httpNetwork.setBlockingExecutor(executor);
+ Request<String> request = buildRequest();
+ request.setRetryPolicy(mMockRetryPolicy);
+ doThrow(new VolleyError()).when(mMockRetryPolicy).retry(any(VolleyError.class));
+ httpNetwork.performRequest(request, mockCallback);
+ verify(mockCallback, times(1)).onError(any(VolleyError.class));
+ verify(mockCallback, never()).onSuccess(any(NetworkResponse.class));
+ // should not retry when there is no connection
+ verify(mMockRetryPolicy, never()).retry(any(VolleyError.class));
+ reset(mMockRetryPolicy, mockCallback);
+ }
+
+ @Test
+ public void noConnectionRetry() throws Exception {
+ MockAsyncStack mockAsyncStack = new MockAsyncStack();
+ mockAsyncStack.setExceptionToThrow(new IOException());
+ BasicAsyncNetwork httpNetwork = new BasicAsyncNetwork.Builder(mockAsyncStack).build();
+ httpNetwork.setBlockingExecutor(executor);
+ Request<String> request = buildRequest();
+ request.setRetryPolicy(mMockRetryPolicy);
+ request.setShouldRetryConnectionErrors(true);
+ doThrow(new VolleyError()).when(mMockRetryPolicy).retry(any(VolleyError.class));
+ httpNetwork.performRequest(request, mockCallback);
+ verify(mockCallback, times(1)).onError(any(VolleyError.class));
+ verify(mockCallback, never()).onSuccess(any(NetworkResponse.class));
+ // should retry when there is no connection
+ verify(mMockRetryPolicy).retry(any(NoConnectionError.class));
+ reset(mMockRetryPolicy, mockCallback);
+ }
+
+ @Test
+ public void noConnectionNoRetry() throws Exception {
+ MockAsyncStack mockAsyncStack = new MockAsyncStack();
+ mockAsyncStack.setExceptionToThrow(new IOException());
+ BasicAsyncNetwork httpNetwork = new BasicAsyncNetwork.Builder(mockAsyncStack).build();
+ httpNetwork.setBlockingExecutor(executor);
+ Request<String> request = buildRequest();
+ request.setRetryPolicy(mMockRetryPolicy);
+ request.setShouldRetryConnectionErrors(false);
+ doThrow(new VolleyError()).when(mMockRetryPolicy).retry(any(VolleyError.class));
+ httpNetwork.performRequest(request, mockCallback);
+ verify(mockCallback, times(1)).onError(any(VolleyError.class));
+ verify(mockCallback, never()).onSuccess(any(NetworkResponse.class));
+ // should not retry when there is no connection
+ verify(mMockRetryPolicy, never()).retry(any(VolleyError.class));
+ reset(mMockRetryPolicy, mockCallback);
+ }
+
+ @Test
+ public void unauthorized() throws Exception {
+ MockAsyncStack mockAsyncStack = new MockAsyncStack();
+ HttpResponse fakeResponse = new HttpResponse(401, Collections.<Header>emptyList());
+ mockAsyncStack.setResponseToReturn(fakeResponse);
+ BasicAsyncNetwork httpNetwork = new BasicAsyncNetwork.Builder(mockAsyncStack).build();
+ httpNetwork.setBlockingExecutor(executor);
+ Request<String> request = buildRequest();
+ request.setRetryPolicy(mMockRetryPolicy);
+ doThrow(new VolleyError()).when(mMockRetryPolicy).retry(any(VolleyError.class));
+ httpNetwork.performRequest(request, mockCallback);
+ verify(mockCallback, times(1)).onError(any(VolleyError.class));
+ verify(mockCallback, never()).onSuccess(any(NetworkResponse.class));
+ // should retry in case it's an auth failure.
+ verify(mMockRetryPolicy).retry(any(AuthFailureError.class));
+ reset(mMockRetryPolicy, mockCallback);
+ }
+
+ @Test(expected = RuntimeException.class)
+ public void malformedUrlRequest() throws VolleyError, ExecutionException, InterruptedException {
+ MockAsyncStack mockAsyncStack = new MockAsyncStack();
+ mockAsyncStack.setExceptionToThrow(new MalformedURLException());
+ BasicAsyncNetwork httpNetwork = new BasicAsyncNetwork.Builder(mockAsyncStack).build();
+ httpNetwork.setBlockingExecutor(executor);
+ Request<String> request = buildRequest();
+ request.setRetryPolicy(mMockRetryPolicy);
+ doThrow(new VolleyError()).when(mMockRetryPolicy).retry(any(VolleyError.class));
+ perform(request, httpNetwork).get();
+ }
+
+ @Test
+ public void forbidden() throws Exception {
+ MockAsyncStack mockAsyncStack = new MockAsyncStack();
+ HttpResponse fakeResponse = new HttpResponse(403, Collections.<Header>emptyList());
+ mockAsyncStack.setResponseToReturn(fakeResponse);
+ BasicAsyncNetwork httpNetwork = new BasicAsyncNetwork.Builder(mockAsyncStack).build();
+ httpNetwork.setBlockingExecutor(executor);
+ Request<String> request = buildRequest();
+ request.setRetryPolicy(mMockRetryPolicy);
+ doThrow(new VolleyError()).when(mMockRetryPolicy).retry(any(VolleyError.class));
+ httpNetwork.performRequest(request, mockCallback);
+ verify(mockCallback, times(1)).onError(any(VolleyError.class));
+ verify(mockCallback, never()).onSuccess(any(NetworkResponse.class));
+ // should retry in case it's an auth failure.
+ verify(mMockRetryPolicy).retry(any(AuthFailureError.class));
+ reset(mMockRetryPolicy, mockCallback);
+ }
+
+ @Test
+ public void redirect() throws Exception {
+ for (int i = 300; i <= 399; i++) {
+ MockAsyncStack mockAsyncStack = new MockAsyncStack();
+ HttpResponse fakeResponse = new HttpResponse(i, Collections.<Header>emptyList());
+ mockAsyncStack.setResponseToReturn(fakeResponse);
+ BasicAsyncNetwork httpNetwork = new BasicAsyncNetwork.Builder(mockAsyncStack).build();
+ httpNetwork.setBlockingExecutor(executor);
+ Request<String> request = buildRequest();
+ request.setRetryPolicy(mMockRetryPolicy);
+ doThrow(new VolleyError()).when(mMockRetryPolicy).retry(any(VolleyError.class));
+ httpNetwork.performRequest(request, mockCallback);
+ if (i != 304) {
+ verify(mockCallback, times(1)).onError(any(VolleyError.class));
+ verify(mockCallback, never()).onSuccess(any(NetworkResponse.class));
+ } else {
+ verify(mockCallback, never()).onError(any(VolleyError.class));
+ verify(mockCallback, times(1)).onSuccess(any(NetworkResponse.class));
+ }
+ // should not retry 300 responses.
+ verify(mMockRetryPolicy, never()).retry(any(VolleyError.class));
+ reset(mMockRetryPolicy, mockCallback);
+ }
+ }
+
+ @Test
+ public void otherClientError() throws Exception {
+ for (int i = 400; i <= 499; i++) {
+ if (i == 401 || i == 403) {
+ // covered above.
+ continue;
+ }
+ MockAsyncStack mockAsyncStack = new MockAsyncStack();
+ HttpResponse fakeResponse = new HttpResponse(i, Collections.<Header>emptyList());
+ mockAsyncStack.setResponseToReturn(fakeResponse);
+ BasicAsyncNetwork httpNetwork = new BasicAsyncNetwork.Builder(mockAsyncStack).build();
+ httpNetwork.setBlockingExecutor(executor);
+ Request<String> request = buildRequest();
+ request.setRetryPolicy(mMockRetryPolicy);
+ doThrow(new VolleyError()).when(mMockRetryPolicy).retry(any(VolleyError.class));
+ httpNetwork.performRequest(request, mockCallback);
+ verify(mockCallback, times(1)).onError(any(VolleyError.class));
+ verify(mockCallback, never()).onSuccess(any(NetworkResponse.class));
+ // should not retry other 400 errors.
+ verify(mMockRetryPolicy, never()).retry(any(VolleyError.class));
+ reset(mMockRetryPolicy, mockCallback);
+ }
+ }
+
+ @Test
+ public void serverError_enableRetries() throws Exception {
+ for (int i = 500; i <= 599; i++) {
+ MockAsyncStack mockAsyncStack = new MockAsyncStack();
+ HttpResponse fakeResponse = new HttpResponse(i, Collections.<Header>emptyList());
+ mockAsyncStack.setResponseToReturn(fakeResponse);
+ BasicAsyncNetwork httpNetwork =
+ new BasicAsyncNetwork.Builder(mockAsyncStack)
+ .setPool(new ByteArrayPool(4096))
+ .build();
+ httpNetwork.setBlockingExecutor(executor);
+ Request<String> request = buildRequest();
+ request.setRetryPolicy(mMockRetryPolicy);
+ request.setShouldRetryServerErrors(true);
+ doThrow(new VolleyError()).when(mMockRetryPolicy).retry(any(VolleyError.class));
+ httpNetwork.performRequest(request, mockCallback);
+ verify(mockCallback, times(1)).onError(any(VolleyError.class));
+ verify(mockCallback, never()).onSuccess(any(NetworkResponse.class));
+ // should retry all 500 errors
+ verify(mMockRetryPolicy).retry(any(ServerError.class));
+ reset(mMockRetryPolicy, mockCallback);
+ }
+ }
+
+ @Test
+ public void serverError_disableRetries() throws Exception {
+ for (int i = 500; i <= 599; i++) {
+ MockAsyncStack mockAsyncStack = new MockAsyncStack();
+ HttpResponse fakeResponse = new HttpResponse(i, Collections.<Header>emptyList());
+ mockAsyncStack.setResponseToReturn(fakeResponse);
+ BasicAsyncNetwork httpNetwork = new BasicAsyncNetwork.Builder(mockAsyncStack).build();
+ httpNetwork.setBlockingExecutor(executor);
+ Request<String> request = buildRequest();
+ request.setRetryPolicy(mMockRetryPolicy);
+ doThrow(new VolleyError()).when(mMockRetryPolicy).retry(any(VolleyError.class));
+ httpNetwork.performRequest(request, mockCallback);
+ verify(mockCallback, times(1)).onError(any(VolleyError.class));
+ verify(mockCallback, never()).onSuccess(any(NetworkResponse.class));
+ // should not retry any 500 error w/ HTTP 500 retries turned off (the default).
+ verify(mMockRetryPolicy, never()).retry(any(VolleyError.class));
+ reset(mMockRetryPolicy, mockCallback);
+ }
+ }
+
+ @Test
+ public void notModifiedShortCircuit() throws Exception {
+ MockAsyncStack mockAsyncStack = new MockAsyncStack();
+ 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);
+ mockAsyncStack.setResponseToReturn(fakeResponse);
+ BasicAsyncNetwork httpNetwork = new BasicAsyncNetwork.Builder(mockAsyncStack).build();
+ httpNetwork.setBlockingExecutor(executor);
+ Request<String> request = buildRequest();
+ httpNetwork.performRequest(request, mockCallback);
+ verify(mockCallback, times(1)).onSuccess(any(NetworkResponse.class));
+ verify(mockCallback, never()).onError(any(VolleyError.class));
+ reset(mMockRetryPolicy, mockCallback);
+ }
+
+ @Test
+ public void performRequestSuccess() throws Exception {
+ MockAsyncStack mockAsyncStack = new MockAsyncStack();
+ HttpResponse fakeResponse =
+ new HttpResponse(
+ 200,
+ Collections.<Header>emptyList(),
+ "foobar".getBytes(StandardCharsets.UTF_8));
+ mockAsyncStack.setResponseToReturn(fakeResponse);
+ BasicAsyncNetwork httpNetwork = new BasicAsyncNetwork.Builder(mockAsyncStack).build();
+ httpNetwork.setBlockingExecutor(executor);
+ Request<String> request = buildRequest();
+ Entry entry = new Entry();
+ entry.etag = "foobar";
+ entry.lastModified = 1503102002000L;
+ request.setCacheEntry(entry);
+ httpNetwork.performRequest(request, mockCallback);
+ verify(mockCallback, times(1)).onSuccess(any(NetworkResponse.class));
+ verify(mockCallback, never()).onError(any(VolleyError.class));
+ reset(mMockRetryPolicy, mockCallback);
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void performRequestNeverSetExecutorTest() throws Exception {
+ MockAsyncStack mockAsyncStack = new MockAsyncStack();
+ HttpResponse fakeResponse = new HttpResponse(200, Collections.<Header>emptyList());
+ mockAsyncStack.setResponseToReturn(fakeResponse);
+ BasicAsyncNetwork httpNetwork = new BasicAsyncNetwork.Builder(mockAsyncStack).build();
+ Request<String> request = buildRequest();
+ perform(request, httpNetwork).get();
+ }
+
+ /** Helper functions */
+ private CompletableFuture<NetworkResponse> perform(Request<?> request, AsyncNetwork network)
+ throws VolleyError {
+ final CompletableFuture<NetworkResponse> future = new CompletableFuture<>();
+ network.performRequest(
+ request,
+ new AsyncNetwork.OnRequestComplete() {
+ @Override
+ public void onSuccess(NetworkResponse networkResponse) {
+ future.complete(networkResponse);
+ }
+
+ @Override
+ public void onError(VolleyError volleyError) {
+ future.complete(null);
+ }
+ });
+ return future;
+ }
+
+ 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;
+ }
+ };
+ }
+}
diff --git a/src/test/java/com/android/volley/toolbox/BasicNetworkTest.java b/src/test/java/com/android/volley/toolbox/BasicNetworkTest.java
index fec0694..3630379 100644
--- a/src/test/java/com/android/volley/toolbox/BasicNetworkTest.java
+++ b/src/test/java/com/android/volley/toolbox/BasicNetworkTest.java
@@ -30,6 +30,7 @@ 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;
@@ -176,7 +177,7 @@ public class BasicNetworkTest {
}
@Test
- public void noConnection() throws Exception {
+ public void noConnectionDefault() throws Exception {
MockHttpStack mockHttpStack = new MockHttpStack();
mockHttpStack.setExceptionToThrow(new IOException());
BasicNetwork httpNetwork = new BasicNetwork(mockHttpStack);
@@ -193,6 +194,43 @@ public class BasicNetworkTest {
}
@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());
diff --git a/src/test/java/com/android/volley/toolbox/DiskBasedCacheTest.java b/src/test/java/com/android/volley/toolbox/DiskBasedCacheTest.java
index ccf68fa..db6e491 100644
--- a/src/test/java/com/android/volley/toolbox/DiskBasedCacheTest.java
+++ b/src/test/java/com/android/volley/toolbox/DiskBasedCacheTest.java
@@ -59,7 +59,7 @@ import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
@RunWith(RobolectricTestRunner.class)
-@Config(manifest = "src/main/AndroidManifest.xml", sdk = 16)
+@Config(sdk = 16)
public class DiskBasedCacheTest {
private static final int MAX_SIZE = 1024 * 1024;
diff --git a/src/test/java/com/android/volley/utils/CacheTestUtils.java b/src/test/java/com/android/volley/utils/CacheTestUtils.java
index 49ab996..5980712 100644
--- a/src/test/java/com/android/volley/utils/CacheTestUtils.java
+++ b/src/test/java/com/android/volley/utils/CacheTestUtils.java
@@ -16,6 +16,11 @@
package com.android.volley.utils;
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertThat;
+
import com.android.volley.Cache;
import java.util.Random;
@@ -51,4 +56,34 @@ public class CacheTestUtils {
public static Cache.Entry makeRandomCacheEntry(byte[] data) {
return makeRandomCacheEntry(data, false, false);
}
+
+ public static void assertThatEntriesAreEqual(Cache.Entry actual, Cache.Entry expected) {
+ assertNotNull(actual);
+ assertThat(actual.data, is(equalTo(expected.data)));
+ assertThat(actual.etag, is(equalTo(expected.etag)));
+ assertThat(actual.lastModified, is(equalTo(expected.lastModified)));
+ assertThat(actual.responseHeaders, is(equalTo(expected.responseHeaders)));
+ assertThat(actual.serverDate, is(equalTo(expected.serverDate)));
+ assertThat(actual.softTtl, is(equalTo(expected.softTtl)));
+ assertThat(actual.ttl, is(equalTo(expected.ttl)));
+ }
+
+ public static Cache.Entry randomData(int length) {
+ Cache.Entry entry = new Cache.Entry();
+ byte[] data = new byte[length];
+ new Random(42).nextBytes(data); // explicit seed for reproducible results
+ entry.data = data;
+ return entry;
+ }
+
+ public static int getEntrySizeOnDisk(String key) {
+ // Header size is:
+ // 4 bytes for magic int
+ // 8 + len(key) bytes for key (long length)
+ // 8 bytes for etag (long length + 0 characters)
+ // 32 bytes for serverDate, lastModified, ttl, and softTtl longs
+ // 4 bytes for length of header list int
+ // == 56 + len(key) bytes total.
+ return 56 + key.length();
+ }
}