Bug 1407384 - P2: test for suspending the channel in DoAuthRetry r=mayhemer
authorKershaw Chang <kershaw@mozilla.com>
Mon, 14 Jan 2019 16:02:58 +0000
changeset 511052 18abdcc812a606114c3b761c5134699bd385c703
parent 511051 7e8129336381f8bf33ecd89558b29effe9c3d864
child 511053 f5b0ec66117d2fe16c7729cee9362831edb1a1ca
push id10547
push userffxbld-merge
push dateMon, 21 Jan 2019 13:03:58 +0000
treeherdermozilla-beta@24ec1916bffe [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmayhemer
bugs1407384
milestone66.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1407384 - P2: test for suspending the channel in DoAuthRetry r=mayhemer Test steps: 1. Register the observers for http-on-modify-request and http-on-before-connect. 2. There are 3 cases to be tested. - suspend in http-on-modify-request - suspend in http-on-before-connect - suspend in both observers 3. See if the channel can be resumed correctly. Differential Revision: https://phabricator.services.mozilla.com/D11899
netwerk/test/unit/test_suspend_channel_on_authRetry.js
netwerk/test/unit/xpcshell.ini
new file mode 100644
--- /dev/null
+++ b/netwerk/test/unit/test_suspend_channel_on_authRetry.js
@@ -0,0 +1,274 @@
+// This file tests async handling of a channel suspended in DoAuthRetry
+// notifying http-on-modify-request and http-on-before-connect observers.
+
+var CC = Components.Constructor;
+
+ChromeUtils.import("resource://testing-common/httpd.js");
+ChromeUtils.import("resource://gre/modules/NetUtil.jsm");
+
+// Turn off the authentication dialog blocking for this test.
+var prefs = Cc["@mozilla.org/preferences-service;1"].
+              getService(Ci.nsIPrefBranch);
+
+XPCOMUtils.defineLazyGetter(this, "URL", function() {
+  return "http://localhost:" + httpserv.identity.primaryPort;
+});
+
+XPCOMUtils.defineLazyGetter(this, "PORT", function() {
+  return httpserv.identity.primaryPort;
+});
+
+var obs = Cc["@mozilla.org/observer-service;1"]
+            .getService(Ci.nsIObserverService);
+
+
+var requestObserver = null;
+
+function AuthPrompt()
+{}
+
+AuthPrompt.prototype = {
+  user: "guest",
+  pass: "guest",
+
+  QueryInterface: function authprompt_qi(iid) {
+    if (iid.equals(Ci.nsISupports) ||
+        iid.equals(Ci.nsIAuthPrompt))
+      return this;
+    throw Cr.NS_ERROR_NO_INTERFACE;
+  },
+
+  prompt: function ap1_prompt(title, text, realm, save, defaultText, result) {
+    do_throw("unexpected prompt call");
+  },
+
+  promptUsernameAndPassword:
+    function promptUP(title, text, realm, savePW, user, pw)
+  {
+    user.value = this.user;
+    pw.value = this.pass;
+
+    obs.addObserver(requestObserver, "http-on-before-connect");
+    obs.addObserver(requestObserver, "http-on-modify-request");
+    return true;
+  },
+
+  promptPassword: function promptPW(title, text, realm, save, pwd) {
+    do_throw("unexpected promptPassword call");
+  }
+
+};
+
+function requestListenerObserver(suspendOnBeforeConnect, suspendOnModifyRequest)
+{
+  this.suspendOnModifyRequest = suspendOnModifyRequest;
+  this.suspendOnBeforeConnect = suspendOnBeforeConnect;
+}
+
+requestListenerObserver.prototype = {
+  suspendOnModifyRequest: false,
+  suspendOnBeforeConnect: false,
+  gotOnBeforeConnect: false,
+  resumeOnBeforeConnect: false,
+  gotOnModifyRequest: false,
+  resumeOnModifyRequest: false,
+  QueryInterface: function queryinterface(iid) {
+    if (iid.equals(Ci.nsISupports) ||
+        iid.equals(Ci.nsIObserver))
+      return this;
+    throw Cr.NS_ERROR_NO_INTERFACE;
+  },
+
+  observe: function(subject, topic, data) {
+    if (topic === "http-on-before-connect" &&
+        subject instanceof Ci.nsIHttpChannel) {
+      if (this.suspendOnBeforeConnect) {
+        var chan = subject.QueryInterface(Ci.nsIHttpChannel);
+        executeSoon(() => {
+          this.resumeOnBeforeConnect = true;
+          chan.resume();
+        });
+        this.gotOnBeforeConnect = true;
+        chan.suspend();
+      }
+    }
+    else if (topic === "http-on-modify-request" &&
+        subject instanceof Ci.nsIHttpChannel) {
+      if (this.suspendOnModifyRequest) {
+        var chan = subject.QueryInterface(Ci.nsIHttpChannel);
+        executeSoon(() => {
+          this.resumeOnModifyRequest = true;
+          chan.resume();
+        });
+        this.gotOnModifyRequest = true;
+        chan.suspend();
+      }
+    }
+  }
+};
+
+function Requestor() {};
+
+Requestor.prototype = {
+  QueryInterface: function requestor_qi(iid) {
+    if (iid.equals(Ci.nsISupports) ||
+        iid.equals(Ci.nsIInterfaceRequestor))
+      return this;
+    throw Cr.NS_ERROR_NO_INTERFACE;
+  },
+
+  getInterface: function requestor_gi(iid) {
+    if (iid.equals(Ci.nsIAuthPrompt)) {
+      // Allow the prompt to store state by caching it here
+      if (!this.prompt)
+        this.prompt = new AuthPrompt();
+      return this.prompt;
+    }
+
+    throw Cr.NS_ERROR_NO_INTERFACE;
+  },
+
+  prompt: null
+};
+
+var listener = {
+  expectedCode: -1, // Uninitialized
+
+  onStartRequest: function test_onStartR(request, ctx) {
+    try {
+      if (!Components.isSuccessCode(request.status))
+        do_throw("Channel should have a success code!");
+
+      if (!(request instanceof Ci.nsIHttpChannel))
+        do_throw("Expecting an HTTP channel");
+
+      Assert.equal(request.responseStatus, this.expectedCode);
+      // The request should be succeeded iff we expect 200
+      Assert.equal(request.requestSucceeded, this.expectedCode == 200);
+
+    } catch (e) {
+      do_throw("Unexpected exception: " + e);
+    }
+    throw Cr.NS_ERROR_ABORT;
+  },
+
+  onDataAvailable: function test_ODA() {
+    do_throw("Should not get any data!");
+  },
+
+  onStopRequest: function test_onStopR(request, ctx, status) {
+    Assert.equal(status, Cr.NS_ERROR_ABORT);
+    if (requestObserver.suspendOnBeforeConnect) {
+      Assert.ok(requestObserver.gotOnBeforeConnect && requestObserver.resumeOnBeforeConnect);
+    }
+    if (requestObserver.suspendOnModifyRequest) {
+      Assert.ok(requestObserver.gotOnModifyRequest && requestObserver.resumeOnModifyRequest);
+    }
+    obs.removeObserver(requestObserver, "http-on-before-connect");
+    obs.removeObserver(requestObserver, "http-on-modify-request");
+    moveToNextTest();
+  }
+};
+
+function makeChan(url, loadingUrl) {
+  var ios = Cc["@mozilla.org/network/io-service;1"].
+              getService(Ci.nsIIOService);
+  var ssm = Cc["@mozilla.org/scriptsecuritymanager;1"]
+              .getService(Ci.nsIScriptSecurityManager);
+  var principal = ssm.createCodebasePrincipal(ios.newURI(loadingUrl), {});
+  return NetUtil.newChannel(
+    { uri: url, loadingPrincipal: principal,
+      securityFlags: Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
+      contentPolicyType: Ci.nsIContentPolicy.TYPE_OTHER
+    });
+}
+
+var tests = [test_suspend_on_before_connect,
+             test_suspend_on_modify_request,
+             test_suspend_all];
+
+var current_test = 0;
+
+var httpserv = null;
+
+function moveToNextTest() {
+  if (current_test < (tests.length - 1)) {
+    // First, gotta clear the auth cache
+    Cc["@mozilla.org/network/http-auth-manager;1"]
+      .getService(Ci.nsIHttpAuthManager)
+      .clearAll();
+
+    current_test++;
+    tests[current_test]();
+  } else {
+    do_test_pending();
+    httpserv.stop(do_test_finished);
+  }
+
+  do_test_finished();
+}
+
+function run_test() {
+  httpserv = new HttpServer();
+
+  httpserv.registerPathHandler("/auth", authHandler);
+
+  httpserv.start(-1);
+
+  tests[0]();
+}
+
+function test_suspend_on_auth(suspendOnBeforeConnect, suspendOnModifyRequest) {
+  var chan = makeChan(URL + "/auth", URL);
+  requestObserver =
+    new requestListenerObserver(suspendOnBeforeConnect, suspendOnModifyRequest);
+  chan.notificationCallbacks = new Requestor();
+  listener.expectedCode = 200; // OK
+  chan.asyncOpen2(listener);
+
+  do_test_pending();
+}
+
+function test_suspend_on_before_connect()
+{
+  test_suspend_on_auth(true, false);
+}
+
+function test_suspend_on_modify_request()
+{
+  test_suspend_on_auth(false, true);
+}
+
+function test_suspend_all()
+{
+  test_suspend_on_auth(true, true);
+}
+
+
+// PATH HANDLERS
+
+// /auth
+function authHandler(metadata, response) {
+  // btoa("guest:guest"), but that function is not available here
+  var expectedHeader = "Basic Z3Vlc3Q6Z3Vlc3Q=";
+
+  var body;
+  if (metadata.hasHeader("Authorization") &&
+      metadata.getHeader("Authorization") == expectedHeader)
+  {
+    response.setStatusLine(metadata.httpVersion, 200, "OK, authorized");
+    response.setHeader("WWW-Authenticate", 'Basic realm="secret"', false);
+
+    body = "success";
+  }
+  else
+  {
+    // didn't know guest:guest, failure
+    response.setStatusLine(metadata.httpVersion, 401, "Unauthorized");
+    response.setHeader("WWW-Authenticate", 'Basic realm="secret"', false);
+
+    body = "failed";
+  }
+
+  response.bodyOutputStream.write(body, body.length);
+}
--- a/netwerk/test/unit/xpcshell.ini
+++ b/netwerk/test/unit/xpcshell.ini
@@ -420,8 +420,9 @@ skip-if = os == "android"
 [test_captive_portal_service.js]
 skip-if = os == "android" # CP service is disabled on Android
 run-sequentially = node server exceptions dont replay well
 [test_esni_dns_fetch.js]
 # http2-using tests require node available
 skip-if = os == "android"
 [test_network_connectivity_service.js]
 skip-if = os == "android" # DNSv6 issues on android
+[test_suspend_channel_on_authRetry.js]