From b0ce93d17a21d58e709bc589d13b6ef453aad7d3 Mon Sep 17 00:00:00 2001 From: Shane Tomlinson <stomlinson@mozilla.com> Date: Fri, 21 Oct 2011 16:55:01 +0100 Subject: [PATCH] Fixing calls that did not handle ajax errors correctly. Adding tests for all network calls to check whether they handle ajax errors correctly. This includes network disconnection, server error codes, etc. Fixing several calls that did not handle this appropriately. issue #448 --- browserid/static/dialog/resources/network.js | 25 +- .../test/qunit/resources/network_unit_test.js | 263 +++++++++++++++++- 2 files changed, 269 insertions(+), 19 deletions(-) diff --git a/browserid/static/dialog/resources/network.js b/browserid/static/dialog/resources/network.js index 18d041256..f0fd530dd 100644 --- a/browserid/static/dialog/resources/network.js +++ b/browserid/static/dialog/resources/network.js @@ -37,12 +37,12 @@ BrowserID.Network = (function() { "use strict"; - var csrf_token; - var xhr = $; - var server_time; - var auth_status; + var csrf_token, + xhr = $, + server_time, + auth_status; - function withContext(cb) { + function withContext(cb, error) { if (typeof auth_status === 'boolean' && typeof csrf_token !== 'undefined') cb(); else { xhr.ajax({ @@ -57,6 +57,7 @@ BrowserID.Network = (function() { auth_status = result.authenticated; _.defer(cb); }, + error: error, dataType: "json" }); } @@ -93,7 +94,7 @@ BrowserID.Network = (function() { success: options.success, error: options.error }); - }); + }, options.error); } var Network = { @@ -158,15 +159,16 @@ BrowserID.Network = (function() { } catch(e) { if (onFailure) onFailure(e.toString()); } - }); + }, onFailure); }, /** * Log the authenticated user out * @method logout * @param {function} [onSuccess] - called on completion + * @param {function} [onFailure] - Called on XHR failure. */ - logout: function(onSuccess) { + logout: function(onSuccess, onFailure) { post({ url: "/wsapi/logout", success: function() { @@ -177,7 +179,8 @@ BrowserID.Network = (function() { // user was successfully logged out. auth_status = false; if (onSuccess) _.defer(onSuccess); - } + }, + error: onFailure }); }, @@ -430,7 +433,7 @@ BrowserID.Network = (function() { _.delay(onSuccess, 0, status.success); } }, - failure: onFailure + error: onFailure }); }, @@ -482,7 +485,7 @@ BrowserID.Network = (function() { } catch(e) { onFailure(e.toString()); } - }); + }, onFailure); } }; diff --git a/browserid/static/dialog/test/qunit/resources/network_unit_test.js b/browserid/static/dialog/test/qunit/resources/network_unit_test.js index 1f532b24b..840084d99 100644 --- a/browserid/static/dialog/test/qunit/resources/network_unit_test.js +++ b/browserid/static/dialog/test/qunit/resources/network_unit_test.js @@ -59,33 +59,54 @@ steal.plugins("jquery", "funcunit/qunit").then("/dialog/resources/network", func }; + /** + * This is the results table, the keys are the request type, url, and + * a "selector" for testing. The right is the expected return value, already + * decoded. If a result is "undefined", the request's error handler will be + * called. + */ var xhr = { results: { - "get /wsapi/session_context valid": contextInfo, + "get /wsapi/session_context valid": contextInfo, "get /wsapi/session_context invalid": contextInfo, + // We are going to test for ajax errors for session_context using + // call to serverTime. We are going to use the flag contextAjaxError + "get /wsapi/session_context ajaxError": contextInfo, + "get /wsapi/session_context contextAjaxError": undefined, "post /wsapi/authenticate_user valid": { success: true }, "post /wsapi/authenticate_user invalid": { success: false }, + "post /wsapi/authenticate_user ajaxError": undefined, "post /wsapi/complete_email_addition valid": { success: true }, "post /wsapi/complete_email_addition invalid": { success: false }, + "post /wsapi/complete_email_addition ajaxError": undefined, "post /wsapi/stage_user valid": { success: true }, "post /wsapi/stage_user invalid": { success: false }, + "post /wsapi/stage_user ajaxError": undefined, "get /wsapi/user_creation_status?email=address notcreated": undefined, // undefined because server returns 400 error "get /wsapi/user_creation_status?email=address pending": { status: "pending" }, "get /wsapi/user_creation_status?email=address complete": { status: "complete" }, + "get /wsapi/user_creation_status?email=address ajaxError": undefined, "post /wsapi/complete_user_creation valid": { success: true }, "post /wsapi/complete_user_creation invalid": { success: false }, + "post /wsapi/complete_user_creation ajaxError": undefined, "post /wsapi/logout valid": { success: true }, + "post /wsapi/logout ajaxError": undefined, "get /wsapi/have_email?email=address taken": { email_known: true }, "get /wsapi/have_email?email=address nottaken" : { email_known: false }, + "get /wsapi/have_email?email=address ajaxError" : undefined, "post /wsapi/remove_email valid": { success: true }, "post /wsapi/remove_email invalid": { success: false }, + "post /wsapi/remove_email ajaxError": undefined, "post /wsapi/account_cancel valid": { success: true }, "post /wsapi/account_cancel invalid": { success: false }, + "post /wsapi/account_cancel ajaxError": undefined, "post /wsapi/stage_email valid": { success: true }, "post /wsapi/stage_email invalid": { success: false }, + "post /wsapi/stage_email ajaxError": undefined, "get /wsapi/email_addition_status?email=address notcreated": undefined, // undefined because server returns 400 error "get /wsapi/email_addition_status?email=address pending": { status: "pending" }, "get /wsapi/email_addition_status?email=address complete": { status: "complete" }, + "get /wsapi/email_addition_status?email=address ajaxError": undefined }, useResult: function(result) { @@ -165,6 +186,20 @@ steal.plugins("jquery", "funcunit/qunit").then("/dialog/resources/network", func }); + wrappedAsyncTest("authenticate with ajax error after context already setup", function() { + xhr.useResult("ajaxError"); + network.authenticate("testuser@testuser.com", "ajaxError", function onSuccess(authenticated) { + ok(false, "ajax error should never pass"); + wrappedStart(); + }, function onFailure() { + ok(true, "ajax error should never pass"); + wrappedStart(); + }); + + stop(); + }); + + wrappedAsyncTest("checkAuth with valid authentication", function() { contextInfo.authenticated = true; network.checkAuth(function onSuccess(authenticated) { @@ -194,6 +229,22 @@ steal.plugins("jquery", "funcunit/qunit").then("/dialog/resources/network", func }); + wrappedAsyncTest("checkAuth with ajax error", function() { + xhr.useResult("ajaxError"); + contextInfo.authenticated = false; + + network.checkAuth(function onSuccess() { + ok(true, "checkAuth does not make an ajax call, all good"); + wrappedStart(); + }, function onFailure() { + ok(false, "checkAuth does not make an ajax call, should not fail"); + wrappedStart(); + }); + + stop(); + }); + + wrappedAsyncTest("logout", function() { network.logout(function onSuccess() { ok(true, "we can logout"); @@ -207,6 +258,21 @@ steal.plugins("jquery", "funcunit/qunit").then("/dialog/resources/network", func }); + wrappedAsyncTest("logout with ajax error", function() { + xhr.useResult("ajaxError"); + + network.logout(function onSuccess() { + ok(false, "ajax error should never call success"); + wrappedStart(); + }, function onFailure() { + ok(true, "ajax error should always call failure"); + wrappedStart(); + }); + + stop(); + }); + + wrappedAsyncTest("complete_email_addition valid", function() { network.completeEmailRegistration("goodtoken", function onSuccess(proven) { equal(proven, true, "good token proved"); @@ -230,6 +296,19 @@ steal.plugins("jquery", "funcunit/qunit").then("/dialog/resources/network", func stop(); }); + wrappedAsyncTest("complete_email_addition with ajax error", function() { + xhr.useResult("ajaxError"); + network.completeEmailRegistration("goodtoken", function onSuccess(proven) { + ok(false, "ajax error should never call success"); + wrappedStart(); + }, function onFailure() { + ok(true, "ajax error should always call failure"); + wrappedStart(); + }); + + stop(); + }); + wrappedAsyncTest("createUser with valid user", function() { network.createUser("validuser", "origin", function onSuccess(created) { ok(created); @@ -253,6 +332,20 @@ steal.plugins("jquery", "funcunit/qunit").then("/dialog/resources/network", func stop(); }); + wrappedAsyncTest("createUser with ajax error", function() { + xhr.useResult("ajaxError"); + + network.createUser("validuser", "origin", function onSuccess(created) { + ok(false, "ajax error should never call success"); + wrappedStart(); + }, function onFailure() { + ok(true, "ajax error should always call failure"); + wrappedStart(); + }); + + stop(); + }); + wrappedAsyncTest("checkUserRegistration with pending email", function() { xhr.useResult("pending"); @@ -281,6 +374,20 @@ steal.plugins("jquery", "funcunit/qunit").then("/dialog/resources/network", func stop(); }); + wrappedAsyncTest("checkUserRegistration with ajax error", function() { + xhr.useResult("ajaxError"); + + network.checkUserRegistration("address", function(status) { + ok(false, "ajax error should never call success"); + wrappedStart(); + }, function onFailure() { + ok(true, "ajax error should always call failure"); + wrappedStart(); + }); + + stop(); + }); + wrappedAsyncTest("completeUserRegistration with valid token", function() { network.completeUserRegistration("token", "password", function(registered) { ok(registered); @@ -295,6 +402,7 @@ steal.plugins("jquery", "funcunit/qunit").then("/dialog/resources/network", func wrappedAsyncTest("completeUserRegistration with invalid token", function() { xhr.useResult("invalid"); + network.completeUserRegistration("token", "password", function(registered) { equal(registered, false); wrappedStart(); @@ -306,7 +414,22 @@ steal.plugins("jquery", "funcunit/qunit").then("/dialog/resources/network", func stop(); }); + wrappedAsyncTest("completeUserRegistration with ajax error", function() { + xhr.useResult("ajaxError"); + + network.completeUserRegistration("token", "password", function(registered) { + ok(false, "ajax error should never call success"); + wrappedStart(); + }, function onFailure() { + ok(true, "ajax error should always call failure"); + wrappedStart(); + }); + + stop(); + }); + wrappedAsyncTest("cancelUser valid", function() { + network.cancelUser(function() { // XXX need a test here. ok(true); @@ -320,6 +443,7 @@ steal.plugins("jquery", "funcunit/qunit").then("/dialog/resources/network", func wrappedAsyncTest("cancelUser invalid", function() { xhr.useResult("invalid"); + network.cancelUser(function() { // XXX need a test here. ok(true); @@ -331,6 +455,20 @@ steal.plugins("jquery", "funcunit/qunit").then("/dialog/resources/network", func stop(); }); + wrappedAsyncTest("cancelUser with ajax error", function() { + xhr.useResult("ajaxError"); + + network.cancelUser(function() { + ok(false, "ajax error should never call success"); + wrappedStart(); + }, function onFailure() { + ok(true, "ajax error should always call failure"); + wrappedStart(); + }); + + stop(); + }); + wrappedAsyncTest("emailRegistered with taken email", function() { xhr.useResult("taken"); @@ -359,6 +497,20 @@ steal.plugins("jquery", "funcunit/qunit").then("/dialog/resources/network", func stop(); }); + wrappedAsyncTest("emailRegistered with ajax error", function() { + xhr.useResult("ajaxError"); + + network.emailRegistered("address", function(taken) { + ok(false, "ajax error should never call success"); + wrappedStart(); + }, function onFailure() { + ok(true, "ajax error should always call failure"); + wrappedStart(); + }); + + stop(); + }); + wrappedAsyncTest("addEmail valid", function() { network.addEmail("address", "origin", function onSuccess(added) { @@ -385,6 +537,20 @@ steal.plugins("jquery", "funcunit/qunit").then("/dialog/resources/network", func stop(); }); + wrappedAsyncTest("addEmail with ajax error", function() { + xhr.useResult("ajaxError"); + + network.addEmail("address", "origin", function onSuccess(added) { + ok(false, "ajax error should never call success"); + wrappedStart(); + }, function onFailure() { + ok(true, "ajax error should always call failure"); + wrappedStart(); + }); + + stop(); + }); + wrappedAsyncTest("checkEmailRegistration pending", function() { xhr.useResult("pending"); @@ -413,6 +579,20 @@ steal.plugins("jquery", "funcunit/qunit").then("/dialog/resources/network", func stop(); }); + wrappedAsyncTest("checkEmailRegistration with ajax error", function() { + xhr.useResult("ajaxError"); + + network.checkEmailRegistration("address", function(status) { + ok(false, "ajax error should never call success"); + wrappedStart(); + }, function onFailure() { + ok(true, "ajax error should always call failure"); + wrappedStart(); + }); + + stop(); + }); + wrappedAsyncTest("removeEmail valid", function() { network.removeEmail("validemail", function onSuccess() { @@ -429,6 +609,7 @@ steal.plugins("jquery", "funcunit/qunit").then("/dialog/resources/network", func wrappedAsyncTest("removeEmail invalid", function() { xhr.useResult("invalid"); + network.removeEmail("invalidemail", function onSuccess() { // XXX need a test here; ok(true); @@ -441,15 +622,18 @@ steal.plugins("jquery", "funcunit/qunit").then("/dialog/resources/network", func stop(); }); + wrappedAsyncTest("removeEmail with ajax error", function() { + xhr.useResult("ajaxError"); - wrappedAsyncTest("setKey", function() { - ok(true, "setKey"); - start(); - }); + network.removeEmail("invalidemail", function onSuccess() { + ok(false, "ajax error should never call success"); + wrappedStart(); + }, function onFailure() { + ok(true, "ajax error should always call failure"); + wrappedStart(); + }); - wrappedAsyncTest("syncEmails", function() { - ok(true, "syncEmails"); - start(); + stop(); }); @@ -466,6 +650,20 @@ steal.plugins("jquery", "funcunit/qunit").then("/dialog/resources/network", func stop(); }); + wrappedAsyncTest("requestPasswordReset with ajax error", function() { + xhr.useResult("ajaxError"); + + network.requestPasswordReset("address", "origin", function onSuccess() { + ok(false, "ajax error should never call success"); + wrappedStart(); + }, function onFailure() { + ok(true, "ajax error should always call failure"); + wrappedStart(); + }); + + stop(); + }); + wrappedAsyncTest("resetPassword", function() { network.resetPassword("password", function onSuccess() { // XXX need a test here; @@ -479,6 +677,23 @@ steal.plugins("jquery", "funcunit/qunit").then("/dialog/resources/network", func stop(); }); + wrappedAsyncTest("resetPassword with ajax error", function() { + xhr.useResult("ajaxError"); +/* + the body of this function is not yet written + + network.resetPassword("password", function onSuccess() { + ok(false, "ajax error should never call success"); + wrappedStart(); + }, function onFailure() { + ok(true, "ajax error should always call failure"); + wrappedStart(); + }); + stop(); +*/ + start(); + }); + wrappedAsyncTest("changePassword", function() { network.changePassword("oldpassword", "newpassword", function onSuccess() { // XXX need a real wrappedAsyncTest here. @@ -492,6 +707,24 @@ steal.plugins("jquery", "funcunit/qunit").then("/dialog/resources/network", func stop(); }); + wrappedAsyncTest("changePassword with ajax error", function() { + xhr.useResult("ajaxError"); + + /* + the body of this function is not yet written. + network.changePassword("oldpassword", "newpassword", function onSuccess() { + ok(false, "ajax error should never call success"); + wrappedStart(); + }, function onFailure() { + ok(true, "ajax error should always call failure"); + wrappedStart(); + }); + + stop(); + */ + start(); + }); + wrappedAsyncTest("serverTime", function() { // I am forcing the server time to be 1.25 seconds off. contextInfo.server_time = new Date().getTime() - 1250; @@ -509,4 +742,18 @@ steal.plugins("jquery", "funcunit/qunit").then("/dialog/resources/network", func stop(); }); + + wrappedAsyncTest("serverTime with ajax error before context has been setup", function() { + xhr.useResult("contextAjaxError"); + + network.serverTime(function onSuccess(time) { + ok(false, "ajax error should never call success"); + wrappedStart(); + }, function onFailure() { + ok(true, "ajax error should always call failure"); + wrappedStart(); + }); + + stop(); + }); }); -- GitLab