Bug 1417255 - Use WebMockServer to test https. draft
authorNevin Chen <cnevinchen@gmail.com>
Sun, 17 Dec 2017 21:26:22 -0800
changeset 712633 e2875739fe0b9bf17e20d4b0f7c2f882f9d168e0
parent 711893 f7759addb79cf8c651d3487c290f1553c0e213f3
child 744090 f1e3c9e2ebb6b8ed6c2c8da45e7a2388f99f2a91
push id93377
push userbmo:cnevinchen@gmail.com
push dateMon, 18 Dec 2017 05:27:00 +0000
bugs1417255
milestone59.0a1
Bug 1417255 - Use WebMockServer to test https. The problem with this appraach is that certificate is self-signed. So we'll still get https error. The only way I know to test a CA certified certificate is hosting a test server with real ip/domain. I need to find other ways to test this scenario. MozReview-Commit-ID: BsrCAXVUpnG
mobile/android/app/build.gradle
mobile/android/app/src/androidTest/java/org/mozilla/gecko/EspressoBrowserAppTest.java
mobile/android/base/java/org/mozilla/gecko/pwa/PwaUtils.java
mobile/android/base/java/org/mozilla/gecko/toolbar/BrowserToolbar.java
--- a/mobile/android/app/build.gradle
+++ b/mobile/android/app/build.gradle
@@ -326,16 +326,17 @@ dependencies {
 
     testCompile 'junit:junit:4.12'
     testCompile 'org.robolectric:robolectric:3.1.2'
     testCompile 'org.simpleframework:simple-http:6.0.1'
     testCompile 'org.mockito:mockito-core:1.10.19'
 
     // Including the Robotium JAR directly can cause issues with dexing.
     androidTestCompile 'com.jayway.android.robotium:robotium-solo:5.5.4'
+    androidTestCompile 'com.squareup.okhttp3:mockwebserver:3.9.1'
 }
 
 // TODO: (bug 1261486): This impl is not robust -
 // we just wanted to land something.
 task checkstyle(type: Checkstyle) {
     configFile file("checkstyle.xml")
     // TODO: should use sourceSets from project instead of hard-coded str.
     source = ['../base/java/','../geckoview/src/main/java/']
--- a/mobile/android/app/src/androidTest/java/org/mozilla/gecko/EspressoBrowserAppTest.java
+++ b/mobile/android/app/src/androidTest/java/org/mozilla/gecko/EspressoBrowserAppTest.java
@@ -1,24 +1,46 @@
 package org.mozilla.gecko;
 
+import android.os.Environment;
 import android.support.annotation.Keep;
+import android.support.test.espresso.IdlingPolicies;
 import android.support.test.espresso.IdlingRegistry;
 import android.support.test.espresso.idling.CountingIdlingResource;
 import android.support.test.espresso.matcher.ViewMatchers;
 import android.support.test.rule.ActivityTestRule;
 import android.support.test.runner.AndroidJUnit4;
+import android.util.Log;
 
+
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mozilla.gecko.icons.storage.DiskStorage;
+import org.mozilla.gecko.util.FileUtils;
 import org.mozilla.gecko.util.GeckoBundle;
 
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.KeyStore;
+import java.util.concurrent.TimeUnit;
+
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLSocketFactory;
+import javax.net.ssl.TrustManagerFactory;
+
+import okhttp3.mockwebserver.MockResponse;
+import okhttp3.mockwebserver.MockWebServer;
+import okio.Buffer;
+
 import static android.support.test.espresso.Espresso.onView;
 import static android.support.test.espresso.action.ViewActions.click;
 import static android.support.test.espresso.action.ViewActions.pressImeActionButton;
 import static android.support.test.espresso.action.ViewActions.replaceText;
 import static android.support.test.espresso.assertion.ViewAssertions.matches;
 import static android.support.test.espresso.matcher.ViewMatchers.withEffectiveVisibility;
 import static android.support.test.espresso.matcher.ViewMatchers.withId;
 import static android.support.test.espresso.matcher.ViewMatchers.withParent;
@@ -27,51 +49,62 @@ import static org.hamcrest.Matchers.allO
 import static org.hamcrest.Matchers.is;
 import static org.mozilla.gecko.toolbar.PageActionLayout.PageAction.UUID_PAGE_ACTION_PWA;
 
 @EspressoOnly
 @Keep
 @RunWith(AndroidJUnit4.class)
 public class EspressoBrowserAppTest {
 
-    private static final String WEB_FORM_URL = "https://pwa.rocks";
-    private final String STATIC_HTML = "index.html";
+
+    MockWebServer server;
+    private static final String pwa_real_site = "https://pwa.rocks";
+    private static final String TAG = "EspressoBrowserAppTest";
+
+    final String pwa_index = "pwa_test_site/index.html";
+    final String pwa_manifest = "pwa_test_site/pwa.webmanifest";
+    final String pwa_icon = "pwa_test_site/images/icon.png";
 
     @Rule
     public ActivityTestRule<BrowserApp> mActivityRule =
             new ActivityTestRule(BrowserApp.class);
     CountingIdlingResource countingResource;
     private String url = null;
 
     @Before
     public void setup() {
-        clearData();
+
         countingResource = new CountingIdlingResource("PWA");
 
-        url = adjustUrl(STATIC_HTML);
+        IdlingPolicies.setIdlingResourceTimeout(120, TimeUnit.MINUTES);
+
+        prepareWebServer();
+        url = adjustUrl(pwa_manifest);
         // To prove that the test fails, omit this call:
         IdlingRegistry.getInstance().register(countingResource);
     }
 
     private String adjustUrl(String path) {
-        return "resource://android/assets/pwa_test_site/" + path;
+        return "resource://android/assets/" + path;
 
     }
 
     @Test
     public void loadUrl() {
+//        clearData();
         // Enter url
         onView(withId(R.id.url_bar_title_scroll_view)).perform(click());
 //        final EditText titleInput = (EditText) mActivityRule.getActivity().findViewById(R.id.url_edit_text);
 //        getInstrumentation().runOnMainSync(new Runnable() {
 //            public void run() {
 //                titleInput.setText("Engineer");
 //            }
 //        });
 
+        url = "https://" + server.getHostName() + ":" + server.getPort() + "/" + pwa_index;
         onView(withId(R.id.url_edit_text)).perform(click(), replaceText(url), pressImeActionButton());
 
         mActivityRule.getActivity().setIdlingResource(countingResource);
         onView(withId(R.id.page_action_layout)).check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)));
 
         onView(allOf(
                 withParent(withId(R.id.page_action_layout)),
                 withTagValue(is((Object) UUID_PAGE_ACTION_PWA))))
