From bd98c782184a2e8acfcb1ecc10faaa88b37c02ee Mon Sep 17 00:00:00 2001
From: Shane Tomlinson <stomlinson@mozilla.com>
Date: Fri, 6 Jan 2012 12:25:34 +0000
Subject: [PATCH] Supporting the new add email address scheme @lloyd
 implemented. Simplifying network unit tests.

* email_for_token now returns whether the user needs to set their password.
* complete_email_addition now takes a password if necessary.
* Update the add_email_address page to support scheme.
* Update network.js and user.js to support these schemes.
* HUGE simplification of the Network unit tests.  Vast reduction in number.
---
 resources/static/pages/add_email_address.js   |  22 +-
 .../static/pages/verify_email_address.js      |   6 +-
 resources/static/shared/error-messages.js     |   4 +
 resources/static/shared/network.js            |  17 +-
 resources/static/shared/user.js               |  53 ++-
 resources/static/test/qunit/mocks/xhr.js      |   1 +
 .../qunit/pages/add_email_address_test.js     |  13 +-
 .../test/qunit/shared/network_unit_test.js    | 361 +++++-------------
 .../test/qunit/shared/user_unit_test.js       |   8 +-
 9 files changed, 148 insertions(+), 337 deletions(-)

diff --git a/resources/static/pages/add_email_address.js b/resources/static/pages/add_email_address.js
index eb60d5f1a..9b8cdbdbf 100644
--- a/resources/static/pages/add_email_address.js
+++ b/resources/static/pages/add_email_address.js
@@ -53,12 +53,16 @@ BrowserID.addEmailAddress = (function() {
   }
 
   function emailRegistrationComplete(oncomplete, info) {
+    function complete(status) {
+      oncomplete && oncomplete(status);
+    }
+
     var valid = info.valid;
     if (valid) {
-      emailRegistrationSuccess(info, oncomplete.bind(null, true));
+      emailRegistrationSuccess(info, complete.curry(true));
     }
     else {
-      showError("#cannotconfirm", oncomplete.bind(null, false));
+      showError("#cannotconfirm", complete.curry(false));
     }
   }
 
@@ -81,17 +85,13 @@ BrowserID.addEmailAddress = (function() {
     }, 2000);
   }
 
