Bug 969892 - Fixes and improvements to FxAccounts test suite; r=ttaubert
authorJed Parsons <jedp@mozilla.com>
Tue, 18 Feb 2014 09:47:52 -0800
changeset 191031 945ddca04d49fb3989885833e36c1d856fed4e6b
parent 191030 da039c0c66bc0a1fa5f3f78d45cce390f1e48543
child 191032 b1150afb0f41aaba748b0ea7acb52c0bacbb1fc0
push id474
push userasasaki@mozilla.com
push dateMon, 02 Jun 2014 21:01:02 +0000
treeherdermozilla-release@967f4cf1b31c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersttaubert
bugs969892
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 969892 - Fixes and improvements to FxAccounts test suite; r=ttaubert
services/common/tests/unit/test_hawkclient.js
services/fxaccounts/FxAccounts.jsm
services/fxaccounts/tests/xpcshell/test_client.js
--- a/services/common/tests/unit/test_hawkclient.js
+++ b/services/common/tests/unit/test_hawkclient.js
@@ -337,16 +337,17 @@ add_task(function test_multiple_401_retr
   };
 
   // We begin with no offset
   do_check_eq(client.localtimeOffsetMsec, 0);
 
   // Request will have bad timestamp; client will retry once
   try {
     yield client.request("/maybe", method, credentials);
+    do_throw("Expected an error");
   } catch (err) {
     do_check_eq(err.code, 401);
   }
   do_check_eq(attempts, 2);
 
   yield deferredStop(server);
 });
 
