aboutsummaryrefslogtreecommitdiff
path: root/src/test/java/com/android/volley/cronet/CronetHttpStackTest.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/test/java/com/android/volley/cronet/CronetHttpStackTest.java')
-rw-r--r--src/test/java/com/android/volley/cronet/CronetHttpStackTest.java381
1 files changed, 381 insertions, 0 deletions
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;
+ }
+}