1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/services/fxaccounts/tests/xpcshell/test_accounts.js Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,650 @@ 1.4 +/* Any copyright is dedicated to the Public Domain. 1.5 + * http://creativecommons.org/publicdomain/zero/1.0/ */ 1.6 + 1.7 +"use strict"; 1.8 + 1.9 +Cu.import("resource://services-common/utils.js"); 1.10 +Cu.import("resource://gre/modules/Services.jsm"); 1.11 +Cu.import("resource://gre/modules/FxAccounts.jsm"); 1.12 +Cu.import("resource://gre/modules/FxAccountsClient.jsm"); 1.13 +Cu.import("resource://gre/modules/FxAccountsCommon.js"); 1.14 +Cu.import("resource://gre/modules/Promise.jsm"); 1.15 +Cu.import("resource://gre/modules/Log.jsm"); 1.16 + 1.17 +const ONE_HOUR_MS = 1000 * 60 * 60; 1.18 +const ONE_DAY_MS = ONE_HOUR_MS * 24; 1.19 +const TWO_MINUTES_MS = 1000 * 60 * 2; 1.20 + 1.21 +initTestLogging("Trace"); 1.22 + 1.23 +// XXX until bug 937114 is fixed 1.24 +Cu.importGlobalProperties(['atob']); 1.25 + 1.26 +let log = Log.repository.getLogger("Services.FxAccounts.test"); 1.27 +log.level = Log.Level.Debug; 1.28 + 1.29 +// See verbose logging from FxAccounts.jsm 1.30 +Services.prefs.setCharPref("identity.fxaccounts.loglevel", "DEBUG"); 1.31 + 1.32 +function run_test() { 1.33 + run_next_test(); 1.34 +} 1.35 + 1.36 +/* 1.37 + * The FxAccountsClient communicates with the remote Firefox 1.38 + * Accounts auth server. Mock the server calls, with a little 1.39 + * lag time to simulate some latency. 1.40 + * 1.41 + * We add the _verified attribute to mock the change in verification 1.42 + * state on the FXA server. 1.43 + */ 1.44 +function MockFxAccountsClient() { 1.45 + this._email = "nobody@example.com"; 1.46 + this._verified = false; 1.47 + 1.48 + // mock calls up to the auth server to determine whether the 1.49 + // user account has been verified 1.50 + this.recoveryEmailStatus = function (sessionToken) { 1.51 + // simulate a call to /recovery_email/status 1.52 + let deferred = Promise.defer(); 1.53 + 1.54 + let response = { 1.55 + email: this._email, 1.56 + verified: this._verified 1.57 + }; 1.58 + deferred.resolve(response); 1.59 + 1.60 + return deferred.promise; 1.61 + }; 1.62 + 1.63 + this.accountKeys = function (keyFetchToken) { 1.64 + let deferred = Promise.defer(); 1.65 + 1.66 + do_timeout(50, () => { 1.67 + let response = { 1.68 + kA: expandBytes("11"), 1.69 + wrapKB: expandBytes("22") 1.70 + }; 1.71 + deferred.resolve(response); 1.72 + }); 1.73 + return deferred.promise; 1.74 + }; 1.75 + 1.76 + this.resendVerificationEmail = function(sessionToken) { 1.77 + // Return the session token to show that we received it in the first place 1.78 + return Promise.resolve(sessionToken); 1.79 + }; 1.80 + 1.81 + this.signCertificate = function() { throw "no" }; 1.82 + 1.83 + this.signOut = function() { return Promise.resolve(); }; 1.84 + 1.85 + FxAccountsClient.apply(this); 1.86 +} 1.87 +MockFxAccountsClient.prototype = { 1.88 + __proto__: FxAccountsClient.prototype 1.89 +} 1.90 + 1.91 +let MockStorage = function() { 1.92 + this.data = null; 1.93 +}; 1.94 +MockStorage.prototype = Object.freeze({ 1.95 + set: function (contents) { 1.96 + this.data = contents; 1.97 + return Promise.resolve(null); 1.98 + }, 1.99 + get: function () { 1.100 + return Promise.resolve(this.data); 1.101 + }, 1.102 +}); 1.103 + 1.104 +/* 1.105 + * We need to mock the FxAccounts module's interfaces to external 1.106 + * services, such as storage and the FxAccounts client. We also 1.107 + * mock the now() method, so that we can simulate the passing of 1.108 + * time and verify that signatures expire correctly. 1.109 + */ 1.110 +function MockFxAccounts() { 1.111 + return new FxAccounts({ 1.112 + _getCertificateSigned_calls: [], 1.113 + _d_signCertificate: Promise.defer(), 1.114 + _now_is: new Date(), 1.115 + signedInUserStorage: new MockStorage(), 1.116 + now: function () { 1.117 + return this._now_is; 1.118 + }, 1.119 + getCertificateSigned: function (sessionToken, serializedPublicKey) { 1.120 + _("mock getCertificateSigned\n"); 1.121 + this._getCertificateSigned_calls.push([sessionToken, serializedPublicKey]); 1.122 + return this._d_signCertificate.promise; 1.123 + }, 1.124 + fxAccountsClient: new MockFxAccountsClient() 1.125 + }); 1.126 +} 1.127 + 1.128 +add_test(function test_non_https_remote_server_uri() { 1.129 + Services.prefs.setCharPref( 1.130 + "identity.fxaccounts.remote.signup.uri", 1.131 + "http://example.com/browser/browser/base/content/test/general/accounts_testRemoteCommands.html"); 1.132 + do_check_throws_message(function () { 1.133 + fxAccounts.getAccountsSignUpURI(); 1.134 + }, "Firefox Accounts server must use HTTPS"); 1.135 + 1.136 + Services.prefs.clearUserPref("identity.fxaccounts.remote.signup.uri"); 1.137 + 1.138 + run_next_test(); 1.139 +}); 1.140 + 1.141 +add_task(function test_get_signed_in_user_initially_unset() { 1.142 + // This test, unlike the rest, uses an un-mocked FxAccounts instance. 1.143 + // However, we still need to pass an object to the constructor to 1.144 + // force it to expose "internal", so we can test the disk storage. 1.145 + let account = new FxAccounts({onlySetInternal: true}) 1.146 + let credentials = { 1.147 + email: "foo@example.com", 1.148 + uid: "1234@lcip.org", 1.149 + assertion: "foobar", 1.150 + sessionToken: "dead", 1.151 + kA: "beef", 1.152 + kB: "cafe", 1.153 + verified: true 1.154 + }; 1.155 + 1.156 + let result = yield account.getSignedInUser(); 1.157 + do_check_eq(result, null); 1.158 + 1.159 + yield account.setSignedInUser(credentials); 1.160 + 1.161 + let result = yield account.getSignedInUser(); 1.162 + do_check_eq(result.email, credentials.email); 1.163 + do_check_eq(result.assertion, credentials.assertion); 1.164 + do_check_eq(result.kB, credentials.kB); 1.165 + 1.166 + // Delete the memory cache and force the user 1.167 + // to be read and parsed from storage (e.g. disk via JSONStorage). 1.168 + delete account.internal.signedInUser; 1.169 + let result = yield account.getSignedInUser(); 1.170 + do_check_eq(result.email, credentials.email); 1.171 + do_check_eq(result.assertion, credentials.assertion); 1.172 + do_check_eq(result.kB, credentials.kB); 1.173 + 1.174 + // sign out 1.175 + let localOnly = true; 1.176 + yield account.signOut(localOnly); 1.177 + 1.178 + // user should be undefined after sign out 1.179 + let result = yield account.getSignedInUser(); 1.180 + do_check_eq(result, null); 1.181 +}); 1.182 + 1.183 +// Sanity-check that our mocked client is working correctly 1.184 +add_test(function test_client_mock() { 1.185 + do_test_pending(); 1.186 + 1.187 + let fxa = new MockFxAccounts(); 1.188 + let client = fxa.internal.fxAccountsClient; 1.189 + do_check_eq(client._verified, false); 1.190 + do_check_eq(typeof client.signIn, "function"); 1.191 + 1.192 + // The recoveryEmailStatus function eventually fulfills its promise 1.193 + client.recoveryEmailStatus() 1.194 + .then(response => { 1.195 + do_check_eq(response.verified, false); 1.196 + do_test_finished(); 1.197 + run_next_test(); 1.198 + }); 1.199 +}); 1.200 + 1.201 +// Sign in a user, and after a little while, verify the user's email. 1.202 +// Right after signing in the user, we should get the 'onlogin' notification. 1.203 +// Polling should detect that the email is verified, and eventually 1.204 +// 'onverified' should be observed 1.205 +add_test(function test_verification_poll() { 1.206 + do_test_pending(); 1.207 + 1.208 + let fxa = new MockFxAccounts(); 1.209 + let test_user = getTestUser("francine"); 1.210 + let login_notification_received = false; 1.211 + 1.212 + makeObserver(ONVERIFIED_NOTIFICATION, function() { 1.213 + log.debug("test_verification_poll observed onverified"); 1.214 + // Once email verification is complete, we will observe onverified 1.215 + fxa.internal.getUserAccountData().then(user => { 1.216 + // And confirm that the user's state has changed 1.217 + do_check_eq(user.verified, true); 1.218 + do_check_eq(user.email, test_user.email); 1.219 + do_check_true(login_notification_received); 1.220 + do_test_finished(); 1.221 + run_next_test(); 1.222 + }); 1.223 + }); 1.224 + 1.225 + makeObserver(ONLOGIN_NOTIFICATION, function() { 1.226 + log.debug("test_verification_poll observer onlogin"); 1.227 + login_notification_received = true; 1.228 + }); 1.229 + 1.230 + fxa.setSignedInUser(test_user).then(() => { 1.231 + fxa.internal.getUserAccountData().then(user => { 1.232 + // The user is signing in, but email has not been verified yet 1.233 + do_check_eq(user.verified, false); 1.234 + do_timeout(200, function() { 1.235 + log.debug("Mocking verification of francine's email"); 1.236 + fxa.internal.fxAccountsClient._email = test_user.email; 1.237 + fxa.internal.fxAccountsClient._verified = true; 1.238 + }); 1.239 + }); 1.240 + }); 1.241 +}); 1.242 + 1.243 +// Sign in the user, but never verify the email. The check-email 1.244 +// poll should time out. No verifiedlogin event should be observed, and the 1.245 +// internal whenVerified promise should be rejected 1.246 +add_test(function test_polling_timeout() { 1.247 + do_test_pending(); 1.248 + 1.249 + // This test could be better - the onverified observer might fire on 1.250 + // somebody else's stack, and we're not making sure that we're not receiving 1.251 + // such a message. In other words, this tests either failure, or success, but 1.252 + // not both. 1.253 + 1.254 + let fxa = new MockFxAccounts(); 1.255 + let test_user = getTestUser("carol"); 1.256 + 1.257 + let removeObserver = makeObserver(ONVERIFIED_NOTIFICATION, function() { 1.258 + do_throw("We should not be getting a login event!"); 1.259 + }); 1.260 + 1.261 + fxa.internal.POLL_SESSION = 1; 1.262 + fxa.internal.POLL_STEP = 2; 1.263 + 1.264 + let p = fxa.internal.whenVerified({}); 1.265 + 1.266 + fxa.setSignedInUser(test_user).then(() => { 1.267 + p.then( 1.268 + (success) => { 1.269 + do_throw("this should not succeed"); 1.270 + }, 1.271 + (fail) => { 1.272 + removeObserver(); 1.273 + do_test_finished(); 1.274 + run_next_test(); 1.275 + } 1.276 + ); 1.277 + }); 1.278 +}); 1.279 + 1.280 +add_test(function test_getKeys() { 1.281 + do_test_pending(); 1.282 + let fxa = new MockFxAccounts(); 1.283 + let user = getTestUser("eusebius"); 1.284 + 1.285 + // Once email has been verified, we will be able to get keys 1.286 + user.verified = true; 1.287 + 1.288 + fxa.setSignedInUser(user).then(() => { 1.289 + fxa.getSignedInUser().then((user) => { 1.290 + // Before getKeys, we have no keys 1.291 + do_check_eq(!!user.kA, false); 1.292 + do_check_eq(!!user.kB, false); 1.293 + // And we still have a key-fetch token to use 1.294 + do_check_eq(!!user.keyFetchToken, true); 1.295 + 1.296 + fxa.internal.getKeys().then(() => { 1.297 + fxa.getSignedInUser().then((user) => { 1.298 + // Now we should have keys 1.299 + do_check_eq(fxa.internal.isUserEmailVerified(user), true); 1.300 + do_check_eq(!!user.verified, true); 1.301 + do_check_eq(user.kA, expandHex("11")); 1.302 + do_check_eq(user.kB, expandHex("66")); 1.303 + do_check_eq(user.keyFetchToken, undefined); 1.304 + do_test_finished(); 1.305 + run_next_test(); 1.306 + }); 1.307 + }); 1.308 + }); 1.309 + }); 1.310 +}); 1.311 + 1.312 +// fetchAndUnwrapKeys with no keyFetchToken should trigger signOut 1.313 +add_test(function test_fetchAndUnwrapKeys_no_token() { 1.314 + do_test_pending(); 1.315 + 1.316 + let fxa = new MockFxAccounts(); 1.317 + let user = getTestUser("lettuce.protheroe"); 1.318 + delete user.keyFetchToken 1.319 + 1.320 + makeObserver(ONLOGOUT_NOTIFICATION, function() { 1.321 + log.debug("test_fetchAndUnwrapKeys_no_token observed logout"); 1.322 + fxa.internal.getUserAccountData().then(user => { 1.323 + do_test_finished(); 1.324 + run_next_test(); 1.325 + }); 1.326 + }); 1.327 + 1.328 + fxa.setSignedInUser(user).then((user) => { 1.329 + fxa.internal.fetchAndUnwrapKeys(); 1.330 + }); 1.331 +}); 1.332 + 1.333 +// Alice (User A) signs up but never verifies her email. Then Bob (User B) 1.334 +// signs in with a verified email. Ensure that no sign-in events are triggered 1.335 +// on Alice's behalf. In the end, Bob should be the signed-in user. 1.336 +add_test(function test_overlapping_signins() { 1.337 + do_test_pending(); 1.338 + 1.339 + let fxa = new MockFxAccounts(); 1.340 + let alice = getTestUser("alice"); 1.341 + let bob = getTestUser("bob"); 1.342 + 1.343 + makeObserver(ONVERIFIED_NOTIFICATION, function() { 1.344 + log.debug("test_overlapping_signins observed onverified"); 1.345 + // Once email verification is complete, we will observe onverified 1.346 + fxa.internal.getUserAccountData().then(user => { 1.347 + do_check_eq(user.email, bob.email); 1.348 + do_check_eq(user.verified, true); 1.349 + do_test_finished(); 1.350 + run_next_test(); 1.351 + }); 1.352 + }); 1.353 + 1.354 + // Alice is the user signing in; her email is unverified. 1.355 + fxa.setSignedInUser(alice).then(() => { 1.356 + log.debug("Alice signing in ..."); 1.357 + fxa.internal.getUserAccountData().then(user => { 1.358 + do_check_eq(user.email, alice.email); 1.359 + do_check_eq(user.verified, false); 1.360 + log.debug("Alice has not verified her email ..."); 1.361 + 1.362 + // Now Bob signs in instead and actually verifies his email 1.363 + log.debug("Bob signing in ..."); 1.364 + fxa.setSignedInUser(bob).then(() => { 1.365 + do_timeout(200, function() { 1.366 + // Mock email verification ... 1.367 + log.debug("Bob verifying his email ..."); 1.368 + fxa.internal.fxAccountsClient._verified = true; 1.369 + }); 1.370 + }); 1.371 + }); 1.372 + }); 1.373 +}); 1.374 + 1.375 +add_task(function test_getAssertion() { 1.376 + let fxa = new MockFxAccounts(); 1.377 + 1.378 + do_check_throws(function() { 1.379 + yield fxa.getAssertion("nonaudience"); 1.380 + }); 1.381 + 1.382 + let creds = { 1.383 + sessionToken: "sessionToken", 1.384 + kA: expandHex("11"), 1.385 + kB: expandHex("66"), 1.386 + verified: true 1.387 + }; 1.388 + // By putting kA/kB/verified in "creds", we skip ahead 1.389 + // to the "we're ready" stage. 1.390 + yield fxa.setSignedInUser(creds); 1.391 + 1.392 + _("== ready to go\n"); 1.393 + // Start with a nice arbitrary but realistic date. Here we use a nice RFC 1.394 + // 1123 date string like we would get from an HTTP header. Over the course of 1.395 + // the test, we will update 'now', but leave 'start' where it is. 1.396 + let now = Date.parse("Mon, 13 Jan 2014 21:45:06 GMT"); 1.397 + let start = now; 1.398 + fxa.internal._now_is = now; 1.399 + 1.400 + let d = fxa.getAssertion("audience.example.com"); 1.401 + // At this point, a thread has been spawned to generate the keys. 1.402 + _("-- back from fxa.getAssertion\n"); 1.403 + fxa.internal._d_signCertificate.resolve("cert1"); 1.404 + let assertion = yield d; 1.405 + do_check_eq(fxa.internal._getCertificateSigned_calls.length, 1); 1.406 + do_check_eq(fxa.internal._getCertificateSigned_calls[0][0], "sessionToken"); 1.407 + do_check_neq(assertion, null); 1.408 + _("ASSERTION: " + assertion + "\n"); 1.409 + let pieces = assertion.split("~"); 1.410 + do_check_eq(pieces[0], "cert1"); 1.411 + let keyPair = fxa.internal.currentAccountState.keyPair; 1.412 + let cert = fxa.internal.currentAccountState.cert; 1.413 + do_check_neq(keyPair, undefined); 1.414 + _(keyPair.validUntil + "\n"); 1.415 + let p2 = pieces[1].split("."); 1.416 + let header = JSON.parse(atob(p2[0])); 1.417 + _("HEADER: " + JSON.stringify(header) + "\n"); 1.418 + do_check_eq(header.alg, "DS128"); 1.419 + let payload = JSON.parse(atob(p2[1])); 1.420 + _("PAYLOAD: " + JSON.stringify(payload) + "\n"); 1.421 + do_check_eq(payload.aud, "audience.example.com"); 1.422 + do_check_eq(keyPair.validUntil, start + KEY_LIFETIME); 1.423 + do_check_eq(cert.validUntil, start + CERT_LIFETIME); 1.424 + _("delta: " + Date.parse(payload.exp - start) + "\n"); 1.425 + let exp = Number(payload.exp); 1.426 + 1.427 + do_check_eq(exp, now + ASSERTION_LIFETIME); 1.428 + 1.429 + // Reset for next call. 1.430 + fxa.internal._d_signCertificate = Promise.defer(); 1.431 + 1.432 + // Getting a new assertion "soon" (i.e., w/o incrementing "now"), even for 1.433 + // a new audience, should not provoke key generation or a signing request. 1.434 + assertion = yield fxa.getAssertion("other.example.com"); 1.435 + 1.436 + // There were no additional calls - same number of getcert calls as before 1.437 + do_check_eq(fxa.internal._getCertificateSigned_calls.length, 1); 1.438 + 1.439 + // Wait an hour; assertion use period expires, but not the certificate 1.440 + now += ONE_HOUR_MS; 1.441 + fxa.internal._now_is = now; 1.442 + 1.443 + // This won't block on anything - will make an assertion, but not get a 1.444 + // new certificate. 1.445 + assertion = yield fxa.getAssertion("third.example.com"); 1.446 + 1.447 + // Test will time out if that failed (i.e., if that had to go get a new cert) 1.448 + pieces = assertion.split("~"); 1.449 + do_check_eq(pieces[0], "cert1"); 1.450 + p2 = pieces[1].split("."); 1.451 + header = JSON.parse(atob(p2[0])); 1.452 + payload = JSON.parse(atob(p2[1])); 1.453 + do_check_eq(payload.aud, "third.example.com"); 1.454 + 1.455 + // The keypair and cert should have the same validity as before, but the 1.456 + // expiration time of the assertion should be different. We compare this to 1.457 + // the initial start time, to which they are relative, not the current value 1.458 + // of "now". 1.459 + 1.460 + keyPair = fxa.internal.currentAccountState.keyPair; 1.461 + cert = fxa.internal.currentAccountState.cert; 1.462 + do_check_eq(keyPair.validUntil, start + KEY_LIFETIME); 1.463 + do_check_eq(cert.validUntil, start + CERT_LIFETIME); 1.464 + exp = Number(payload.exp); 1.465 + do_check_eq(exp, now + ASSERTION_LIFETIME); 1.466 + 1.467 + // Now we wait even longer, and expect both assertion and cert to expire. So 1.468 + // we will have to get a new keypair and cert. 1.469 + now += ONE_DAY_MS; 1.470 + fxa.internal._now_is = now; 1.471 + d = fxa.getAssertion("fourth.example.com"); 1.472 + fxa.internal._d_signCertificate.resolve("cert2"); 1.473 + assertion = yield d; 1.474 + do_check_eq(fxa.internal._getCertificateSigned_calls.length, 2); 1.475 + do_check_eq(fxa.internal._getCertificateSigned_calls[1][0], "sessionToken"); 1.476 + pieces = assertion.split("~"); 1.477 + do_check_eq(pieces[0], "cert2"); 1.478 + p2 = pieces[1].split("."); 1.479 + header = JSON.parse(atob(p2[0])); 1.480 + payload = JSON.parse(atob(p2[1])); 1.481 + do_check_eq(payload.aud, "fourth.example.com"); 1.482 + keyPair = fxa.internal.currentAccountState.keyPair; 1.483 + cert = fxa.internal.currentAccountState.cert; 1.484 + do_check_eq(keyPair.validUntil, now + KEY_LIFETIME); 1.485 + do_check_eq(cert.validUntil, now + CERT_LIFETIME); 1.486 + exp = Number(payload.exp); 1.487 + 1.488 + do_check_eq(exp, now + ASSERTION_LIFETIME); 1.489 + _("----- DONE ----\n"); 1.490 +}); 1.491 + 1.492 +add_task(function test_resend_email_not_signed_in() { 1.493 + let fxa = new MockFxAccounts(); 1.494 + 1.495 + try { 1.496 + yield fxa.resendVerificationEmail(); 1.497 + } catch(err) { 1.498 + do_check_eq(err.message, 1.499 + "Cannot resend verification email; no signed-in user"); 1.500 + return; 1.501 + } 1.502 + do_throw("Should not be able to resend email when nobody is signed in"); 1.503 +}); 1.504 + 1.505 +add_test(function test_resend_email() { 1.506 + let fxa = new MockFxAccounts(); 1.507 + let alice = getTestUser("alice"); 1.508 + 1.509 + let initialState = fxa.internal.currentAccountState; 1.510 + 1.511 + // Alice is the user signing in; her email is unverified. 1.512 + fxa.setSignedInUser(alice).then(() => { 1.513 + log.debug("Alice signing in"); 1.514 + 1.515 + // We're polling for the first email 1.516 + do_check_true(fxa.internal.currentAccountState !== initialState); 1.517 + let aliceState = fxa.internal.currentAccountState; 1.518 + 1.519 + // The polling timer is ticking 1.520 + do_check_true(fxa.internal.currentTimer > 0); 1.521 + 1.522 + fxa.internal.getUserAccountData().then(user => { 1.523 + do_check_eq(user.email, alice.email); 1.524 + do_check_eq(user.verified, false); 1.525 + log.debug("Alice wants verification email resent"); 1.526 + 1.527 + fxa.resendVerificationEmail().then((result) => { 1.528 + // Mock server response; ensures that the session token actually was 1.529 + // passed to the client to make the hawk call 1.530 + do_check_eq(result, "alice's session token"); 1.531 + 1.532 + // Timer was not restarted 1.533 + do_check_true(fxa.internal.currentAccountState === aliceState); 1.534 + 1.535 + // Timer is still ticking 1.536 + do_check_true(fxa.internal.currentTimer > 0); 1.537 + 1.538 + // Ok abort polling before we go on to the next test 1.539 + fxa.internal.abortExistingFlow(); 1.540 + run_next_test(); 1.541 + }); 1.542 + }); 1.543 + }); 1.544 +}); 1.545 + 1.546 +add_test(function test_sign_out() { 1.547 + do_test_pending(); 1.548 + let fxa = new MockFxAccounts(); 1.549 + let remoteSignOutCalled = false; 1.550 + let client = fxa.internal.fxAccountsClient; 1.551 + client.signOut = function() { remoteSignOutCalled = true; return Promise.resolve(); }; 1.552 + makeObserver(ONLOGOUT_NOTIFICATION, function() { 1.553 + log.debug("test_sign_out_with_remote_error observed onlogout"); 1.554 + // user should be undefined after sign out 1.555 + fxa.internal.getUserAccountData().then(user => { 1.556 + do_check_eq(user, null); 1.557 + do_check_true(remoteSignOutCalled); 1.558 + do_test_finished(); 1.559 + run_next_test(); 1.560 + }); 1.561 + }); 1.562 + fxa.signOut(); 1.563 +}); 1.564 + 1.565 +add_test(function test_sign_out_with_remote_error() { 1.566 + do_test_pending(); 1.567 + let fxa = new MockFxAccounts(); 1.568 + let client = fxa.internal.fxAccountsClient; 1.569 + let remoteSignOutCalled = false; 1.570 + // Force remote sign out to trigger an error 1.571 + client.signOut = function() { remoteSignOutCalled = true; throw "Remote sign out error"; }; 1.572 + makeObserver(ONLOGOUT_NOTIFICATION, function() { 1.573 + log.debug("test_sign_out_with_remote_error observed onlogout"); 1.574 + // user should be undefined after sign out 1.575 + fxa.internal.getUserAccountData().then(user => { 1.576 + do_check_eq(user, null); 1.577 + do_check_true(remoteSignOutCalled); 1.578 + do_test_finished(); 1.579 + run_next_test(); 1.580 + }); 1.581 + }); 1.582 + fxa.signOut(); 1.583 +}); 1.584 + 1.585 +/* 1.586 + * End of tests. 1.587 + * Utility functions follow. 1.588 + */ 1.589 + 1.590 +function expandHex(two_hex) { 1.591 + // Return a 64-character hex string, encoding 32 identical bytes. 1.592 + let eight_hex = two_hex + two_hex + two_hex + two_hex; 1.593 + let thirtytwo_hex = eight_hex + eight_hex + eight_hex + eight_hex; 1.594 + return thirtytwo_hex + thirtytwo_hex; 1.595 +}; 1.596 + 1.597 +function expandBytes(two_hex) { 1.598 + return CommonUtils.hexToBytes(expandHex(two_hex)); 1.599 +}; 1.600 + 1.601 +function getTestUser(name) { 1.602 + return { 1.603 + email: name + "@example.com", 1.604 + uid: "1ad7f502-4cc7-4ec1-a209-071fd2fae348", 1.605 + sessionToken: name + "'s session token", 1.606 + keyFetchToken: name + "'s keyfetch token", 1.607 + unwrapBKey: expandHex("44"), 1.608 + verified: false 1.609 + }; 1.610 +} 1.611 + 1.612 +function makeObserver(aObserveTopic, aObserveFunc) { 1.613 + let observer = { 1.614 + // nsISupports provides type management in C++ 1.615 + // nsIObserver is to be an observer 1.616 + QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports, Ci.nsIObserver]), 1.617 + 1.618 + observe: function (aSubject, aTopic, aData) { 1.619 + log.debug("observed " + aTopic + " " + aData); 1.620 + if (aTopic == aObserveTopic) { 1.621 + removeMe(); 1.622 + aObserveFunc(aSubject, aTopic, aData); 1.623 + } 1.624 + } 1.625 + }; 1.626 + 1.627 + function removeMe() { 1.628 + log.debug("removing observer for " + aObserveTopic); 1.629 + Services.obs.removeObserver(observer, aObserveTopic); 1.630 + } 1.631 + 1.632 + Services.obs.addObserver(observer, aObserveTopic, false); 1.633 + return removeMe; 1.634 +} 1.635 + 1.636 +function do_check_throws(func, result, stack) 1.637 +{ 1.638 + if (!stack) 1.639 + stack = Components.stack.caller; 1.640 + 1.641 + try { 1.642 + func(); 1.643 + } catch (ex) { 1.644 + if (ex.name == result) { 1.645 + return; 1.646 + } 1.647 + do_throw("Expected result " + result + ", caught " + ex, stack); 1.648 + } 1.649 + 1.650 + if (result) { 1.651 + do_throw("Expected result " + result + ", none thrown", stack); 1.652 + } 1.653 +}