services/fxaccounts/tests/xpcshell/test_accounts.js

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6

michael@0 1 /* Any copyright is dedicated to the Public Domain.
michael@0 2 * http://creativecommons.org/publicdomain/zero/1.0/ */
michael@0 3
michael@0 4 "use strict";
michael@0 5
michael@0 6 Cu.import("resource://services-common/utils.js");
michael@0 7 Cu.import("resource://gre/modules/Services.jsm");
michael@0 8 Cu.import("resource://gre/modules/FxAccounts.jsm");
michael@0 9 Cu.import("resource://gre/modules/FxAccountsClient.jsm");
michael@0 10 Cu.import("resource://gre/modules/FxAccountsCommon.js");
michael@0 11 Cu.import("resource://gre/modules/Promise.jsm");
michael@0 12 Cu.import("resource://gre/modules/Log.jsm");
michael@0 13
michael@0 14 const ONE_HOUR_MS = 1000 * 60 * 60;
michael@0 15 const ONE_DAY_MS = ONE_HOUR_MS * 24;
michael@0 16 const TWO_MINUTES_MS = 1000 * 60 * 2;
michael@0 17
michael@0 18 initTestLogging("Trace");
michael@0 19
michael@0 20 // XXX until bug 937114 is fixed
michael@0 21 Cu.importGlobalProperties(['atob']);
michael@0 22
michael@0 23 let log = Log.repository.getLogger("Services.FxAccounts.test");
michael@0 24 log.level = Log.Level.Debug;
michael@0 25
michael@0 26 // See verbose logging from FxAccounts.jsm
michael@0 27 Services.prefs.setCharPref("identity.fxaccounts.loglevel", "DEBUG");
michael@0 28
michael@0 29 function run_test() {
michael@0 30 run_next_test();
michael@0 31 }
michael@0 32
michael@0 33 /*
michael@0 34 * The FxAccountsClient communicates with the remote Firefox
michael@0 35 * Accounts auth server. Mock the server calls, with a little
michael@0 36 * lag time to simulate some latency.
michael@0 37 *
michael@0 38 * We add the _verified attribute to mock the change in verification
michael@0 39 * state on the FXA server.
michael@0 40 */
michael@0 41 function MockFxAccountsClient() {
michael@0 42 this._email = "nobody@example.com";
michael@0 43 this._verified = false;
michael@0 44
michael@0 45 // mock calls up to the auth server to determine whether the
michael@0 46 // user account has been verified
michael@0 47 this.recoveryEmailStatus = function (sessionToken) {
michael@0 48 // simulate a call to /recovery_email/status
michael@0 49 let deferred = Promise.defer();
michael@0 50
michael@0 51 let response = {
michael@0 52 email: this._email,
michael@0 53 verified: this._verified
michael@0 54 };
michael@0 55 deferred.resolve(response);
michael@0 56
michael@0 57 return deferred.promise;
michael@0 58 };
michael@0 59
michael@0 60 this.accountKeys = function (keyFetchToken) {
michael@0 61 let deferred = Promise.defer();
michael@0 62
michael@0 63 do_timeout(50, () => {
michael@0 64 let response = {
michael@0 65 kA: expandBytes("11"),
michael@0 66 wrapKB: expandBytes("22")
michael@0 67 };
michael@0 68 deferred.resolve(response);
michael@0 69 });
michael@0 70 return deferred.promise;
michael@0 71 };
michael@0 72
michael@0 73 this.resendVerificationEmail = function(sessionToken) {
michael@0 74 // Return the session token to show that we received it in the first place
michael@0 75 return Promise.resolve(sessionToken);
michael@0 76 };
michael@0 77
michael@0 78 this.signCertificate = function() { throw "no" };
michael@0 79
michael@0 80 this.signOut = function() { return Promise.resolve(); };
michael@0 81
michael@0 82 FxAccountsClient.apply(this);
michael@0 83 }
michael@0 84 MockFxAccountsClient.prototype = {
michael@0 85 __proto__: FxAccountsClient.prototype
michael@0 86 }
michael@0 87
michael@0 88 let MockStorage = function() {
michael@0 89 this.data = null;
michael@0 90 };
michael@0 91 MockStorage.prototype = Object.freeze({
michael@0 92 set: function (contents) {
michael@0 93 this.data = contents;
michael@0 94 return Promise.resolve(null);
michael@0 95 },
michael@0 96 get: function () {
michael@0 97 return Promise.resolve(this.data);
michael@0 98 },
michael@0 99 });
michael@0 100
michael@0 101 /*
michael@0 102 * We need to mock the FxAccounts module's interfaces to external
michael@0 103 * services, such as storage and the FxAccounts client. We also
michael@0 104 * mock the now() method, so that we can simulate the passing of
michael@0 105 * time and verify that signatures expire correctly.
michael@0 106 */
michael@0 107 function MockFxAccounts() {
michael@0 108 return new FxAccounts({
michael@0 109 _getCertificateSigned_calls: [],
michael@0 110 _d_signCertificate: Promise.defer(),
michael@0 111 _now_is: new Date(),
michael@0 112 signedInUserStorage: new MockStorage(),
michael@0 113 now: function () {
michael@0 114 return this._now_is;
michael@0 115 },
michael@0 116 getCertificateSigned: function (sessionToken, serializedPublicKey) {
michael@0 117 _("mock getCertificateSigned\n");
michael@0 118 this._getCertificateSigned_calls.push([sessionToken, serializedPublicKey]);
michael@0 119 return this._d_signCertificate.promise;
michael@0 120 },
michael@0 121 fxAccountsClient: new MockFxAccountsClient()
michael@0 122 });
michael@0 123 }
michael@0 124
michael@0 125 add_test(function test_non_https_remote_server_uri() {
michael@0 126 Services.prefs.setCharPref(
michael@0 127 "identity.fxaccounts.remote.signup.uri",
michael@0 128 "http://example.com/browser/browser/base/content/test/general/accounts_testRemoteCommands.html");
michael@0 129 do_check_throws_message(function () {
michael@0 130 fxAccounts.getAccountsSignUpURI();
michael@0 131 }, "Firefox Accounts server must use HTTPS");
michael@0 132
michael@0 133 Services.prefs.clearUserPref("identity.fxaccounts.remote.signup.uri");
michael@0 134
michael@0 135 run_next_test();
michael@0 136 });
michael@0 137
michael@0 138 add_task(function test_get_signed_in_user_initially_unset() {
michael@0 139 // This test, unlike the rest, uses an un-mocked FxAccounts instance.
michael@0 140 // However, we still need to pass an object to the constructor to
michael@0 141 // force it to expose "internal", so we can test the disk storage.
michael@0 142 let account = new FxAccounts({onlySetInternal: true})
michael@0 143 let credentials = {
michael@0 144 email: "foo@example.com",
michael@0 145 uid: "1234@lcip.org",
michael@0 146 assertion: "foobar",
michael@0 147 sessionToken: "dead",
michael@0 148 kA: "beef",
michael@0 149 kB: "cafe",
michael@0 150 verified: true
michael@0 151 };
michael@0 152
michael@0 153 let result = yield account.getSignedInUser();
michael@0 154 do_check_eq(result, null);
michael@0 155
michael@0 156 yield account.setSignedInUser(credentials);
michael@0 157
michael@0 158 let result = yield account.getSignedInUser();
michael@0 159 do_check_eq(result.email, credentials.email);
michael@0 160 do_check_eq(result.assertion, credentials.assertion);
michael@0 161 do_check_eq(result.kB, credentials.kB);
michael@0 162
michael@0 163 // Delete the memory cache and force the user
michael@0 164 // to be read and parsed from storage (e.g. disk via JSONStorage).
michael@0 165 delete account.internal.signedInUser;
michael@0 166 let result = yield account.getSignedInUser();
michael@0 167 do_check_eq(result.email, credentials.email);
michael@0 168 do_check_eq(result.assertion, credentials.assertion);
michael@0 169 do_check_eq(result.kB, credentials.kB);
michael@0 170
michael@0 171 // sign out
michael@0 172 let localOnly = true;
michael@0 173 yield account.signOut(localOnly);
michael@0 174
michael@0 175 // user should be undefined after sign out
michael@0 176 let result = yield account.getSignedInUser();
michael@0 177 do_check_eq(result, null);
michael@0 178 });
michael@0 179
michael@0 180 // Sanity-check that our mocked client is working correctly
michael@0 181 add_test(function test_client_mock() {
michael@0 182 do_test_pending();
michael@0 183
michael@0 184 let fxa = new MockFxAccounts();
michael@0 185 let client = fxa.internal.fxAccountsClient;
michael@0 186 do_check_eq(client._verified, false);
michael@0 187 do_check_eq(typeof client.signIn, "function");
michael@0 188
michael@0 189 // The recoveryEmailStatus function eventually fulfills its promise
michael@0 190 client.recoveryEmailStatus()
michael@0 191 .then(response => {
michael@0 192 do_check_eq(response.verified, false);
michael@0 193 do_test_finished();
michael@0 194 run_next_test();
michael@0 195 });
michael@0 196 });
michael@0 197
michael@0 198 // Sign in a user, and after a little while, verify the user's email.
michael@0 199 // Right after signing in the user, we should get the 'onlogin' notification.
michael@0 200 // Polling should detect that the email is verified, and eventually
michael@0 201 // 'onverified' should be observed
michael@0 202 add_test(function test_verification_poll() {
michael@0 203 do_test_pending();
michael@0 204
michael@0 205 let fxa = new MockFxAccounts();
michael@0 206 let test_user = getTestUser("francine");
michael@0 207 let login_notification_received = false;
michael@0 208
michael@0 209 makeObserver(ONVERIFIED_NOTIFICATION, function() {
michael@0 210 log.debug("test_verification_poll observed onverified");
michael@0 211 // Once email verification is complete, we will observe onverified
michael@0 212 fxa.internal.getUserAccountData().then(user => {
michael@0 213 // And confirm that the user's state has changed
michael@0 214 do_check_eq(user.verified, true);
michael@0 215 do_check_eq(user.email, test_user.email);
michael@0 216 do_check_true(login_notification_received);
michael@0 217 do_test_finished();
michael@0 218 run_next_test();
michael@0 219 });
michael@0 220 });
michael@0 221
michael@0 222 makeObserver(ONLOGIN_NOTIFICATION, function() {
michael@0 223 log.debug("test_verification_poll observer onlogin");
michael@0 224 login_notification_received = true;
michael@0 225 });
michael@0 226
michael@0 227 fxa.setSignedInUser(test_user).then(() => {
michael@0 228 fxa.internal.getUserAccountData().then(user => {
michael@0 229 // The user is signing in, but email has not been verified yet
michael@0 230 do_check_eq(user.verified, false);
michael@0 231 do_timeout(200, function() {
michael@0 232 log.debug("Mocking verification of francine's email");
michael@0 233 fxa.internal.fxAccountsClient._email = test_user.email;
michael@0 234 fxa.internal.fxAccountsClient._verified = true;
michael@0 235 });
michael@0 236 });
michael@0 237 });
michael@0 238 });
michael@0 239
michael@0 240 // Sign in the user, but never verify the email. The check-email
michael@0 241 // poll should time out. No verifiedlogin event should be observed, and the
michael@0 242 // internal whenVerified promise should be rejected
michael@0 243 add_test(function test_polling_timeout() {
michael@0 244 do_test_pending();
michael@0 245
michael@0 246 // This test could be better - the onverified observer might fire on
michael@0 247 // somebody else's stack, and we're not making sure that we're not receiving
michael@0 248 // such a message. In other words, this tests either failure, or success, but
michael@0 249 // not both.
michael@0 250
michael@0 251 let fxa = new MockFxAccounts();
michael@0 252 let test_user = getTestUser("carol");
michael@0 253
michael@0 254 let removeObserver = makeObserver(ONVERIFIED_NOTIFICATION, function() {
michael@0 255 do_throw("We should not be getting a login event!");
michael@0 256 });
michael@0 257
michael@0 258 fxa.internal.POLL_SESSION = 1;
michael@0 259 fxa.internal.POLL_STEP = 2;
michael@0 260
michael@0 261 let p = fxa.internal.whenVerified({});
michael@0 262
michael@0 263 fxa.setSignedInUser(test_user).then(() => {
michael@0 264 p.then(
michael@0 265 (success) => {
michael@0 266 do_throw("this should not succeed");
michael@0 267 },
michael@0 268 (fail) => {
michael@0 269 removeObserver();
michael@0 270 do_test_finished();
michael@0 271 run_next_test();
michael@0 272 }
michael@0 273 );
michael@0 274 });
michael@0 275 });
michael@0 276
michael@0 277 add_test(function test_getKeys() {
michael@0 278 do_test_pending();
michael@0 279 let fxa = new MockFxAccounts();
michael@0 280 let user = getTestUser("eusebius");
michael@0 281
michael@0 282 // Once email has been verified, we will be able to get keys
michael@0 283 user.verified = true;
michael@0 284
michael@0 285 fxa.setSignedInUser(user).then(() => {
michael@0 286 fxa.getSignedInUser().then((user) => {
michael@0 287 // Before getKeys, we have no keys
michael@0 288 do_check_eq(!!user.kA, false);
michael@0 289 do_check_eq(!!user.kB, false);
michael@0 290 // And we still have a key-fetch token to use
michael@0 291 do_check_eq(!!user.keyFetchToken, true);
michael@0 292
michael@0 293 fxa.internal.getKeys().then(() => {
michael@0 294 fxa.getSignedInUser().then((user) => {
michael@0 295 // Now we should have keys
michael@0 296 do_check_eq(fxa.internal.isUserEmailVerified(user), true);
michael@0 297 do_check_eq(!!user.verified, true);
michael@0 298 do_check_eq(user.kA, expandHex("11"));
michael@0 299 do_check_eq(user.kB, expandHex("66"));
michael@0 300 do_check_eq(user.keyFetchToken, undefined);
michael@0 301 do_test_finished();
michael@0 302 run_next_test();
michael@0 303 });
michael@0 304 });
michael@0 305 });
michael@0 306 });
michael@0 307 });
michael@0 308
michael@0 309 // fetchAndUnwrapKeys with no keyFetchToken should trigger signOut
michael@0 310 add_test(function test_fetchAndUnwrapKeys_no_token() {
michael@0 311 do_test_pending();
michael@0 312
michael@0 313 let fxa = new MockFxAccounts();
michael@0 314 let user = getTestUser("lettuce.protheroe");
michael@0 315 delete user.keyFetchToken
michael@0 316
michael@0 317 makeObserver(ONLOGOUT_NOTIFICATION, function() {
michael@0 318 log.debug("test_fetchAndUnwrapKeys_no_token observed logout");
michael@0 319 fxa.internal.getUserAccountData().then(user => {
michael@0 320 do_test_finished();
michael@0 321 run_next_test();
michael@0 322 });
michael@0 323 });
michael@0 324
michael@0 325 fxa.setSignedInUser(user).then((user) => {
michael@0 326 fxa.internal.fetchAndUnwrapKeys();
michael@0 327 });
michael@0 328 });
michael@0 329
michael@0 330 // Alice (User A) signs up but never verifies her email. Then Bob (User B)
michael@0 331 // signs in with a verified email. Ensure that no sign-in events are triggered
michael@0 332 // on Alice's behalf. In the end, Bob should be the signed-in user.
michael@0 333 add_test(function test_overlapping_signins() {
michael@0 334 do_test_pending();
michael@0 335
michael@0 336 let fxa = new MockFxAccounts();
michael@0 337 let alice = getTestUser("alice");
michael@0 338 let bob = getTestUser("bob");
michael@0 339
michael@0 340 makeObserver(ONVERIFIED_NOTIFICATION, function() {
michael@0 341 log.debug("test_overlapping_signins observed onverified");
michael@0 342 // Once email verification is complete, we will observe onverified
michael@0 343 fxa.internal.getUserAccountData().then(user => {
michael@0 344 do_check_eq(user.email, bob.email);
michael@0 345 do_check_eq(user.verified, true);
michael@0 346 do_test_finished();
michael@0 347 run_next_test();
michael@0 348 });
michael@0 349 });
michael@0 350
michael@0 351 // Alice is the user signing in; her email is unverified.
michael@0 352 fxa.setSignedInUser(alice).then(() => {
michael@0 353 log.debug("Alice signing in ...");
michael@0 354 fxa.internal.getUserAccountData().then(user => {
michael@0 355 do_check_eq(user.email, alice.email);
michael@0 356 do_check_eq(user.verified, false);
michael@0 357 log.debug("Alice has not verified her email ...");
michael@0 358
michael@0 359 // Now Bob signs in instead and actually verifies his email
michael@0 360 log.debug("Bob signing in ...");
michael@0 361 fxa.setSignedInUser(bob).then(() => {
michael@0 362 do_timeout(200, function() {
michael@0 363 // Mock email verification ...
michael@0 364 log.debug("Bob verifying his email ...");
michael@0 365 fxa.internal.fxAccountsClient._verified = true;
michael@0 366 });
michael@0 367 });
michael@0 368 });
michael@0 369 });
michael@0 370 });
michael@0 371
michael@0 372 add_task(function test_getAssertion() {
michael@0 373 let fxa = new MockFxAccounts();
michael@0 374
michael@0 375 do_check_throws(function() {
michael@0 376 yield fxa.getAssertion("nonaudience");
michael@0 377 });
michael@0 378
michael@0 379 let creds = {
michael@0 380 sessionToken: "sessionToken",
michael@0 381 kA: expandHex("11"),
michael@0 382 kB: expandHex("66"),
michael@0 383 verified: true
michael@0 384 };
michael@0 385 // By putting kA/kB/verified in "creds", we skip ahead
michael@0 386 // to the "we're ready" stage.
michael@0 387 yield fxa.setSignedInUser(creds);
michael@0 388
michael@0 389 _("== ready to go\n");
michael@0 390 // Start with a nice arbitrary but realistic date. Here we use a nice RFC
michael@0 391 // 1123 date string like we would get from an HTTP header. Over the course of
michael@0 392 // the test, we will update 'now', but leave 'start' where it is.
michael@0 393 let now = Date.parse("Mon, 13 Jan 2014 21:45:06 GMT");
michael@0 394 let start = now;
michael@0 395 fxa.internal._now_is = now;
michael@0 396
michael@0 397 let d = fxa.getAssertion("audience.example.com");
michael@0 398 // At this point, a thread has been spawned to generate the keys.
michael@0 399 _("-- back from fxa.getAssertion\n");
michael@0 400 fxa.internal._d_signCertificate.resolve("cert1");
michael@0 401 let assertion = yield d;
michael@0 402 do_check_eq(fxa.internal._getCertificateSigned_calls.length, 1);
michael@0 403 do_check_eq(fxa.internal._getCertificateSigned_calls[0][0], "sessionToken");
michael@0 404 do_check_neq(assertion, null);
michael@0 405 _("ASSERTION: " + assertion + "\n");
michael@0 406 let pieces = assertion.split("~");
michael@0 407 do_check_eq(pieces[0], "cert1");
michael@0 408 let keyPair = fxa.internal.currentAccountState.keyPair;
michael@0 409 let cert = fxa.internal.currentAccountState.cert;
michael@0 410 do_check_neq(keyPair, undefined);
michael@0 411 _(keyPair.validUntil + "\n");
michael@0 412 let p2 = pieces[1].split(".");
michael@0 413 let header = JSON.parse(atob(p2[0]));
michael@0 414 _("HEADER: " + JSON.stringify(header) + "\n");
michael@0 415 do_check_eq(header.alg, "DS128");
michael@0 416 let payload = JSON.parse(atob(p2[1]));
michael@0 417 _("PAYLOAD: " + JSON.stringify(payload) + "\n");
michael@0 418 do_check_eq(payload.aud, "audience.example.com");
michael@0 419 do_check_eq(keyPair.validUntil, start + KEY_LIFETIME);
michael@0 420 do_check_eq(cert.validUntil, start + CERT_LIFETIME);
michael@0 421 _("delta: " + Date.parse(payload.exp - start) + "\n");
michael@0 422 let exp = Number(payload.exp);
michael@0 423
michael@0 424 do_check_eq(exp, now + ASSERTION_LIFETIME);
michael@0 425
michael@0 426 // Reset for next call.
michael@0 427 fxa.internal._d_signCertificate = Promise.defer();
michael@0 428
michael@0 429 // Getting a new assertion "soon" (i.e., w/o incrementing "now"), even for
michael@0 430 // a new audience, should not provoke key generation or a signing request.
michael@0 431 assertion = yield fxa.getAssertion("other.example.com");
michael@0 432
michael@0 433 // There were no additional calls - same number of getcert calls as before
michael@0 434 do_check_eq(fxa.internal._getCertificateSigned_calls.length, 1);
michael@0 435
michael@0 436 // Wait an hour; assertion use period expires, but not the certificate
michael@0 437 now += ONE_HOUR_MS;
michael@0 438 fxa.internal._now_is = now;
michael@0 439
michael@0 440 // This won't block on anything - will make an assertion, but not get a
michael@0 441 // new certificate.
michael@0 442 assertion = yield fxa.getAssertion("third.example.com");
michael@0 443
michael@0 444 // Test will time out if that failed (i.e., if that had to go get a new cert)
michael@0 445 pieces = assertion.split("~");
michael@0 446 do_check_eq(pieces[0], "cert1");
michael@0 447 p2 = pieces[1].split(".");
michael@0 448 header = JSON.parse(atob(p2[0]));
michael@0 449 payload = JSON.parse(atob(p2[1]));
michael@0 450 do_check_eq(payload.aud, "third.example.com");
michael@0 451
michael@0 452 // The keypair and cert should have the same validity as before, but the
michael@0 453 // expiration time of the assertion should be different. We compare this to
michael@0 454 // the initial start time, to which they are relative, not the current value
michael@0 455 // of "now".
michael@0 456
michael@0 457 keyPair = fxa.internal.currentAccountState.keyPair;
michael@0 458 cert = fxa.internal.currentAccountState.cert;
michael@0 459 do_check_eq(keyPair.validUntil, start + KEY_LIFETIME);
michael@0 460 do_check_eq(cert.validUntil, start + CERT_LIFETIME);
michael@0 461 exp = Number(payload.exp);
michael@0 462 do_check_eq(exp, now + ASSERTION_LIFETIME);
michael@0 463
michael@0 464 // Now we wait even longer, and expect both assertion and cert to expire. So
michael@0 465 // we will have to get a new keypair and cert.
michael@0 466 now += ONE_DAY_MS;
michael@0 467 fxa.internal._now_is = now;
michael@0 468 d = fxa.getAssertion("fourth.example.com");
michael@0 469 fxa.internal._d_signCertificate.resolve("cert2");
michael@0 470 assertion = yield d;
michael@0 471 do_check_eq(fxa.internal._getCertificateSigned_calls.length, 2);
michael@0 472 do_check_eq(fxa.internal._getCertificateSigned_calls[1][0], "sessionToken");
michael@0 473 pieces = assertion.split("~");
michael@0 474 do_check_eq(pieces[0], "cert2");
michael@0 475 p2 = pieces[1].split(".");
michael@0 476 header = JSON.parse(atob(p2[0]));
michael@0 477 payload = JSON.parse(atob(p2[1]));
michael@0 478 do_check_eq(payload.aud, "fourth.example.com");
michael@0 479 keyPair = fxa.internal.currentAccountState.keyPair;
michael@0 480 cert = fxa.internal.currentAccountState.cert;
michael@0 481 do_check_eq(keyPair.validUntil, now + KEY_LIFETIME);
michael@0 482 do_check_eq(cert.validUntil, now + CERT_LIFETIME);
michael@0 483 exp = Number(payload.exp);
michael@0 484
michael@0 485 do_check_eq(exp, now + ASSERTION_LIFETIME);
michael@0 486 _("----- DONE ----\n");
michael@0 487 });
michael@0 488
michael@0 489 add_task(function test_resend_email_not_signed_in() {
michael@0 490 let fxa = new MockFxAccounts();
michael@0 491
michael@0 492 try {
michael@0 493 yield fxa.resendVerificationEmail();
michael@0 494 } catch(err) {
michael@0 495 do_check_eq(err.message,
michael@0 496 "Cannot resend verification email; no signed-in user");
michael@0 497 return;
michael@0 498 }
michael@0 499 do_throw("Should not be able to resend email when nobody is signed in");
michael@0 500 });
michael@0 501
michael@0 502 add_test(function test_resend_email() {
michael@0 503 let fxa = new MockFxAccounts();
michael@0 504 let alice = getTestUser("alice");
michael@0 505
michael@0 506 let initialState = fxa.internal.currentAccountState;
michael@0 507
michael@0 508 // Alice is the user signing in; her email is unverified.
michael@0 509 fxa.setSignedInUser(alice).then(() => {
michael@0 510 log.debug("Alice signing in");
michael@0 511
michael@0 512 // We're polling for the first email
michael@0 513 do_check_true(fxa.internal.currentAccountState !== initialState);
michael@0 514 let aliceState = fxa.internal.currentAccountState;
michael@0 515
michael@0 516 // The polling timer is ticking
michael@0 517 do_check_true(fxa.internal.currentTimer > 0);
michael@0 518
michael@0 519 fxa.internal.getUserAccountData().then(user => {
michael@0 520 do_check_eq(user.email, alice.email);
michael@0 521 do_check_eq(user.verified, false);
michael@0 522 log.debug("Alice wants verification email resent");
michael@0 523
michael@0 524 fxa.resendVerificationEmail().then((result) => {
michael@0 525 // Mock server response; ensures that the session token actually was
michael@0 526 // passed to the client to make the hawk call
michael@0 527 do_check_eq(result, "alice's session token");
michael@0 528
michael@0 529 // Timer was not restarted
michael@0 530 do_check_true(fxa.internal.currentAccountState === aliceState);
michael@0 531
michael@0 532 // Timer is still ticking
michael@0 533 do_check_true(fxa.internal.currentTimer > 0);
michael@0 534
michael@0 535 // Ok abort polling before we go on to the next test
michael@0 536 fxa.internal.abortExistingFlow();
michael@0 537 run_next_test();
michael@0 538 });
michael@0 539 });
michael@0 540 });
michael@0 541 });
michael@0 542
michael@0 543 add_test(function test_sign_out() {
michael@0 544 do_test_pending();
michael@0 545 let fxa = new MockFxAccounts();
michael@0 546 let remoteSignOutCalled = false;
michael@0 547 let client = fxa.internal.fxAccountsClient;
michael@0 548 client.signOut = function() { remoteSignOutCalled = true; return Promise.resolve(); };
michael@0 549 makeObserver(ONLOGOUT_NOTIFICATION, function() {
michael@0 550 log.debug("test_sign_out_with_remote_error observed onlogout");
michael@0 551 // user should be undefined after sign out
michael@0 552 fxa.internal.getUserAccountData().then(user => {
michael@0 553 do_check_eq(user, null);
michael@0 554 do_check_true(remoteSignOutCalled);
michael@0 555 do_test_finished();
michael@0 556 run_next_test();
michael@0 557 });
michael@0 558 });
michael@0 559 fxa.signOut();
michael@0 560 });
michael@0 561
michael@0 562 add_test(function test_sign_out_with_remote_error() {
michael@0 563 do_test_pending();
michael@0 564 let fxa = new MockFxAccounts();
michael@0 565 let client = fxa.internal.fxAccountsClient;
michael@0 566 let remoteSignOutCalled = false;
michael@0 567 // Force remote sign out to trigger an error
michael@0 568 client.signOut = function() { remoteSignOutCalled = true; throw "Remote sign out error"; };
michael@0 569 makeObserver(ONLOGOUT_NOTIFICATION, function() {
michael@0 570 log.debug("test_sign_out_with_remote_error observed onlogout");
michael@0 571 // user should be undefined after sign out
michael@0 572 fxa.internal.getUserAccountData().then(user => {
michael@0 573 do_check_eq(user, null);
michael@0 574 do_check_true(remoteSignOutCalled);
michael@0 575 do_test_finished();
michael@0 576 run_next_test();
michael@0 577 });
michael@0 578 });
michael@0 579 fxa.signOut();
michael@0 580 });
michael@0 581
michael@0 582 /*
michael@0 583 * End of tests.
michael@0 584 * Utility functions follow.
michael@0 585 */
michael@0 586
michael@0 587 function expandHex(two_hex) {
michael@0 588 // Return a 64-character hex string, encoding 32 identical bytes.
michael@0 589 let eight_hex = two_hex + two_hex + two_hex + two_hex;
michael@0 590 let thirtytwo_hex = eight_hex + eight_hex + eight_hex + eight_hex;
michael@0 591 return thirtytwo_hex + thirtytwo_hex;
michael@0 592 };
michael@0 593
michael@0 594 function expandBytes(two_hex) {
michael@0 595 return CommonUtils.hexToBytes(expandHex(two_hex));
michael@0 596 };
michael@0 597
michael@0 598 function getTestUser(name) {
michael@0 599 return {
michael@0 600 email: name + "@example.com",
michael@0 601 uid: "1ad7f502-4cc7-4ec1-a209-071fd2fae348",
michael@0 602 sessionToken: name + "'s session token",
michael@0 603 keyFetchToken: name + "'s keyfetch token",
michael@0 604 unwrapBKey: expandHex("44"),
michael@0 605 verified: false
michael@0 606 };
michael@0 607 }
michael@0 608
michael@0 609 function makeObserver(aObserveTopic, aObserveFunc) {
michael@0 610 let observer = {
michael@0 611 // nsISupports provides type management in C++
michael@0 612 // nsIObserver is to be an observer
michael@0 613 QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports, Ci.nsIObserver]),
michael@0 614
michael@0 615 observe: function (aSubject, aTopic, aData) {
michael@0 616 log.debug("observed " + aTopic + " " + aData);
michael@0 617 if (aTopic == aObserveTopic) {
michael@0 618 removeMe();
michael@0 619 aObserveFunc(aSubject, aTopic, aData);
michael@0 620 }
michael@0 621 }
michael@0 622 };
michael@0 623
michael@0 624 function removeMe() {
michael@0 625 log.debug("removing observer for " + aObserveTopic);
michael@0 626 Services.obs.removeObserver(observer, aObserveTopic);
michael@0 627 }
michael@0 628
michael@0 629 Services.obs.addObserver(observer, aObserveTopic, false);
michael@0 630 return removeMe;
michael@0 631 }
michael@0 632
michael@0 633 function do_check_throws(func, result, stack)
michael@0 634 {
michael@0 635 if (!stack)
michael@0 636 stack = Components.stack.caller;
michael@0 637
michael@0 638 try {
michael@0 639 func();
michael@0 640 } catch (ex) {
michael@0 641 if (ex.name == result) {
michael@0 642 return;
michael@0 643 }
michael@0 644 do_throw("Expected result " + result + ", caught " + ex, stack);
michael@0 645 }
michael@0 646
michael@0 647 if (result) {
michael@0 648 do_throw("Expected result " + result + ", none thrown", stack);
michael@0 649 }
michael@0 650 }

mercurial