Bug 977502 (part 2) - add tests with mock http responses simulating errors. r=ckarlof
authorMark Hammond <mhammond@skippinet.com.au>
Fri, 07 Mar 2014 15:41:32 +1100
changeset 189669 00825a70f56f22b32056baecb2255a239cd33c18
parent 189668 6e4f7fc7e1f305003571a40cc3e40e0291fb04c9
child 189670 409750b6699de0e16a874b3763d51bd84c049016
push id3503
push userraliiev@mozilla.com
push dateMon, 28 Apr 2014 18:51:11 +0000
treeherdermozilla-beta@c95ac01e332e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersckarlof
bugs977502
milestone30.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 977502 (part 2) - add tests with mock http responses simulating errors. r=ckarlof
services/common/hawkclient.js
services/common/tokenserverclient.js
services/sync/tests/unit/test_browserid_identity.js
--- a/services/common/hawkclient.js
+++ b/services/common/hawkclient.js
@@ -194,18 +194,23 @@ this.HawkClient.prototype = {
       deferred.resolve(this.response.body);
     };
 
     let extra = {
       now: this.now(),
       localtimeOffsetMsec: this.localtimeOffsetMsec,
     };
 
-    let request = new HAWKAuthenticatedRESTRequest(uri, credentials, extra);
+    let request = this.newHAWKAuthenticatedRESTRequest(uri, credentials, extra);
     if (method == "post" || method == "put") {
       request[method](payloadObj, onComplete);
     } else {
       request[method](onComplete);
     }
 
     return deferred.promise;
-  }
+  },
+
+  // override points for testing.
+  newHAWKAuthenticatedRESTRequest: function(uri, credentials, extra) {
+    return new HAWKAuthenticatedRESTRequest(uri, credentials, extra);
+  },
 }
--- a/services/common/tokenserverclient.js
+++ b/services/common/tokenserverclient.js
@@ -240,17 +240,17 @@ TokenServerClient.prototype = {
     }
 
     if (!cb) {
       throw new TokenServerClientError("cb argument is not valid.");
     }
 
     this._log.debug("Beginning BID assertion exchange: " + url);
 
-    let req = new RESTRequest(url);
+    let req = this.newRESTRequest(url);
     req.setHeader("Accept", "application/json");
     req.setHeader("Authorization", "BrowserID " + assertion);
 
     for (let header in addHeaders) {
       req.setHeader(header, addHeaders[header]);
     }
 
     let client = this;
@@ -398,10 +398,15 @@ TokenServerClient.prototype = {
     this._log.debug("Successful token response: " + result.id);
     cb(null, {
       id:       result.id,
       key:      result.key,
       endpoint: result.api_endpoint,
       uid:      result.uid,
       duration: result.duration,
     });
+  },
+
+  // override points for testing.
+  newRESTRequest: function(url) {
+    return new RESTRequest(url);
   }
 };
--- a/services/sync/tests/unit/test_browserid_identity.js
+++ b/services/sync/tests/unit/test_browserid_identity.js
@@ -7,16 +7,19 @@ Cu.import("resource://services-sync/rest
 Cu.import("resource://services-sync/util.js");
 Cu.import("resource://services-common/utils.js");
 Cu.import("resource://services-crypto/utils.js");
 Cu.import("resource://testing-common/services/sync/utils.js");
 Cu.import("resource://services-common/hawkclient.js");
 Cu.import("resource://gre/modules/FxAccounts.jsm");
 Cu.import("resource://gre/modules/FxAccountsClient.jsm");
 Cu.import("resource://gre/modules/FxAccountsCommon.js");
+Cu.import("resource://services-common/tokenserverclient.js");
+Cu.import("resource://services-sync/status.js");
+Cu.import("resource://services-sync/constants.js");
 
 const SECOND_MS = 1000;
 const MINUTE_MS = SECOND_MS * 60;
 const HOUR_MS = MINUTE_MS * 60;
 
 let identityConfig = makeIdentityConfig();
 let browseridManager = new BrowserIDManager();
 configureFxAccountIdentity(browseridManager, identityConfig);
@@ -339,19 +342,152 @@ add_test(function test_computeXClientSta
 
   let bidUser = new BrowserIDManager();
   let header = bidUser._computeXClientState(kB);
 
   do_check_eq(header, "6ae94683571c7a7c54dab4700aa3995f");
   run_next_test();
 });
 