@@ -93,20 +126,87 @@ public class EspressoBrowserAppTest {
             // remember their check states. The key names are private.data.X,
             // where X is a string from Gecko sanitization. This prefix is
             // removed here so we can send the values to Gecko, which then does
             // the sanitization for each key.
             final String key = value.substring(PREF_KEY_PREFIX.length());
 
             if (values.equals("private.data.offlineApps")) {
                 // Remove all icons from storage if removing "Offline website data" was selected.
-                DiskStorage.get(mActivityRule.getActivity()).evictAll();
+//                DiskStorage.get(mActivityRule.getActivity()).evictAll();
             } else {
                 data.putBoolean(key, true);
             }
         }
 
 
         // clear private data in gecko
         EventDispatcher.getInstance().dispatch("Sanitize:ClearData", data);
 
     }
+
+    public void prepareWebServer() {
+        // https://stackoverflow.com/questions/16774764/bouncy-castle-keystore-bks-java-io-ioexception-wrong-version-of-key-store
+        // https://stackoverflow.com/questions/21169248/android-java-io-ioexception-wrong-version-of-key-store
+        // https://github.com/square/okhttp/tree/master/mockwebserver
+        // https://github.com/square/okhttp/issues/2980
+        try {
+            final InputStream inputStream = mActivityRule.getActivity().getAssets().open("mockwebserver.keystore");
+            char[] serverKeyStorePassword = "mozilla".toCharArray();
+            KeyStore serverKeyStore = KeyStore.getInstance(KeyStore.getDefaultType());
+            serverKeyStore.load(inputStream, serverKeyStorePassword);
+
+            String kmfAlgoritm = KeyManagerFactory.getDefaultAlgorithm();
+            KeyManagerFactory kmf = KeyManagerFactory.getInstance(kmfAlgoritm);
+            kmf.init(serverKeyStore, serverKeyStorePassword);
+
+            TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(kmfAlgoritm);
+            trustManagerFactory.init(serverKeyStore);
+
+            SSLContext sslContext = SSLContext.getInstance("SSL");
+            sslContext.init(kmf.getKeyManagers(), trustManagerFactory.getTrustManagers(), null);
+            SSLSocketFactory sf = sslContext.getSocketFactory();
+
+            // Create a MockWebServer. These are lean enough that you can create a new
+            // instance for every unit test.
+            server = new MockWebServer();
+            server.useHttps(sf, false);
+
+
+            Buffer index = getBinaryFileAsBuffer(pwa_index);
+            server.enqueue(new MockResponse().setResponseCode(200).setBody(index));
+
+            Buffer manifest = getBinaryFileAsBuffer(pwa_manifest);
+            server.enqueue(new MockResponse().setResponseCode(200).setBody(manifest));
+
+            Buffer icon = getBinaryFileAsBuffer(pwa_icon);
+            server.enqueue(new MockResponse().setResponseCode(200).addHeader("Content-Type:image/png").setBody(icon));
+
+            // Start the server.
+            server.start();
+        } catch (Exception e) {
+            Log.e(TAG, "prepareWebServer error:" + e);
+        }
+
+
+    }
+
+    @After
+    public void tearDown() throws IOException {
+        if (server != null) {
+            server.shutdown();
+        }
+
+    }
+
+    //Buffer wrap a binary file to return with a mock.
+    public Buffer getBinaryFileAsBuffer(String path) throws IOException {
+        InputStream is = mActivityRule.getActivity().getAssets().open(path);
+        byte[] fileData = new byte[is.available()];
+        is.read(fileData);
+        is.close();
+        Buffer buf = new Buffer();
+        buf.write(fileData);
+        Log.d(TAG, "BUFFER SIZE FOR " + path + " IS:" + buf.size());
+        return buf;
+    }
+
 }