--- a/services/fxaccounts/FxAccounts.jsm
+++ b/services/fxaccounts/FxAccounts.jsm
@@ -62,32 +62,32 @@ let publicProperties = [
 AccountState = function(fxaInternal) {
   this.fxaInternal = fxaInternal;
 };
 
 AccountState.prototype = {
   cert: null,
   keyPair: null,
   signedInUser: null,
-  whenVerifiedPromise: null,
-  whenKeysReadyPromise: null,
+  whenVerifiedDeferred: null,
+  whenKeysReadyDeferred: null,
 
   get isCurrent() this.fxaInternal && this.fxaInternal.currentAccountState === this,
 
   abort: function() {
-    if (this.whenVerifiedPromise) {
-      this.whenVerifiedPromise.reject(
+    if (this.whenVerifiedDeferred) {
+      this.whenVerifiedDeferred.reject(
         new Error("Verification aborted; Another user signing in"));
-      this.whenVerifiedPromise = null;
+      this.whenVerifiedDeferred = null;
     }
 
-    if (this.whenKeysReadyPromise) {
-      this.whenKeysReadyPromise.reject(
+    if (this.whenKeysReadyDeferred) {
+      this.whenKeysReadyDeferred.reject(
         new Error("Verification aborted; Another user signing in"));
-      this.whenKeysReadyPromise = null;
+      this.whenKeysReadyDeferred = null;
     }
     this.cert = null;
     this.keyPair = null;
     this.signedInUser = null;
     this.fxaInternal = null;
   },
 
   getUserAccountData: function() {
@@ -479,23 +479,23 @@ FxAccountsInternal.prototype = {
     let currentState = this.currentAccountState;
     return currentState.getUserAccountData().then((data) => {
       if (!data) {
         throw new Error("Can't get keys; User is not signed in");
       }
       if (data.kA && data.kB) {
         return data;
       }
-      if (!currentState.whenKeysReadyPromise) {
-        currentState.whenKeysReadyPromise = Promise.defer();
+      if (!currentState.whenKeysReadyDeferred) {
+        currentState.whenKeysReadyDeferred = Promise.defer();
         this.fetchAndUnwrapKeys(data.keyFetchToken).then(data => {
-          currentState.whenKeysReadyPromise.resolve(data);
+          currentState.whenKeysReadyDeferred.resolve(data);
         });
       }
-      return currentState.whenKeysReadyPromise.promise;
+      return currentState.whenKeysReadyDeferred.promise;
     }).then(result => currentState.resolve(result));
    },
 
   fetchAndUnwrapKeys: function(keyFetchToken) {
     log.debug("fetchAndUnwrapKeys: token: " + keyFetchToken);
     let currentState = this.currentAccountState;
     return Task.spawn(function* task() {
       // Sign out if we don't have a key fetch token.
@@ -602,21 +602,21 @@ FxAccountsInternal.prototype = {
   },
 
   whenVerified: function(data) {
     let currentState = this.currentAccountState;
     if (data.verified) {
       log.debug("already verified");
       return currentState.resolve(data);
     }
-    if (!currentState.whenVerifiedPromise) {
+    if (!currentState.whenVerifiedDeferred) {
       log.debug("whenVerified promise starts polling for verified email");
       this.pollEmailStatus(currentState, data.sessionToken, "start");
     }
-    return currentState.whenVerifiedPromise.promise.then(
+    return currentState.whenVerifiedDeferred.promise.then(
       result => currentState.resolve(result)
     );
   },
 
   notifyObservers: function(topic) {
     log.debug("Notifying observers of " + topic);
     Services.obs.notifyObservers(null, topic, null);
   },
@@ -624,53 +624,53 @@ FxAccountsInternal.prototype = {
   // XXX - pollEmailStatus should maybe be on the AccountState object?
   pollEmailStatus: function pollEmailStatus(currentState, sessionToken, why) {
     log.debug("entering pollEmailStatus: " + why);
     if (why == "start") {
       // If we were already polling, stop and start again.  This could happen
       // if the user requested the verification email to be resent while we
       // were already polling for receipt of an earlier email.
       this.pollTimeRemaining = this.POLL_SESSION;
-      if (!currentState.whenVerifiedPromise) {
-        currentState.whenVerifiedPromise = Promise.defer();
+      if (!currentState.whenVerifiedDeferred) {
+        currentState.whenVerifiedDeferred = Promise.defer();
       }
     }
 
     this.checkEmailStatus(sessionToken)
       .then((response) => {
         log.debug("checkEmailStatus -> " + JSON.stringify(response));
         if (response && response.verified) {
           // Bug 947056 - Server should be able to tell FxAccounts.jsm to back
           // off or stop polling altogether
           currentState.getUserAccountData()
             .then((data) => {
               data.verified = true;
               return currentState.setUserAccountData(data);
             })
             .then((data) => {
               // Now that the user is verified, we can proceed to fetch keys
-              if (currentState.whenVerifiedPromise) {
-                currentState.whenVerifiedPromise.resolve(data);
-                delete currentState.whenVerifiedPromise;
+              if (currentState.whenVerifiedDeferred) {
+                currentState.whenVerifiedDeferred.resolve(data);
+                delete currentState.whenVerifiedDeferred;
               }
             });
         } else {
           log.debug("polling with step = " + this.POLL_STEP);
           this.pollTimeRemaining -= this.POLL_STEP;
           log.debug("time remaining: " + this.pollTimeRemaining);
           if (this.pollTimeRemaining > 0) {
             this.currentTimer = setTimeout(() => {
               this.pollEmailStatus(currentState, sessionToken, "timer")}, this.POLL_STEP);
             log.debug("started timer " + this.currentTimer);
           } else {
-            if (currentState.whenVerifiedPromise) {
-              currentState.whenVerifiedPromise.reject(
+            if (currentState.whenVerifiedDeferred) {
+              currentState.whenVerifiedDeferred.reject(
                 new Error("User email verification timed out.")
               );
-              delete currentState.whenVerifiedPromise;
+              delete currentState.whenVerifiedDeferred;
             }
           }
         }
       });
     },
 
   // Return the URI of the remote UI flows.
   getAccountsURI: function() {
--- a/services/fxaccounts/tests/xpcshell/test_client.js
+++ b/services/fxaccounts/tests/xpcshell/test_client.js
@@ -1,40 +1,61 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
+"use strict";
+
 Cu.import("resource://gre/modules/FxAccountsClient.jsm");
 Cu.import("resource://gre/modules/Promise.jsm");
 Cu.import("resource://services-common/utils.js");
 Cu.import("resource://services-crypto/utils.js");
 
 const FAKE_SESSION_TOKEN = "a0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebf";
 
 function run_test() {
   run_next_test();
 }
 
+// https://wiki.mozilla.org/Identity/AttachedServices/KeyServerProtocol#.2Faccount.2Fkeys
+let ACCOUNT_KEYS = {
+  keyFetch:     h("8081828384858687 88898a8b8c8d8e8f"+
+                  "9091929394959697 98999a9b9c9d9e9f"),
+
+  response:     h("ee5c58845c7c9412 b11bbd20920c2fdd"+
+                  "d83c33c9cd2c2de2 d66b222613364636"+
+                  "c2c0f8cfbb7c6304 72c0bd88451342c6"+
+                  "c05b14ce342c5ad4 6ad89e84464c993c"+
+                  "3927d30230157d08 17a077eef4b20d97"+
+                  "6f7a97363faf3f06 4c003ada7d01aa70"),
+
+  kA:           h("2021222324252627 28292a2b2c2d2e2f"+
+                  "3031323334353637 38393a3b3c3d3e3f"),
+
+  wrapKB:       h("4041424344454647 48494a4b4c4d4e4f"+
+                  "5051525354555657 58595a5b5c5d5e5f"),
+};
+
+// https://github.com/mozilla/fxa-auth-server/wiki/onepw-protocol#wiki-use-session-certificatesign-etc
+let SESSION_KEYS = {
+  sessionToken: h("a0a1a2a3a4a5a6a7 a8a9aaabacadaeaf"+
+                  "b0b1b2b3b4b5b6b7 b8b9babbbcbdbebf"),
+
+  tokenID:      h("c0a29dcf46174973 da1378696e4c82ae"+
+                  "10f723cf4f4d9f75 e39f4ae3851595ab"),
+
+  reqHMACkey:   h("9d8f22998ee7f579 8b887042466b72d5"+
+                  "3e56ab0c094388bf 65831f702d2febc0"),
+};
+
 function deferredStop(server) {
   let deferred = Promise.defer();
   server.stop(deferred.resolve);
   return deferred.promise;
 }
 
-add_test(function test_hawk_credentials() {
-  let client = new FxAccountsClient();
-
-  let sessionToken = "a0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebf";
-  let result = client._deriveHawkCredentials(sessionToken, "session");
-
-  do_check_eq(result.id, "639503a218ffbb62983e9628be5cd64a0438d0ae81b2b9dadeb900a83470bc6b");
-  do_check_eq(CommonUtils.bytesAsHex(result.key), "3a0188943837ab228fe74e759566d0e4837cbcc7494157aac4da82025b2811b2");
-
-  run_next_test();
-});
-
 add_task(function test_authenticated_get_request() {
   let message = "{\"msg\": \"Great Success!\"}";
   let credentials = {
     id: "eyJleHBpcmVzIjogMTM2NTAxMDg5OC4x",
     key: "qTZf4ZFpAMpMoeSsX3zVRjiqmNs=",
     algorithm: "sha256"
   };
   let method = "GET";
@@ -89,16 +110,17 @@ add_task(function test_500_error() {
       response.bodyOutputStream.write(message, message.length);
     }
   });
 
   let client = new FxAccountsClient(server.baseURI);
 
   try {
     yield client._request("/foo", method);
+    do_throw("Expected to catch an exception");
   } catch (e) {
     do_check_eq(500, e.code);
     do_check_eq("Internal Server Error", e.message);
   }
 
   yield deferredStop(server);
 });
 
@@ -166,68 +188,75 @@ add_task(function test_signUp() {
       // https://github.com/mozilla/fxa-auth-server/wiki/onepw-protocol#wiki-test-vectors
       do_check_eq(jsonBody.email, "andré@example.org");
 
       if (!created) {
         do_check_eq(jsonBody.authPW, "247b675ffb4c46310bc87e26d712153abe5e1c90ef00a4784594f97ef54f2375");
         created = true;
 
         response.setStatusLine(request.httpVersion, 200, "OK");
-        return response.bodyOutputStream.write(creationMessage, creationMessage.length);
+        response.bodyOutputStream.write(creationMessage, creationMessage.length);
+        return;
       }
 
       // Error trying to create same account a second time
       response.setStatusLine(request.httpVersion, 400, "Bad request");
-      return response.bodyOutputStream.write(errorMessage, errorMessage.length);
+      response.bodyOutputStream.write(errorMessage, errorMessage.length);
+      return;
     },
   });
 
   let client = new FxAccountsClient(server.baseURI);
   let result = yield client.signUp('andré@example.org', 'pässwörd');
   do_check_eq("uid", result.uid);
   do_check_eq("sessionToken", result.sessionToken);
   do_check_eq("keyFetchToken", result.keyFetchToken);
 
   // Try to create account again.  Triggers error path.
   try {
     result = yield client.signUp('andré@example.org', 'pässwörd');
+    do_throw("Expected to catch an exception");
   } catch(expectedError) {
     do_check_eq(101, expectedError.errno);
   }
 
   yield deferredStop(server);
 });
 
 add_task(function test_signIn() {
   let sessionMessage = JSON.stringify({sessionToken: FAKE_SESSION_TOKEN});
   let errorMessage = JSON.stringify({code: 400, errno: 102, error: "doesn't exist"});
+
   let server = httpd_setup({
     "/account/login": function(request, response) {
       let body = CommonUtils.readBytesFromInputStream(request.bodyInputStream);
       let jsonBody = JSON.parse(body);
 
       if (jsonBody.email == "mé@example.com") {
         do_check_eq(jsonBody.authPW, "08b9d111196b8408e8ed92439da49206c8ecfbf343df0ae1ecefcd1e0174a8b6");
         response.setStatusLine(request.httpVersion, 200, "OK");
-        return response.bodyOutputStream.write(sessionMessage, sessionMessage.length);
+        response.bodyOutputStream.write(sessionMessage, sessionMessage.length);
+        return;
       }
 
       // Error trying to sign in to nonexistent account
       response.setStatusLine(request.httpVersion, 400, "Bad request");
-      return response.bodyOutputStream.write(errorMessage, errorMessage.length);
+      response.bodyOutputStream.write(errorMessage, errorMessage.length);
+      return;
     },
   });
 
   let client = new FxAccountsClient(server.baseURI);
   let result = yield client.signIn('mé@example.com', 'bigsecret');
   do_check_eq(FAKE_SESSION_TOKEN, result.sessionToken);
 
   // Trigger error path
   try {
     result = yield client.signIn("yøü@bad.example.org", "nofear");
+    do_throw("Expected to catch an exception");
   } catch(expectedError) {
     do_check_eq(102, expectedError.errno);
   }
 
   yield deferredStop(server);
 });
 
 add_task(function test_signOut() {
@@ -236,32 +265,35 @@ add_task(function test_signOut() {
   let signedOut = false;
 
   let server = httpd_setup({
     "/session/destroy": function(request, response) {
       if (!signedOut) {
         signedOut = true;
         do_check_true(request.hasHeader("Authorization"));
         response.setStatusLine(request.httpVersion, 200, "OK");
-        return response.bodyOutputStream.write(signoutMessage, signoutMessage.length);
+        response.bodyOutputStream.write(signoutMessage, signoutMessage.length);
+        return;
       }
 
       // Error trying to sign out of nonexistent account
       response.setStatusLine(request.httpVersion, 400, "Bad request");
-      return response.bodyOutputStream.write(errorMessage, errorMessage.length);
+      response.bodyOutputStream.write(errorMessage, errorMessage.length);
+      return;
     },
   });
 
   let client = new FxAccountsClient(server.baseURI);
   let result = yield client.signOut("FakeSession");
   do_check_eq(typeof result, "object");
 
   // Trigger error path
   try {
     result = yield client.signOut("FakeSession");
+    do_throw("Expected to catch an exception");
   } catch(expectedError) {
     do_check_eq(102, expectedError.errno);
   }
 
   yield deferredStop(server);
 });
 
 add_task(function test_recoveryEmailStatus() {
@@ -269,93 +301,87 @@ add_task(function test_recoveryEmailStat
   let errorMessage = JSON.stringify({code: 400, errno: 102, error: "doesn't exist"});
   let tries = 0;
 
   let server = httpd_setup({
     "/recovery_email/status": function(request, response) {
       do_check_true(request.hasHeader("Authorization"));
 
       if (tries === 0) {
+        tries += 1;
         response.setStatusLine(request.httpVersion, 200, "OK");
-        return response.bodyOutputStream.write(emailStatus, emailStatus.length);
+        response.bodyOutputStream.write(emailStatus, emailStatus.length);
+        return;
       }
 
       // Second call gets an error trying to query a nonexistent account
       response.setStatusLine(request.httpVersion, 400, "Bad request");
-      return response.bodyOutputStream.write(errorMessage, errorMessage.length);
+      response.bodyOutputStream.write(errorMessage, errorMessage.length);
+      return;
     },
   });
 
   let client = new FxAccountsClient(server.baseURI);
   let result = yield client.recoveryEmailStatus(FAKE_SESSION_TOKEN);
   do_check_eq(result.verified, true);
 
   // Trigger error path
   try {
     result = yield client.recoveryEmailStatus("some bogus session");
+    do_throw("Expected to catch an exception");
   } catch(expectedError) {
     do_check_eq(102, expectedError.errno);
   }
 
   yield deferredStop(server);
 });
 
 add_task(function test_resendVerificationEmail() {
   let emptyMessage = "{}";
   let errorMessage = JSON.stringify({code: 400, errno: 102, error: "doesn't exist"});
   let tries = 0;
 
   let server = httpd_setup({
     "/recovery_email/resend_code": function(request, response) {
       do_check_true(request.hasHeader("Authorization"));
       if (tries === 0) {
+        tries += 1;
         response.setStatusLine(request.httpVersion, 200, "OK");
-        return response.bodyOutputStream.write(emptyMessage, emptyMessage.length);
+        response.bodyOutputStream.write(emptyMessage, emptyMessage.length);
+        return;
       }
 
       // Second call gets an error trying to query a nonexistent account
       response.setStatusLine(request.httpVersion, 400, "Bad request");
-      return response.bodyOutputStream.write(errorMessage, errorMessage.length);
+      response.bodyOutputStream.write(errorMessage, errorMessage.length);
+      return;
     },
   });
 
   let client = new FxAccountsClient(server.baseURI);
   let result = yield client.resendVerificationEmail(FAKE_SESSION_TOKEN);
   do_check_eq(JSON.stringify(result), emptyMessage);
 
   // Trigger error path
   try {
     result = yield client.resendVerificationEmail("some bogus session");
+    do_throw("Expected to catch an exception");
   } catch(expectedError) {
     do_check_eq(102, expectedError.errno);
   }
 
   yield deferredStop(server);
 });
 
 add_task(function test_accountKeys() {
-  // Vectors: https://wiki.mozilla.org/Identity/AttachedServices/KeyServerProtocol#.2Faccount.2Fkeys
-
-  let keyFetch = h("8081828384858687 88898a8b8c8d8e8f"+
-                   "9091929394959697 98999a9b9c9d9e9f");
-
-  let response = h("ee5c58845c7c9412 b11bbd20920c2fdd"+
-                   "d83c33c9cd2c2de2 d66b222613364636"+
-                   "c2c0f8cfbb7c6304 72c0bd88451342c6"+
-                   "c05b14ce342c5ad4 6ad89e84464c993c"+
-                   "3927d30230157d08 17a077eef4b20d97"+
-                   "6f7a97363faf3f06 4c003ada7d01aa70");
-
-  let kA =       h("2021222324252627 28292a2b2c2d2e2f"+
-                   "3031323334353637 38393a3b3c3d3e3f");
-
-  let wrapKB =   h("4041424344454647 48494a4b4c4d4e4f"+
-                   "5051525354555657 58595a5b5c5d5e5f");
-
-  let responseMessage = JSON.stringify({bundle: response});
+  // Four calls to accountKeys().  The first one should work correctly, and we
+  // should get a valid bundle back, in exchange for our keyFetch token, from
+  // which we correctly derive kA and wrapKB.  The subsequent three calls
+  // should all trigger separate error paths.
+  let responseMessage = JSON.stringify({bundle: ACCOUNT_KEYS.response});
   let errorMessage = JSON.stringify({code: 400, errno: 102, error: "doesn't exist"});
   let emptyMessage = "{}";
   let attempt = 0;
 
   let server = httpd_setup({
     "/account/keys": function(request, response) {
       do_check_true(request.hasHeader("Authorization"));
       attempt += 1;
@@ -370,55 +396,60 @@ add_task(function test_accountKeys() {
         case 2:
           // Second time, return no bundle to trigger client error
           response.setStatusLine(request.httpVersion, 200, "OK");
           response.bodyOutputStream.write(emptyMessage, emptyMessage.length);
           break;
 
         case 3:
           // Return gibberish to trigger client MAC error
-          let garbage = response;
-          garbage[0] = 0; // tweak a byte
+          // Tweak a byte
+          let garbageResponse = JSON.stringify({
+            bundle: ACCOUNT_KEYS.response.slice(0, -1) + "1"
+          });
           response.setStatusLine(request.httpVersion, 200, "OK");
-          response.bodyOutputStream.write(responseMessage, responseMessage.length);
+          response.bodyOutputStream.write(garbageResponse, garbageResponse.length);
           break;
 
         case 4:
           // Trigger error for nonexistent account
           response.setStatusLine(request.httpVersion, 400, "Bad request");
           response.bodyOutputStream.write(errorMessage, errorMessage.length);
           break;
       }
     },
   });
 
   let client = new FxAccountsClient(server.baseURI);
 
   // First try, all should be good
-  let result = yield client.accountKeys(keyFetch);
-  do_check_eq(CommonUtils.hexToBytes(kA), result.kA);
-  do_check_eq(CommonUtils.hexToBytes(wrapKB), result.wrapKB);
+  let result = yield client.accountKeys(ACCOUNT_KEYS.keyFetch);
+  do_check_eq(CommonUtils.hexToBytes(ACCOUNT_KEYS.kA), result.kA);
+  do_check_eq(CommonUtils.hexToBytes(ACCOUNT_KEYS.wrapKB), result.wrapKB);
 
   // Second try, empty bundle should trigger error
   try {
-    result = yield client.accountKeys(keyFetch);
+    result = yield client.accountKeys(ACCOUNT_KEYS.keyFetch);
+    do_throw("Expected to catch an exception");
   } catch(expectedError) {
     do_check_eq(expectedError.message, "failed to retrieve keys");
   }
 
   // Third try, bad bundle results in MAC error
   try {
-    result = yield client.accountKeys(keyFetch);
+    result = yield client.accountKeys(ACCOUNT_KEYS.keyFetch);
+    do_throw("Expected to catch an exception");
   } catch(expectedError) {
     do_check_eq(expectedError.message, "error unbundling encryption keys");
   }
 
   // Fourth try, pretend account doesn't exist
   try {
-    result = yield client.accountKeys(keyFetch);
+    result = yield client.accountKeys(ACCOUNT_KEYS.keyFetch);
+    do_throw("Expected to catch an exception");
   } catch(expectedError) {
     do_check_eq(102, expectedError.errno);
   }
 
   yield deferredStop(server);
 });
 
 add_task(function test_signCertificate() {
@@ -432,32 +463,35 @@ add_task(function test_signCertificate()
 
       if (tries === 0) {
         tries += 1;
         let body = CommonUtils.readBytesFromInputStream(request.bodyInputStream);
         let jsonBody = JSON.parse(body);
         do_check_eq(JSON.parse(jsonBody.publicKey).foo, "bar");
         do_check_eq(jsonBody.duration, 600);
         response.setStatusLine(request.httpVersion, 200, "OK");
-        return response.bodyOutputStream.write(certSignMessage, certSignMessage.length);
+        response.bodyOutputStream.write(certSignMessage, certSignMessage.length);
+        return;
       }
 
       // Second attempt, trigger error
       response.setStatusLine(request.httpVersion, 400, "Bad request");
-      return response.bodyOutputStream.write(errorMessage, errorMessage.length);
+      response.bodyOutputStream.write(errorMessage, errorMessage.length);
+      return;
     },
   });
 
   let client = new FxAccountsClient(server.baseURI);
   let result = yield client.signCertificate(FAKE_SESSION_TOKEN, JSON.stringify({foo: "bar"}), 600);
   do_check_eq("baz", result.bar);
 
   // Account doesn't exist
   try {
     result = yield client.signCertificate("bogus", JSON.stringify({foo: "bar"}), 600);
+    do_throw("Expected to catch an exception");
   } catch(expectedError) {
     do_check_eq(102, expectedError.errno);
   }
 
   yield deferredStop(server);
 });
 
 add_task(function test_accountExists() {
@@ -497,37 +531,28 @@ add_task(function test_accountExists() {
           break;
       }
     },
   });
 
   let client = new FxAccountsClient(server.baseURI);
   let result;
 
-  try {
-    result = yield client.accountExists("i.exist@example.com");
-  } catch(expectedError) {
-    do_check_eq(expectedError.code, 400);
-    do_check_eq(expectedError.errno, 103);
-  }
+  result = yield client.accountExists("i.exist@example.com");
+  do_check_true(result);
 
-  try {
-    result = yield client.accountExists("i.also.exist@example.com");
-  } catch(expectedError) {
-    do_check_eq(expectedError.errno, 103);
-  }
+  result = yield client.accountExists("i.also.exist@example.com");
+  do_check_true(result);
 
-  try {
-    result = yield client.accountExists("i.dont.exist@example.com");
-  } catch(expectedError) {
-    do_check_eq(expectedError.errno, 102);
-  }
+  result = yield client.accountExists("i.dont.exist@example.com");
+  do_check_false(result);
 
   try {
     result = yield client.accountExists("i.break.things@example.com");
+    do_throw("Expected to catch an exception");
   } catch(unexpectedError) {
     do_check_eq(unexpectedError.code, 500);
   }
 
   yield deferredStop(server);
 });
 
 add_task(function test_email_case() {
@@ -578,12 +603,23 @@ add_task(function test_email_case() {
 
   let result = yield client.signIn(clientEmail, "123456");
   do_check_eq(result.areWeHappy, "yes");
   do_check_eq(attempts, 2);
 
   yield deferredStop(server);
 });
 
+add_task(function test__deriveHawkCredentials() {
+  let client = new FxAccountsClient("https://example.org");
+
+  let credentials = client._deriveHawkCredentials(
+    SESSION_KEYS.sessionToken, "sessionToken");
+
+  do_check_eq(credentials.algorithm, "sha256");
+  do_check_eq(credentials.id, SESSION_KEYS.tokenID);
+  do_check_eq(CommonUtils.bytesAsHex(credentials.key), SESSION_KEYS.reqHMACkey);
+});
+
 // turn formatted test vectors into normal hex strings
 function h(hexStr) {
   return hexStr.replace(/\s+/g, "");
 }