+add_task(function test_getTokenErrors() {
+  _("BrowserIDManager correctly handles various failures to get a token.");
+
+  _("Arrange for a 401 - Sync should reflect an auth error.");
+  yield initializeIdentityWithTokenServerFailure({
+    status: 401,
+    headers: {"content-type": "application/json"},
+    body: JSON.stringify({}),
+  });
+  Assert.equal(Status.login, LOGIN_FAILED_LOGIN_REJECTED, "login was rejected");
+
+  // XXX - other interesting responses to return?
+
+  // And for good measure, some totally "unexpected" errors - we generally
+  // assume these problems are going to magically go away at some point.
+  _("Arrange for an empty body with a 200 response - should reflect a network error.");
+  yield initializeIdentityWithTokenServerFailure({
+    status: 200,
+    headers: [],
+    body: "",
+  });
+  Assert.equal(Status.login, LOGIN_FAILED_NETWORK_ERROR, "login state is LOGIN_FAILED_NETWORK_ERROR");
+});
+
+add_task(function test_getHAWKErrors() {
+  _("BrowserIDManager correctly handles various HAWK failures.");
+
+  _("Arrange for a 401 - Sync should reflect an auth error.");
+  yield initializeIdentityWithHAWKFailure({
+    status: 401,
+    headers: {"content-type": "application/json"},
+    body: JSON.stringify({}),
+  });
+  Assert.equal(Status.login, LOGIN_FAILED_LOGIN_REJECTED, "login was rejected");
+
+  // XXX - other interesting responses to return?
+
+  // And for good measure, some totally "unexpected" errors - we generally
+  // assume these problems are going to magically go away at some point.
+  _("Arrange for an empty body with a 200 response - should reflect a network error.");
+  yield initializeIdentityWithHAWKFailure({
+    status: 200,
+    headers: [],
+    body: "",
+  });
+  Assert.equal(Status.login, LOGIN_FAILED_NETWORK_ERROR, "login state is LOGIN_FAILED_NETWORK_ERROR");
+});
+
 // End of tests
 // Utility functions follow
 
+// Create a new browserid_identity object and initialize it with a
+// mocked TokenServerClient which always gets the specified response.
+function* initializeIdentityWithTokenServerFailure(response) {
+  // First create a mock "request" object that well' hack into the token server.
+  // A log for it
+  let requestLog = Log.repository.getLogger("testing.mock-rest");
+  if (!requestLog.appenders.length) { // might as well see what it says :)
+    requestLog.addAppender(new Log.DumpAppender());
+    requestLog.level = Log.Level.Trace;
+  }
+
+  // A mock request object.
+  function MockRESTRequest(url) {};
+  MockRESTRequest.prototype = {
+    _log: requestLog,
+    setHeader: function() {},
+    get: function(callback) {
+      this.response = response;
+      callback.call(this);
+    }
+  }
+  // The mocked TokenServer client which will get the response.
+  function MockTSC() { }
+  MockTSC.prototype = new TokenServerClient();
+  MockTSC.prototype.constructor = MockTSC;
+  MockTSC.prototype.newRESTRequest = function(url) {
+    return new MockRESTRequest(url);
+  }
+  // tie it all together.
+  let mockTSC = new MockTSC()
+  configureFxAccountIdentity(browseridManager);
+  browseridManager._tokenServerClient = mockTSC;
+
+  yield browseridManager.initializeWithCurrentIdentity();
+  try {
+    yield browseridManager.whenReadyToAuthenticate.promise;
+    Assert.ok(false, "expecting this promise to resolve with an error");
+  } catch (ex) {}
+}
+
+
+// Create a new browserid_identity object and initialize it with a
+// hawk mock that simulates a failure.
+// A token server mock will be used that doesn't hit a server, so we move
+// directly to a hawk request.
+function* initializeIdentityWithHAWKFailure(response) {
+  // A mock request object.
+  function MockRESTRequest() {};
+  MockRESTRequest.prototype = {
+    setHeader: function() {},
+    post: function(data, callback) {
+      this.response = response;
+      callback.call(this);
+    }
+  }
+
+  // The hawk client.
+  function MockedHawkClient() {}
+  MockedHawkClient.prototype = new HawkClient();
+  MockedHawkClient.prototype.constructor = MockedHawkClient;
+  MockedHawkClient.prototype.newHAWKAuthenticatedRESTRequest = function(uri, credentials, extra) {
+    return new MockRESTRequest();
+  }
+
+  // tie it all together - configureFxAccountIdentity isn't useful here :(
+  let fxaClient = new MockFxAccountsClient();
+  fxaClient.hawk = new MockedHawkClient();
+  let config = makeIdentityConfig();
+  let internal = {
+    fxAccountsClient: fxaClient,
+  }
+  let fxa = new FxAccounts(internal);
+  fxa.internal.currentAccountState.signedInUser = {
+      accountData: config.fxaccount.user,
+  };
+
+  browseridManager._fxaService = fxa;
+  yield browseridManager.initializeWithCurrentIdentity();
+  try {
+    yield browseridManager.whenReadyToAuthenticate.promise;
+    Assert.ok(false, "expecting this promise to resolve with an error");
+  } catch (ex) {}
+}
+
+
 function getTimestamp(hawkAuthHeader) {
   return parseInt(/ts="(\d+)"/.exec(hawkAuthHeader)[1], 10) * SECOND_MS;
 }
 
 function getTimestampDelta(hawkAuthHeader, now=Date.now()) {
   return Math.abs(getTimestamp(hawkAuthHeader) - now);
 }