-  function userMustEnterPassword() {
-    var emails = storage.getEmails(),
-        length = 0,
-        anySecondaries = _.find(emails, function(item) { length++; return item.type === "secondary"; });
-
-    return length && !anySecondaries;
+  function userMustEnterPassword(info) {
+    return !!info.needs_password;
   }
 
   function verifyWithoutPassword(oncomplete) {
     user.verifyEmailNoPassword(token,
-      emailRegistrationComplete.bind(null, oncomplete),
+      emailRegistrationComplete.curry(oncomplete),
       pageHelpers.getFailure(errors.verifyEmail, oncomplete)
     );
   }
@@ -103,7 +103,7 @@ BrowserID.addEmailAddress = (function() {
 
     if(valid) {
       user.verifyEmailWithPassword(token, pass,
-        emailRegistrationComplete.bind(null, oncomplete),
+        emailRegistrationComplete.curry(oncomplete),
         pageHelpers.getFailure(errors.verifyEmail, oncomplete)
       );
     }
@@ -117,7 +117,7 @@ BrowserID.addEmailAddress = (function() {
       if(info) {
         showRegistrationInfo(info);
 
-        if(userMustEnterPassword()) {
+        if(userMustEnterPassword(info)) {
           dom.addClass("body", "enter_password");
           oncomplete(true);
         }
diff --git a/resources/static/pages/verify_email_address.js b/resources/static/pages/verify_email_address.js
index d30fccac5..1d6cb5e6a 100644
--- a/resources/static/pages/verify_email_address.js
+++ b/resources/static/pages/verify_email_address.js
@@ -72,9 +72,9 @@
     }
 
     // go get the email address
-    bid.Network.emailForVerificationToken(token, function(email) {
-      if (email) {
-        $('#email').val(email);
+    bid.Network.emailForVerificationToken(token, function(info) {
+      if (info) {
+        $('#email').val(info.email);
         oncomplete && oncomplete();
       }
       else {
diff --git a/resources/static/shared/error-messages.js b/resources/static/shared/error-messages.js
index 789025acd..d72a076dd 100644
--- a/resources/static/shared/error-messages.js
+++ b/resources/static/shared/error-messages.js
@@ -81,6 +81,10 @@ BrowserID.Errors = (function(){
       title: "Getting Assertion"
     },
 
+    getTokenInfo: {
+      title: "Checking Registration Token"
+    },
+
     isEmailRegistered: {
       title: "Checking Email Address"
     },
diff --git a/resources/static/shared/network.js b/resources/static/shared/network.js
index 9f4dbdb5e..627417493 100644
--- a/resources/static/shared/network.js
+++ b/resources/static/shared/network.js
@@ -66,8 +66,8 @@ BrowserID.Network = (function() {
       network.errorThrown = errorThrown;
       network.responseText = jqXHR.responseText;
 
-      if (cb) cb(info);
       mediator && mediator.publish("xhrError", info);
+      if (cb) cb(info);
     };
   }
 
@@ -310,8 +310,13 @@ BrowserID.Network = (function() {
     emailForVerificationToken: function(token, onComplete, onFailure) {
       get({
         url : "/wsapi/email_for_token?token=" + encodeURIComponent(token),
-        success: function(data) {
-          if (onComplete) onComplete(data.email);
+        success: function(result) {
+          var data = null;
+          if(result.success !== false) {
+            // force needs_password to be set;
+            data = _.extend({ needs_password: false }, result);
+          }
+          if (onComplete) onComplete(data);
         },
         error: onFailure
       });
@@ -359,15 +364,17 @@ BrowserID.Network = (function() {
      * Call with a token to prove an email address ownership.
      * @method completeEmailRegistration
      * @param {string} token - token proving email ownership.
+     * @param {string} password - password to set if necessary.  If not necessary, set to undefined.
      * @param {function} [onComplete] - Callback to call when complete.  Called
      * with one boolean parameter that specifies the validity of the token.
      * @param {function} [onFailure] - Called on XHR failure.
      */
-    completeEmailRegistration: function(token, onComplete, onFailure) {
+    completeEmailRegistration: function(token, password, onComplete, onFailure) {
       post({
         url: "/wsapi/complete_email_addition",
         data: {
-          token: token
+          token: token,
+          pass: password
         },
         success: function(status, textStatus, jqXHR) {
           if (onComplete) onComplete(status.success);
diff --git a/resources/static/shared/user.js b/resources/static/shared/user.js
index 1771c1536..a16b17b87 100644
--- a/resources/static/shared/user.js
+++ b/resources/static/shared/user.js
@@ -491,13 +491,12 @@ BrowserID.User = (function() {
      * @param {function} [onFailure]
      */
     tokenInfo: function(token, onComplete, onFailure) {
-      network.emailForVerificationToken(token, function (email) {
-        var info = email ? {
-          email: email,
-          origin: storage.getStagedOnBehalfOf()
-        } : null;
+      network.emailForVerificationToken(token, function (info) {
+        if(info) {
+          info = _.extend(info, { origin: storage.getStagedOnBehalfOf() });
+        }
 
-        onComplete(info);
+        onComplete && onComplete(info);
       }, onFailure);
 
     },
@@ -803,36 +802,28 @@ BrowserID.User = (function() {
      * @param {function} [onFailure] - Called on error.
      */
     verifyEmailNoPassword: function(token, onComplete, onFailure) {
-      network.emailForVerificationToken(token, function (email) {
-        var invalidInfo = { valid: false };
-        if (email) {
-          network.completeEmailRegistration(token, function (valid) {
-            var info = valid ? {
-              valid: valid,
-              email: email,
-              origin: storage.getStagedOnBehalfOf()
-            } : invalidInfo;
-
-            storage.setStagedOnBehalfOf("");
-
-            if (onComplete) onComplete(info);
-          }, onFailure);
-        } else if (onComplete) {
-          onComplete(invalidInfo);
-        }
-      }, onFailure);
+      User.verifyEmailWithPassword(token, undefined, onComplete, onFailure);
     },
 
     verifyEmailWithPassword: function(token, pass, onComplete, onFailure) {
-      User.verifyEmailNoPassword(token, function(userInfo) {
+      function complete(status) {
+        onComplete && onComplete(status);
+      }
+      network.emailForVerificationToken(token, function (info) {
         var invalidInfo = { valid: false };
-        if (userInfo.status !== false) {
-          User.setPassword(pass, function(status) {
-            onComplete(status ? userInfo : invalidInfo);
+        if (info) {
+          network.completeEmailRegistration(token, pass, function (valid) {
+            var result = invalidInfo;
+
+            if(valid) {
+              result = _.extend({ valid: valid, origin: storage.getStagedOnBehalfOf() }, info);
+              storage.setStagedOnBehalfOf("");
+            }
+
+            complete(result);
           }, onFailure);
-        }
-        else {
-          onComplete(invalidInfo);
+        } else {
+          complete(invalidInfo);
         }
       }, onFailure);
     },
diff --git a/resources/static/test/qunit/mocks/xhr.js b/resources/static/test/qunit/mocks/xhr.js
index bb335fdd8..f4ad2c25d 100644
--- a/resources/static/test/qunit/mocks/xhr.js
+++ b/resources/static/test/qunit/mocks/xhr.js
@@ -61,6 +61,7 @@ BrowserID.Mocks.xhr = (function() {
       // the flag contextAjaxError.
       "get /wsapi/session_context contextAjaxError": undefined,
       "get /wsapi/email_for_token?token=token valid": { email: "testuser@testuser.com" },
+      "get /wsapi/email_for_token?token=token needsPassword": { email: "testuser@testuser.com", needs_password: true },
       "get /wsapi/email_for_token?token=token invalid": { success: false },
       "post /wsapi/authenticate_user valid": { success: true },
       "post /wsapi/authenticate_user invalid": { success: false },
diff --git a/resources/static/test/qunit/pages/add_email_address_test.js b/resources/static/test/qunit/pages/add_email_address_test.js
index 06544ba89..4bd93a3db 100644
--- a/resources/static/test/qunit/pages/add_email_address_test.js
+++ b/resources/static/test/qunit/pages/add_email_address_test.js
@@ -60,13 +60,6 @@
     }
   });
 
-  function createPrimaryUser() {
-    storage.addEmail("testuser@testuser.com", {
-      created: new Date(),
-      type: "primary"
-    });
-  }
-
   function createController(options, callback) {
     controller = BrowserID.addEmailAddress.create();
     options = options || {};
@@ -75,7 +68,7 @@
   }
 
   function expectTooltipVisible() {
-    createPrimaryUser();
+    xhr.useResult("needsPassword");
     createController(config, function() {
       controller.submit(function() {
         testHelpers.testTooltipVisible();
@@ -142,7 +135,7 @@
   });
 
   asyncTest("password: first secondary address added", function() {
-    createPrimaryUser();
+    xhr.useResult("needsPassword");
     createController(config, function() {
       equal($("body").hasClass("enter_password"), true, "enter_password added to body");
       testEmail();
@@ -193,7 +186,6 @@
     $("#password").val("password");
     $("#vpassword").val("password");
 
-    createPrimaryUser();
     createController(config, function() {
       controller.submit(function(status) {
         equal(status, true, "correct status");
@@ -208,7 +200,6 @@
     $("#vpassword").val("password");
 
     xhr.useResult("invalid");
-    createPrimaryUser();
     createController(config, function() {
       testCannotConfirm();
       start();
diff --git a/resources/static/test/qunit/shared/network_unit_test.js b/resources/static/test/qunit/shared/network_unit_test.js
index 41bf3d031..55227d465 100644
--- a/resources/static/test/qunit/shared/network_unit_test.js
+++ b/resources/static/test/qunit/shared/network_unit_test.js
@@ -43,62 +43,38 @@
       testHelpers = bid.TestHelpers,
       TEST_EMAIL = "testuser@testuser.com";
 
-  function notificationCheck(cb) {
+  function failureCheck(cb) {
     // Take the original arguments, take off the function.  Add any additional
     // arguments that were passed in, and then tack on the onSuccess and
     // onFailure to the end.  Then call the callback.
-    var args = Array.prototype.slice.call(arguments, 1);
-
-    xhr.useResult("ajaxError");
+    var args = [].slice.call(arguments, 1);
 
-    var handle;
+    var errorInfo;
+    mediator.subscribe("xhrError", function(message, info) {
+      errorInfo = info;
+    });
 
-    var subscriber = function(message, info) {
-      ok(true, "xhr error notified application");
+    args.push(testHelpers.unexpectedSuccess, function onFailure(info) {
+      ok(true, "XHR failure should never pass");
       ok(info.network.url, "url is in network info");
       ok(info.network.type, "request type is in network info");
       equal(info.network.textStatus, "errorStatus", "textStatus is in network info");
       equal(info.network.errorThrown, "errorThrown", "errorThrown is in response info");
-      equal(info.network.responseText, "response text", "responseText is in response info");
-      mediator.unsubscribe(handle);
-      start();
-    };
 
-    handle = mediator.subscribe("xhrError", subscriber);
+      ok(errorInfo.network.url, "url is in network errorInfo");
+      ok(errorInfo.network.type, "request type is in network errorInfo");
+      equal(errorInfo.network.textStatus, "errorStatus", "textStatus is in network errorInfo");
+      equal(errorInfo.network.errorThrown, "errorThrown", "errorThrown is in response errorInfo");
+      equal(errorInfo.network.responseText, "response text", "responseText is in response errorInfo");
 
-    if (cb) {
-      cb.apply(null, args);
-    }
-  }
-
-  function unexpectedFailure() {
-    return function() {
-      ok(false, "unexpected failure");
-      start();
-    }
-  }
-
-  function failureCheck(cb) {
-    // Take the original arguments, take off the function.  Add any additional
-    // arguments that were passed in, and then tack on the onSuccess and
-    // onFailure to the end.  Then call the callback.
-    var args = Array.prototype.slice.call(arguments, 1);
-
-    args.push(function onSuccess() {
-      ok(false, "XHR failure should never pass");
-      start();
-    }, function onFailure(info) {
-      ok(true, "XHR failure should never pass");
-      ok(info.network.url, "url is in network info");
-      ok(info.network.type, "request type is in network info");
-      equal(info.network.textStatus, "errorStatus", "textStatus is in network info");
-      equal(info.network.errorThrown, "errorThrown", "errorThrown is in response info");
       start();
     });
 
-    xhr.useResult("ajaxError");
+    if(xhr.resultType === "valid") {
+      xhr.useResult("ajaxError");
+    }
 
-    cb.apply(null, args);
+    cb && cb.apply(null, args);
   }
 
   var network = BrowserID.Network;
@@ -117,10 +93,7 @@
     network.authenticate(TEST_EMAIL, "testuser", function onSuccess(authenticated) {
       equal(authenticated, true, "valid authentication");
       start();
-    }, function onFailure() {
-      ok(false, "valid authentication");
-      start();
-    });
+    }, testHelpers.unexpectedXHRFailure);
   });
 
   asyncTest("authenticate with invalid user", function() {
@@ -128,14 +101,7 @@
     network.authenticate(TEST_EMAIL, "invalid", function onSuccess(authenticated) {
       equal(authenticated, false, "invalid authentication");
       start();
-    }, function onFailure() {
-      ok(false, "invalid authentication");
-      start();
-    });
-  });
-
-  asyncTest("authenticate with XHR failure, checking whether application is notified", function() {
-    notificationCheck(network.authenticate, TEST_EMAIL, "ajaxError");
+    }, testHelpers.unexpectedXHRFailure);
   });
 
   asyncTest("authenticate with XHR failure after context already setup", function() {
@@ -159,17 +125,9 @@
   });
 
   asyncTest("authenticateWithAssertion with XHR failure", function() {
-    xhr.useResult("ajaxError");
-
-    network.authenticateWithAssertion(
-      TEST_EMAIL,
-      "test_assertion",
-      testHelpers.unexpectedSuccess,
-      testHelpers.expectedXHRFailure
-    );
+    failureCheck(network.authenticateWithAssertion, TEST_EMAIL, "test_assertion");
   });
 
-
   asyncTest("checkAuth with valid authentication", function() {
     xhr.setContextInfo("authenticated", true);
     network.checkAuth(function onSuccess(authenticated) {
@@ -200,11 +158,7 @@
     network.checkAuth(function onSuccess() {
       ok(true, "checkAuth does not make an ajax call, all good");
       start();
-    }, function onFailure() {
-      ok(false, "checkAuth does not make an ajax call, should not fail");
-      start();
-    });
-
+    }, testHelpers.unexpectedFailure);
   });
 
 
@@ -212,59 +166,39 @@
     network.logout(function onSuccess() {
       ok(true, "we can logout");
       start();
-    }, function onFailure() {
-      ok(false, "logout failure");
-      start();
-    });
+    }, testHelpers.unexpectedFailure);
   });
 
 
-  asyncTest("logout with XHR failure", function() {
-    notificationCheck(network.logout);
-  });
-
   asyncTest("logout with XHR failure", function() {
     failureCheck(network.logout);
   });
 
 
-  asyncTest("complete_email_addition valid", function() {
-    network.completeEmailRegistration("goodtoken", function onSuccess(proven) {
+  asyncTest("completeEmailRegistration valid", function() {
+    network.completeEmailRegistration("goodtoken", "password", function onSuccess(proven) {
       equal(proven, true, "good token proved");
       start();
-    }, function onFailure() {
-      start();
-    });
-
+    }, testHelpers.unexpectedXHRFailure);
   });
 
-  asyncTest("complete_email_addition with invalid token", function() {
+  asyncTest("completeEmailRegistration with invalid token", function() {
     xhr.useResult("invalid");
-    network.completeEmailRegistration("badtoken", function onSuccess(proven) {
+    network.completeEmailRegistration("badtoken", "password", function onSuccess(proven) {
       equal(proven, false, "bad token could not be proved");
       start();
-    }, function onFailure() {
-      start();
-    });
-
-  });
-
-  asyncTest("complete_email_addition with XHR failure", function() {
-    notificationCheck(network.completeEmailRegistration, "goodtoken");
+    }, testHelpers.unexpectedXHRFailure);
   });
 
-  asyncTest("complete_email_addition with XHR failure", function() {
-    failureCheck(network.completeEmailRegistration, "goodtoken");
+  asyncTest("completeEmailRegistration with XHR failure", function() {
+    failureCheck(network.completeEmailRegistration, "goodtoken", "password");
   });
 
   asyncTest("createUser with valid user", function() {
     network.createUser("validuser", "origin", function onSuccess(created) {
       ok(created);
       start();
-    }, function onFailure() {
-      start();
-    });
-
+    }, testHelpers.unexpectedFailure);
   });
 
   asyncTest("createUser with invalid user", function() {
@@ -272,10 +206,7 @@
     network.createUser("invaliduser", "origin", function onSuccess(created) {
       equal(created, false);
       start();
-    }, function onFailure() {
-      start();
-    });
-
+    }, testHelpers.unexpectedFailure);
   });
 
   asyncTest("createUser throttled", function() {
@@ -284,15 +215,7 @@
     network.createUser("validuser", "origin", function onSuccess(added) {
       equal(added, false, "throttled email returns onSuccess but with false as the value");
       start();
-    }, function onFailure() {
-      ok(false);
-      start();
-    });
-
-  });
-
-  asyncTest("createUser with XHR failure", function() {
-    notificationCheck(network.createUser, "validuser", "origin");
+    }, testHelpers.unexpectedFailure);
   });
 
   asyncTest("createUser with XHR failure", function() {
@@ -305,11 +228,7 @@
     network.checkUserRegistration("registered@testuser.com", function(status) {
       equal(status, "pending");
       start();
-    }, function onFailure() {
-      ok(false);
-      start();
-    });
-
+    }, testHelpers.unexpectedFailure);
   });
 
   asyncTest("checkUserRegistration with complete email", function() {
@@ -318,15 +237,7 @@
     network.checkUserRegistration("registered@testuser.com", function(status) {
       equal(status, "complete");
       start();
-    }, function onFailure() {
-      ok(false);
-      start();
-    });
-
-  });
-
-  asyncTest("checkUserRegistration with XHR failure", function() {
-    notificationCheck(network.checkUserRegistration, "registered@testuser.com");
+    }, testHelpers.unexpectedFailure);
   });
 
   asyncTest("checkUserRegistration with XHR failure", function() {
@@ -337,11 +248,7 @@
     network.completeUserRegistration("token", "password", function(registered) {
       ok(registered);
       start();
-    }, function onFailure() {
-      ok(false);
-      start();
-    });
-
+    }, testHelpers.unexpectedFailure);
   });
 
   asyncTest("completeUserRegistration with invalid token", function() {
@@ -350,15 +257,7 @@
     network.completeUserRegistration("token", "password", function(registered) {
       equal(registered, false);
       start();
-    }, function onFailure() {
-      ok(false);
-      start();
-    });
-
-  });
-
-  asyncTest("completeUserRegistration with XHR failure", function() {
-    notificationCheck(network.completeUserRegistration, "token", "password");
+    }, testHelpers.unexpectedFailure);
   });
 
   asyncTest("completeUserRegistration with XHR failure", function() {
@@ -371,10 +270,7 @@
       // XXX need a test here.
       ok(true);
       start();
-    }, function onFailure() {
-      start();
-    });
-
+    }, testHelpers.unexpectedFailure);
   });
 
   asyncTest("cancelUser invalid", function() {
@@ -384,14 +280,7 @@
       // XXX need a test here.
       ok(true);
       start();
-    }, function onFailure() {
-      start();
-    });
-
-  });
-
-  asyncTest("cancelUser with XHR failure", function() {
-    notificationCheck(network.cancelUser);
+    }, testHelpers.unexpectedFailure);
   });
 
   asyncTest("cancelUser with XHR failure", function() {
@@ -402,26 +291,14 @@
     network.emailRegistered("registered@testuser.com", function(taken) {
       equal(taken, true, "a taken email is marked taken");
       start();
-    }, function onFailure() {
-      ok(false);
-      start();
-    });
-
+    }, testHelpers.unexpectedFailure);
   });
 
   asyncTest("emailRegistered with nottaken email", function() {
     network.emailRegistered("unregistered@testuser.com", function(taken) {
       equal(taken, false, "a not taken email is not marked taken");
       start();
-    }, function onFailure() {
-      ok(false);
-      start();
-    });
-
-  });
-
-  asyncTest("emailRegistered with XHR failure", function() {
-    notificationCheck(network.emailRegistered, "registered@testuser.com");
+    }, testHelpers.unexpectedFailure);
   });
 
   asyncTest("emailRegistered with XHR failure", function() {
@@ -433,10 +310,7 @@
     network.addSecondaryEmail("address", "origin", function onSuccess(added) {
       ok(added);
       start();
-    }, function onFailure() {
-      ok(false);
-      start();
-    });
+    }, testHelpers.unexpectedFailure);
   });
 
   asyncTest("addSecondaryEmail invalid", function() {
@@ -444,10 +318,7 @@
     network.addSecondaryEmail("address", "origin", function onSuccess(added) {
       equal(added, false);
       start();
-    }, function onFailure() {
-      ok(false);
-      start();
-    });
+    }, testHelpers.unexpectedFailure);
   });
 
   asyncTest("addSecondaryEmail throttled", function() {
@@ -456,15 +327,7 @@
     network.addSecondaryEmail("address", "origin", function onSuccess(added) {
       equal(added, false, "throttled email returns onSuccess but with false as the value");
       start();
-    }, function onFailure() {
-      ok(false);
-      start();
-    });
-
-  });
-
-  asyncTest("addSecondaryEmail with XHR failure", function() {
-    notificationCheck(network.addSecondaryEmail, "address", "origin");
+    }, testHelpers.unexpectedFailure);
   });
 
   asyncTest("addSecondaryEmail with XHR failure", function() {
@@ -477,11 +340,7 @@
     network.checkEmailRegistration("registered@testuser.com", function(status) {
       equal(status, "pending");
       start();
-    }, function onFailure() {
-      ok(false);
-      start();
-    });
-
+    }, testHelpers.unexpectedFailure);
   });
 
   asyncTest("checkEmailRegistration complete", function() {
@@ -497,10 +356,6 @@
 
   });
 
-  asyncTest("checkEmailRegistration with XHR failure", function() {
-    notificationCheck(network.checkEmailRegistration, "address");
-  });
-
   asyncTest("checkEmailRegistration with XHR failure", function() {
     failureCheck(network.checkEmailRegistration, "address");
   });
@@ -523,26 +378,47 @@
   });
 
   asyncTest("addEmailWithAssertion with XHR failure", function() {
-    xhr.useResult("ajaxError");
+    failureCheck(network.addEmailWithAssertion, "test_assertion");
+  });
+
+
+  asyncTest("emailForVerificationToken with XHR failure", function() {
+    failureCheck(network.emailForVerificationToken, "token");
+  });
+
+  asyncTest("emailForVerificationToken with invalid token - returns null result", function() {
+    xhr.useResult("invalid");
+
+    network.emailForVerificationToken("token", function(result) {
+      equal(result, null, "invalid token returns null result");
+      start();
+    }, testHelpers.unexpectedXHRFailure);
+  });
 
-    network.addEmailWithAssertion(
-      "test_assertion",
-      testHelpers.unexpectedSuccess,
-      testHelpers.expectedXHRFailure
-    );
+  asyncTest("emailForVerificationToken that needs password - returns needs_password and email address", function() {
+    xhr.useResult("needsPassword");
+
+    network.emailForVerificationToken("token", function(result) {
+      equal(result.needs_password, true, "needs_password correctly set to true");
+      equal(result.email, "testuser@testuser.com", "email address correctly added");
+      start();
+    }, testHelpers.unexpectedXHRFailure);
   });
 
+  asyncTest("emailForVerificationToken that does not need password", function() {
+    network.emailForVerificationToken("token", function(result) {
+      equal(result.needs_password, false, "needs_password correctly set to false");
+      equal(result.email, "testuser@testuser.com", "email address correctly added");
+      start();
+    }, testHelpers.unexpectedXHRFailure);
+  });
 
   asyncTest("removeEmail valid", function() {
     network.removeEmail("validemail", function onSuccess() {
       // XXX need a test here;
       ok(true);
       start();
-    }, function onFailure() {
-      ok(false);
-      start();
-    });
-
+    }, testHelpers.unexpectedFailure);
   });
 
   asyncTest("removeEmail invalid", function() {
@@ -552,15 +428,7 @@
       // XXX need a test here;
       ok(true);
       start();
-    }, function onFailure() {
-      ok(false);
-      start();
-    });
-
-  });
-
-  asyncTest("removeEmail with XHR failure", function() {
-    notificationCheck(network.removeEmail, "validemail");
+    }, testHelpers.unexpectedFailure);
   });
 
   asyncTest("removeEmail with XHR failure", function() {
@@ -573,15 +441,7 @@
       // XXX need a test here;
       ok(true);
       start();
-    }, function onFailure() {
-      ok(false);
-      start();
-    });
-
-  });
-
-  asyncTest("requestPasswordReset with XHR failure", function() {
-    notificationCheck(network.requestPasswordReset, "address", "origin");
+    }, testHelpers.unexpectedFailure);
   });
 
   asyncTest("requestPasswordReset with XHR failure", function() {
@@ -596,12 +456,7 @@
   });
 
   asyncTest("setPassword with XHR failure", function() {
-    xhr.useResult("ajaxError");
-    network.setPassword(
-      "password",
-      testHelpers.unexpectedSuccess,
-      testHelpers.expectedXHRFailure
-    );
+    failureCheck(network.setPassword, "password");
   });
 
   asyncTest("serverTime", function() {
@@ -622,45 +477,22 @@
   });
 
   asyncTest("serverTime with XHR failure before context has been setup", function() {
-    notificationCheck();
     xhr.useResult("contextAjaxError");
 
-    network.serverTime();
-  });
-
-  asyncTest("serverTime with XHR failure before context has been setup", function() {
-    xhr.useResult("contextAjaxError");
-
-    network.serverTime(function onSuccess(time) {
-      ok(false, "XHR failure should never call success");
-      start();
-    }, function onFailure() {
-      ok(true, "XHR failure should always call failure");
-      start();
-    });
-
+    failureCheck(network.serverTime);
   });
 
   asyncTest("codeVersion", function() {
     network.codeVersion(function onComplete(version) {
       equal(version, "ABC123", "version returned properly");
       start();
-    }, function onFailure() {
-      ok(false, "unexpected failure");
-      start();
-    });
+    }, testHelpers.unexpectedFailure);
   });
 
   asyncTest("codeVersion with XHR failure", function() {
     xhr.useResult("contextAjaxError");
 
-    network.codeVersion(function onComplete(version) {
-      ok(false, "XHR failure should never call complete");
-      start();
-    }, function onFailure() {
-      ok(true, "XHR failure should always return failure");
-      start();
-    });
+    failureCheck(network.codeVersion);
   });
 
   asyncTest("addressInfo with unknown secondary email", function() {
@@ -670,7 +502,7 @@
       equal(data.type, "secondary", "type is secondary");
       equal(data.known, false, "address is unknown to BrowserID");
       start();
-    }, unexpectedFailure);
+    }, testHelpers.unexpectedXHRFailure);
   });
 
   asyncTest("addressInfo with known seconday email", function() {
@@ -680,7 +512,7 @@
       equal(data.type, "secondary", "type is secondary");
       equal(data.known, true, "address is known to BrowserID");
       start();
-    }, unexpectedFailure);
+    }, testHelpers.unexpectedXHRFailure);
   });
 
   asyncTest("addressInfo with primary email", function() {
@@ -691,11 +523,10 @@
       ok("auth" in data, "auth field exists");
       ok("prov" in data, "prov field exists");
       start();
-    }, unexpectedFailure);
+    }, testHelpers.unexpectedXHRFailure);
   });
 
   asyncTest("addressInfo with XHR failure", function() {
-    xhr.useResult("ajaxError");
     failureCheck(network.addressInfo, TEST_EMAIL);
   });
 