--- a/mobile/android/base/java/org/mozilla/gecko/pwa/PwaUtils.java
+++ b/mobile/android/base/java/org/mozilla/gecko/pwa/PwaUtils.java
@@ -25,17 +25,17 @@ public class PwaUtils {
     @CheckResult
     public static boolean shouldAddPwaShortcut(Tab tab) {
         final boolean secure = tab.getSiteIdentity().isSecure();
         // This tab is safe for pwa only when the site is absolutely secure.
         // so no exception is allowed
         final boolean exception = tab.getSiteIdentity().isSecurityException();
 
         // There's no https/mixed content check for espresso.... for now.
-        if (BuildConfig.FLAVOR.contains("espresso")){
+        if (BuildConfig.FLAVOR_audience.equals("espresso")){
             return !tab.isPrivate();
         } else {
             return !tab.isPrivate() && secure && !exception;
         }
 
 
     }
 }
--- a/mobile/android/base/java/org/mozilla/gecko/toolbar/BrowserToolbar.java
+++ b/mobile/android/base/java/org/mozilla/gecko/toolbar/BrowserToolbar.java
@@ -548,17 +548,17 @@ public abstract class BrowserToolbar ext
             updateProgressVisibility(selectedTab, selectedTab.getLoadProgress());
         }
     }
 
     private void updateProgressVisibility(Tab selectedTab, int progress) {
         // https://problemcode.blogspot.com/2015/05/google-espresso-javalangruntimeexceptio.html
         // I hide the progress bar for expresso test otherwise I'll get this error
         // https://android.googlesource.com/platform/frameworks/testing/+/android-support-test/runner/src/main/java/android/support/test/runner/MonitoringInstrumentation.java#358
-        if (BuildConfig.FLAVOR.contains("espresso")){
+        if (BuildConfig.FLAVOR_audience.equals("espresso")){
             progressBar.setVisibility(View.GONE);
             return;
         }
         if (!isEditing() && selectedTab.getState() == Tab.STATE_LOADING) {
             progressBar.setProgress(progress);
             progressBar.setPrivateMode(selectedTab.isPrivate());
             progressBar.setVisibility(View.VISIBLE);
             progressBar.pinDynamicToolbar();