services/fxaccounts/tests/xpcshell/test_accounts.js

changeset 0
6474c204b198
     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 +}

mercurial