@@ -703,10 +534,7 @@
     network.changePassword("oldpassword", "newpassword", function onComplete(status) {
       equal(status, true, "calls onComplete with true status");
       start();
-    }, function onFailure() {
-      ok(false, "unexpected failure");
-      start();
-    });
+    }, testHelpers.unexpectedFailure);
   });
 
   asyncTest("changePassword with incorrect old password, expect complete callback with false status", function() {
@@ -715,22 +543,11 @@
     network.changePassword("oldpassword", "newpassword", function onComplete(status) {
       equal(status, false, "calls onComplete with false status");
       start();
-    }, function onFailure() {
-      ok(false, "unexpected failure");
-      start();
-    });
+    }, testHelpers.unexpectedFailure);
   });
 
   asyncTest("changePassword with XHR failure, expect error callback", function() {
-    xhr.useResult("ajaxError");
-
-    network.changePassword("oldpassword", "newpassword", function onComplete() {
-      ok(false, "XHR failure should never call complete");
-      start();
-    }, function onFailure() {
-      ok(true, "XHR failure should always call failure");
-      start();
-    });
+    failureCheck(network.changePassword, "oldpassword", "newpassword");
   });
 
 }());
diff --git a/resources/static/test/qunit/shared/user_unit_test.js b/resources/static/test/qunit/shared/user_unit_test.js
index b191f8590..7e081cee2 100644
--- a/resources/static/test/qunit/shared/user_unit_test.js
+++ b/resources/static/test/qunit/shared/user_unit_test.js
@@ -779,7 +779,7 @@ var vep = require("./vep");
     }, 500);
   });
 
