[aw] Add proxy override integration tests

Bug: 902215
Change-Id: Ibac9527580253aa924ed8c553bb5f01f47e368aa
Reviewed-on: https://chromium-review.googlesource.com/c/1391561
Reviewed-by: Nate Fischer <ntfschr@chromium.org>
Reviewed-by: Tobias Sargeant <tobiasjs@chromium.org>
Commit-Queue: Laís Minchillo <laisminchillo@chromium.org>
Cr-Commit-Position: refs/heads/master@{#623179}
diff --git a/android_webview/javatests/src/org/chromium/android_webview/test/AwProxyControllerTest.java b/android_webview/javatests/src/org/chromium/android_webview/test/AwProxyControllerTest.java
new file mode 100644
index 0000000..ece3870
--- /dev/null
+++ b/android_webview/javatests/src/org/chromium/android_webview/test/AwProxyControllerTest.java
@@ -0,0 +1,252 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.android_webview.test;
+
+import android.support.test.filters.MediumTest;
+import android.support.test.filters.SmallTest;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.chromium.android_webview.AwContents;
+import org.chromium.android_webview.AwProxyController;
+import org.chromium.base.ThreadUtils;
+import org.chromium.base.test.util.CallbackHelper;
+import org.chromium.base.test.util.Feature;
+import org.chromium.net.test.util.TestWebServer;
+
+import java.util.concurrent.Executor;
+
+/**
+ * AwProxyController tests.
+ */
+@RunWith(AwJUnit4ClassRunner.class)
+public class AwProxyControllerTest {
+    @Rule
+    public AwActivityTestRule mActivityTestRule = new AwActivityTestRule();
+
+    private static final String MATCH_ALL_SCHEMES = "*";
+    private static final String DIRECT = "direct://";
+    private static final String LOOPBACK = "<-loopback>";
+    private static final String CONTENT = "CONTENT";
+    private static final String PROXY = "PROXY";
+
+    private AwProxyController mAwProxyController;
+    private TestWebServer mContentServer;
+    private TestWebServer mProxyServer;
+    private String mContentUrl;
+    private String mProxyUrl;
+
+    @Before
+    public void setup() throws Exception {
+        mAwProxyController = new AwProxyController();
+        mContentServer = TestWebServer.start();
+        mProxyServer = TestWebServer.startAdditional();
+        mContentUrl = mContentServer.setResponse(
+                "/", "<html><head><title>" + CONTENT + "</title></head>Page 1</html>", null);
+        mProxyUrl = mProxyServer
+                            .setResponse("/",
+                                    "<html><head><title>" + PROXY + "</title></head>Page 1</html>",
+                                    null)
+                            .replace("http://", "")
+                            .replace("/", "");
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        clearProxyOverrideSync();
+        mContentServer.shutdown();
+        mProxyServer.shutdown();
+    }
+
+    @Test
+    @MediumTest
+    @Feature({"AndroidWebView"})
+    public void testProxyOverride() throws Throwable {
+        final TestAwContentsClient contentsClient = new TestAwContentsClient();
+        final AwTestContainerView testContainerView =
+                mActivityTestRule.createAwTestContainerViewOnMainSync(contentsClient);
+        final AwContents awContents = testContainerView.getAwContents();
+
+        int proxyServerRequestCount = mProxyServer.getRequestCount("/");
+
+        // Set proxy override and load content url
+        // Localhost should use proxy with loopback rule
+        setProxyOverrideSync(
+                new String[][] {{MATCH_ALL_SCHEMES, mProxyUrl}}, new String[] {LOOPBACK});
+        TestAwContentsClient.OnReceivedTitleHelper onReceivedTitleHelper =
+                contentsClient.getOnReceivedTitleHelper();
+        int onReceivedTitleCallCount = onReceivedTitleHelper.getCallCount();
+        mActivityTestRule.loadUrlSync(
+                awContents, contentsClient.getOnPageFinishedHelper(), mContentUrl);
+        onReceivedTitleHelper.waitForCallback(onReceivedTitleCallCount);
+
+        proxyServerRequestCount++;
+        Assert.assertEquals(proxyServerRequestCount, mProxyServer.getRequestCount("/"));
+        Assert.assertEquals(PROXY, onReceivedTitleHelper.getTitle());
+
+        // Clear proxy override and load content url
+        clearProxyOverrideSync();
+        onReceivedTitleCallCount = onReceivedTitleHelper.getCallCount();
+        mActivityTestRule.loadUrlSync(
+                awContents, contentsClient.getOnPageFinishedHelper(), mContentUrl);
+        onReceivedTitleHelper.waitForCallback(onReceivedTitleCallCount);
+
+        Assert.assertEquals(proxyServerRequestCount, mProxyServer.getRequestCount("/"));
+        Assert.assertEquals(CONTENT, onReceivedTitleHelper.getTitle());
+    }
+
+    @Test
+    @MediumTest
+    @Feature({"AndroidWebView"})
+    public void testProxyOverrideLocalhost() throws Throwable {
+        final TestAwContentsClient contentsClient = new TestAwContentsClient();
+        final AwTestContainerView testContainerView =
+                mActivityTestRule.createAwTestContainerViewOnMainSync(contentsClient);
+        final AwContents awContents = testContainerView.getAwContents();
+
+        int proxyServerRequestCount = mProxyServer.getRequestCount("/");
+
+        // Set proxy override and load a local url
+        // Localhost should not use proxy settings
+        setProxyOverrideSync(new String[][] {{MATCH_ALL_SCHEMES, mProxyUrl}}, new String[] {});
+        TestAwContentsClient.OnReceivedTitleHelper onReceivedTitleHelper =
+                contentsClient.getOnReceivedTitleHelper();
+        int onReceivedTitleCallCount = onReceivedTitleHelper.getCallCount();
+        mActivityTestRule.loadUrlSync(
+                awContents, contentsClient.getOnPageFinishedHelper(), mContentUrl);
+        onReceivedTitleHelper.waitForCallback(onReceivedTitleCallCount);
+
+        Assert.assertEquals(proxyServerRequestCount, mProxyServer.getRequestCount("/"));
+        Assert.assertEquals(CONTENT, onReceivedTitleHelper.getTitle());
+    }
+
+    @Test
+    @SmallTest
+    @Feature({"AndroidWebView"})
+    public void testCallbacks() throws Throwable {
+        // Test setProxyOverride's callback
+        setProxyOverrideSync(null, null);
+        // Test clearProxyOverride's callback with a proxy override setting
+        clearProxyOverrideSync();
+        // Test clearProxyOverride's callback without a proxy override setting
+        clearProxyOverrideSync();
+        // If we got to this point it means all callbacks were called as expected
+    }
+
+    @Test
+    @SmallTest
+    @Feature({"AndroidWebView"})
+    public void testValidInput() throws Throwable {
+        String[][] proxyRules = {{MATCH_ALL_SCHEMES, DIRECT},
+                {MATCH_ALL_SCHEMES, "www.example.com"},
+                {MATCH_ALL_SCHEMES, "http://www.example.com"},
+                {MATCH_ALL_SCHEMES, "https://www.example.com"},
+                {MATCH_ALL_SCHEMES, "www.example.com:123"},
+                {MATCH_ALL_SCHEMES, "http://www.example.com:123"}, {MATCH_ALL_SCHEMES, "10.0.0.1"},
+                {MATCH_ALL_SCHEMES, "10.0.0.1:123"}, {MATCH_ALL_SCHEMES, "http://10.0.0.1"},
+                {MATCH_ALL_SCHEMES, "https://10.0.0.1"}, {MATCH_ALL_SCHEMES, "http://10.0.0.1:123"},
+                {MATCH_ALL_SCHEMES, "[FE80:CD00:0000:0CDE:1257:0000:211E:729C]"},
+                {MATCH_ALL_SCHEMES, "[FE80:CD00:0:CDE:1257:0:211E:729C]"}};
+        String[] bypassRules = {
+                "www.rule.com", "*.rule.com", "*rule.com", "www.*.com", "www.rule*"};
+        setProxyOverrideSync(proxyRules, bypassRules);
+        // If we got to this point it means our input was accepted as expected
+    }
+
+    @Test
+    @SmallTest
+    @Feature({"AndroidWebView"})
+    public void testInvalidProxyUrls() throws Throwable {
+        String[] invalidProxyUrls = {
+                null,
+                "", // empty
+                "   ", // spaces only
+                "dddf:", // bad port
+                "dddd:d", // bad port
+                "http://", // no valid host/port
+                "http:/", // ambiguous, will fail due to bad port
+                "http:", // ambiguous, will fail due to bad port
+                "direct://xyz", // direct shouldn't have host/port
+        };
+
+        for (String proxyUrl : invalidProxyUrls) {
+            try {
+                setProxyOverrideSync(new String[][] {{MATCH_ALL_SCHEMES, proxyUrl}}, null);
+                Assert.fail("No exception for invalid proxy url: " + proxyUrl);
+            } catch (IllegalArgumentException e) {
+                // Expected
+            }
+        }
+    }
+
+    @Test
+    @SmallTest
+    @Feature({"AndroidWebView"})
+    public void testInvalidBypassRules() throws Throwable {
+        String[] invalidBypassRules = {
+                null,
+                "", // empty
+                "http://", // no valid host/port
+                "20:example.com", // bad port
+                "example.com:-20" // bad port
+        };
+
+        for (String bypassRule : invalidBypassRules) {
+            try {
+                setProxyOverrideSync(null, new String[] {bypassRule});
+                Assert.fail("No exception for invalid bypass rule: " + bypassRule);
+            } catch (IllegalArgumentException e) {
+                // Expected
+            }
+        }
+    }
+
+    private void setProxyOverrideSync(String[][] proxyRules, String[] bypassRules)
+            throws Exception {
+        CallbackHelper ch = new CallbackHelper();
+        int callCount = ch.getCallCount();
+        String result = ThreadUtils.runOnUiThreadBlocking(() -> {
+            return mAwProxyController.setProxyOverride(proxyRules, bypassRules, new Runnable() {
+                @Override
+                public void run() {
+                    ch.notifyCalled();
+                }
+            }, new SynchronousExecutor());
+        });
+        if (!result.isEmpty()) {
+            throw new IllegalArgumentException(result);
+        }
+        ch.waitForCallback(callCount);
+    }
+
+    private void clearProxyOverrideSync() throws Exception {
+        CallbackHelper ch = new CallbackHelper();
+        int callCount = ch.getCallCount();
+        String result = ThreadUtils.runOnUiThreadBlocking(() -> {
+            return mAwProxyController.clearProxyOverride(new Runnable() {
+                @Override
+                public void run() {
+                    ch.notifyCalled();
+                }
+            }, new SynchronousExecutor());
+        });
+        if (!result.isEmpty()) {
+            throw new IllegalArgumentException(result);
+        }
+        ch.waitForCallback(callCount);
+    }
+
+    static class SynchronousExecutor implements Executor {
+        @Override
+        public void execute(Runnable r) {
+            r.run();
+        }
+    }
+}
diff --git a/android_webview/test/BUILD.gn b/android_webview/test/BUILD.gn
index ff2678b..f94feb0 100644
--- a/android_webview/test/BUILD.gn
+++ b/android_webview/test/BUILD.gn
@@ -212,6 +212,7 @@
     "../javatests/src/org/chromium/android_webview/test/AwLegacyQuirksTest.java",
     "../javatests/src/org/chromium/android_webview/test/AwNetworkConfigurationTest.java",
     "../javatests/src/org/chromium/android_webview/test/AwPermissionManagerTest.java",
+    "../javatests/src/org/chromium/android_webview/test/AwProxyControllerTest.java",
     "../javatests/src/org/chromium/android_webview/test/AwQuotaManagerBridgeTest.java",
     "../javatests/src/org/chromium/android_webview/test/AwScrollOffsetManagerTest.java",
     "../javatests/src/org/chromium/android_webview/test/AwSecondBrowserProcessTest.java",
diff --git a/testing/buildbot/filters/mojo.fyi.network_webview_instrumentation_test_apk.filter b/testing/buildbot/filters/mojo.fyi.network_webview_instrumentation_test_apk.filter
index e67feb3..82df1aa 100644
--- a/testing/buildbot/filters/mojo.fyi.network_webview_instrumentation_test_apk.filter
+++ b/testing/buildbot/filters/mojo.fyi.network_webview_instrumentation_test_apk.filter
@@ -134,3 +134,11 @@
 # https://crbug.com/893585
 -org.chromium.android_webview.test.WebViewWebVrTest.testWebVrNotFunctional
 
+
+# https://crbug.com/902658
+-org.chromium.android_webview.test.AwProxyControllerTest.testProxyOverride
+-org.chromium.android_webview.test.AwProxyControllerTest.testProxyOverrideLocalhost
+-org.chromium.android_webview.test.AwProxyControllerTest.testCallbacks
+-org.chromium.android_webview.test.AwProxyControllerTest.testValidInput
+-org.chromium.android_webview.test.AwProxyControllerTest.testInvalidProxyUrls
+-org.chromium.android_webview.test.AwProxyControllerTest.testInvalidBypassRules