-  asyncTest("verifyEmailNoPassword with a good token", function() {
+  asyncTest("verifyEmailNoPassword with a good token - callback with email, orgiin, and valid", function() {
     storage.setStagedOnBehalfOf(testOrigin);
     lib.verifyEmailNoPassword("token", function onSuccess(info) {
 
@@ -792,7 +792,7 @@ var vep = require("./vep");
     }, testHelpers.unexpectedXHRFailure);
   });
 
-  asyncTest("verifyEmailNoPassword with a bad token", function() {
+  asyncTest("verifyEmailNoPassword with a bad token - callback with valid: false", function() {
     xhr.useResult("invalid");
 
     lib.verifyEmailNoPassword("token", function onSuccess(info) {
@@ -812,7 +812,7 @@ var vep = require("./vep");
     );
   });
 
-  asyncTest("verifyEmailWithPassword with a good token", function() {
+  asyncTest("verifyEmailWithPassword with a good token - callback with email, origin, valid", function() {
     storage.setStagedOnBehalfOf(testOrigin);
     lib.verifyEmailWithPassword("token", "password", function onSuccess(info) {
 
@@ -825,7 +825,7 @@ var vep = require("./vep");
     }, testHelpers.unexpectedXHRFailure);
   });
 
-  asyncTest("verifyEmailWithPassword with a bad token", function() {
+  asyncTest("verifyEmailWithPassword with a bad token - callback with valid: false", function() {
     xhr.useResult("invalid");
 
     lib.verifyEmailWithPassword("token", "password", function onSuccess(info) {
-- 
GitLab