michael@0: /* Any copyright is dedicated to the Public Domain. michael@0: * http://creativecommons.org/publicdomain/zero/1.0/ */ michael@0: michael@0: "use strict"; michael@0: michael@0: Cu.import("resource://services-common/utils.js"); michael@0: Cu.import("resource://gre/modules/Services.jsm"); michael@0: Cu.import("resource://gre/modules/FxAccounts.jsm"); michael@0: Cu.import("resource://gre/modules/FxAccountsClient.jsm"); michael@0: Cu.import("resource://gre/modules/FxAccountsCommon.js"); michael@0: Cu.import("resource://gre/modules/Promise.jsm"); michael@0: Cu.import("resource://gre/modules/Log.jsm"); michael@0: michael@0: const ONE_HOUR_MS = 1000 * 60 * 60; michael@0: const ONE_DAY_MS = ONE_HOUR_MS * 24; michael@0: const TWO_MINUTES_MS = 1000 * 60 * 2; michael@0: michael@0: initTestLogging("Trace"); michael@0: michael@0: // XXX until bug 937114 is fixed michael@0: Cu.importGlobalProperties(['atob']); michael@0: michael@0: let log = Log.repository.getLogger("Services.FxAccounts.test"); michael@0: log.level = Log.Level.Debug; michael@0: michael@0: // See verbose logging from FxAccounts.jsm michael@0: Services.prefs.setCharPref("identity.fxaccounts.loglevel", "DEBUG"); michael@0: michael@0: function run_test() { michael@0: run_next_test(); michael@0: } michael@0: michael@0: /* michael@0: * The FxAccountsClient communicates with the remote Firefox michael@0: * Accounts auth server. Mock the server calls, with a little michael@0: * lag time to simulate some latency. michael@0: * michael@0: * We add the _verified attribute to mock the change in verification michael@0: * state on the FXA server. michael@0: */ michael@0: function MockFxAccountsClient() { michael@0: this._email = "nobody@example.com"; michael@0: this._verified = false; michael@0: michael@0: // mock calls up to the auth server to determine whether the michael@0: // user account has been verified michael@0: this.recoveryEmailStatus = function (sessionToken) { michael@0: // simulate a call to /recovery_email/status michael@0: let deferred = Promise.defer(); michael@0: michael@0: let response = { michael@0: email: this._email, michael@0: verified: this._verified michael@0: }; michael@0: deferred.resolve(response); michael@0: michael@0: return deferred.promise; michael@0: }; michael@0: michael@0: this.accountKeys = function (keyFetchToken) { michael@0: let deferred = Promise.defer(); michael@0: michael@0: do_timeout(50, () => { michael@0: let response = { michael@0: kA: expandBytes("11"), michael@0: wrapKB: expandBytes("22") michael@0: }; michael@0: deferred.resolve(response); michael@0: }); michael@0: return deferred.promise; michael@0: }; michael@0: michael@0: this.resendVerificationEmail = function(sessionToken) { michael@0: // Return the session token to show that we received it in the first place michael@0: return Promise.resolve(sessionToken); michael@0: }; michael@0: michael@0: this.signCertificate = function() { throw "no" }; michael@0: michael@0: this.signOut = function() { return Promise.resolve(); }; michael@0: michael@0: FxAccountsClient.apply(this); michael@0: } michael@0: MockFxAccountsClient.prototype = { michael@0: __proto__: FxAccountsClient.prototype michael@0: } michael@0: michael@0: let MockStorage = function() { michael@0: this.data = null; michael@0: }; michael@0: MockStorage.prototype = Object.freeze({ michael@0: set: function (contents) { michael@0: this.data = contents; michael@0: return Promise.resolve(null); michael@0: }, michael@0: get: function () { michael@0: return Promise.resolve(this.data); michael@0: }, michael@0: }); michael@0: michael@0: /* michael@0: * We need to mock the FxAccounts module's interfaces to external michael@0: * services, such as storage and the FxAccounts client. We also michael@0: * mock the now() method, so that we can simulate the passing of michael@0: * time and verify that signatures expire correctly. michael@0: */ michael@0: function MockFxAccounts() { michael@0: return new FxAccounts({ michael@0: _getCertificateSigned_calls: [], michael@0: _d_signCertificate: Promise.defer(), michael@0: _now_is: new Date(), michael@0: signedInUserStorage: new MockStorage(), michael@0: now: function () { michael@0: return this._now_is; michael@0: }, michael@0: getCertificateSigned: function (sessionToken, serializedPublicKey) { michael@0: _("mock getCertificateSigned\n"); michael@0: this._getCertificateSigned_calls.push([sessionToken, serializedPublicKey]); michael@0: return this._d_signCertificate.promise; michael@0: }, michael@0: fxAccountsClient: new MockFxAccountsClient() michael@0: }); michael@0: } michael@0: michael@0: add_test(function test_non_https_remote_server_uri() { michael@0: Services.prefs.setCharPref( michael@0: "identity.fxaccounts.remote.signup.uri", michael@0: "http://example.com/browser/browser/base/content/test/general/accounts_testRemoteCommands.html"); michael@0: do_check_throws_message(function () { michael@0: fxAccounts.getAccountsSignUpURI(); michael@0: }, "Firefox Accounts server must use HTTPS"); michael@0: michael@0: Services.prefs.clearUserPref("identity.fxaccounts.remote.signup.uri"); michael@0: michael@0: run_next_test(); michael@0: }); michael@0: michael@0: add_task(function test_get_signed_in_user_initially_unset() { michael@0: // This test, unlike the rest, uses an un-mocked FxAccounts instance. michael@0: // However, we still need to pass an object to the constructor to michael@0: // force it to expose "internal", so we can test the disk storage. michael@0: let account = new FxAccounts({onlySetInternal: true}) michael@0: let credentials = { michael@0: email: "foo@example.com", michael@0: uid: "1234@lcip.org", michael@0: assertion: "foobar", michael@0: sessionToken: "dead", michael@0: kA: "beef", michael@0: kB: "cafe", michael@0: verified: true michael@0: }; michael@0: michael@0: let result = yield account.getSignedInUser(); michael@0: do_check_eq(result, null); michael@0: michael@0: yield account.setSignedInUser(credentials); michael@0: michael@0: let result = yield account.getSignedInUser(); michael@0: do_check_eq(result.email, credentials.email); michael@0: do_check_eq(result.assertion, credentials.assertion); michael@0: do_check_eq(result.kB, credentials.kB); michael@0: michael@0: // Delete the memory cache and force the user michael@0: // to be read and parsed from storage (e.g. disk via JSONStorage). michael@0: delete account.internal.signedInUser; michael@0: let result = yield account.getSignedInUser(); michael@0: do_check_eq(result.email, credentials.email); michael@0: do_check_eq(result.assertion, credentials.assertion); michael@0: do_check_eq(result.kB, credentials.kB); michael@0: michael@0: // sign out michael@0: let localOnly = true; michael@0: yield account.signOut(localOnly); michael@0: michael@0: // user should be undefined after sign out michael@0: let result = yield account.getSignedInUser(); michael@0: do_check_eq(result, null); michael@0: }); michael@0: michael@0: // Sanity-check that our mocked client is working correctly michael@0: add_test(function test_client_mock() { michael@0: do_test_pending(); michael@0: michael@0: let fxa = new MockFxAccounts(); michael@0: let client = fxa.internal.fxAccountsClient; michael@0: do_check_eq(client._verified, false); michael@0: do_check_eq(typeof client.signIn, "function"); michael@0: michael@0: // The recoveryEmailStatus function eventually fulfills its promise michael@0: client.recoveryEmailStatus() michael@0: .then(response => { michael@0: do_check_eq(response.verified, false); michael@0: do_test_finished(); michael@0: run_next_test(); michael@0: }); michael@0: }); michael@0: michael@0: // Sign in a user, and after a little while, verify the user's email. michael@0: // Right after signing in the user, we should get the 'onlogin' notification. michael@0: // Polling should detect that the email is verified, and eventually michael@0: // 'onverified' should be observed michael@0: add_test(function test_verification_poll() { michael@0: do_test_pending(); michael@0: michael@0: let fxa = new MockFxAccounts(); michael@0: let test_user = getTestUser("francine"); michael@0: let login_notification_received = false; michael@0: michael@0: makeObserver(ONVERIFIED_NOTIFICATION, function() { michael@0: log.debug("test_verification_poll observed onverified"); michael@0: // Once email verification is complete, we will observe onverified michael@0: fxa.internal.getUserAccountData().then(user => { michael@0: // And confirm that the user's state has changed michael@0: do_check_eq(user.verified, true); michael@0: do_check_eq(user.email, test_user.email); michael@0: do_check_true(login_notification_received); michael@0: do_test_finished(); michael@0: run_next_test(); michael@0: }); michael@0: }); michael@0: michael@0: makeObserver(ONLOGIN_NOTIFICATION, function() { michael@0: log.debug("test_verification_poll observer onlogin"); michael@0: login_notification_received = true; michael@0: }); michael@0: michael@0: fxa.setSignedInUser(test_user).then(() => { michael@0: fxa.internal.getUserAccountData().then(user => { michael@0: // The user is signing in, but email has not been verified yet michael@0: do_check_eq(user.verified, false); michael@0: do_timeout(200, function() { michael@0: log.debug("Mocking verification of francine's email"); michael@0: fxa.internal.fxAccountsClient._email = test_user.email; michael@0: fxa.internal.fxAccountsClient._verified = true; michael@0: }); michael@0: }); michael@0: }); michael@0: }); michael@0: michael@0: // Sign in the user, but never verify the email. The check-email michael@0: // poll should time out. No verifiedlogin event should be observed, and the michael@0: // internal whenVerified promise should be rejected michael@0: add_test(function test_polling_timeout() { michael@0: do_test_pending(); michael@0: michael@0: // This test could be better - the onverified observer might fire on michael@0: // somebody else's stack, and we're not making sure that we're not receiving michael@0: // such a message. In other words, this tests either failure, or success, but michael@0: // not both. michael@0: michael@0: let fxa = new MockFxAccounts(); michael@0: let test_user = getTestUser("carol"); michael@0: michael@0: let removeObserver = makeObserver(ONVERIFIED_NOTIFICATION, function() { michael@0: do_throw("We should not be getting a login event!"); michael@0: }); michael@0: michael@0: fxa.internal.POLL_SESSION = 1; michael@0: fxa.internal.POLL_STEP = 2; michael@0: michael@0: let p = fxa.internal.whenVerified({}); michael@0: michael@0: fxa.setSignedInUser(test_user).then(() => { michael@0: p.then( michael@0: (success) => { michael@0: do_throw("this should not succeed"); michael@0: }, michael@0: (fail) => { michael@0: removeObserver(); michael@0: do_test_finished(); michael@0: run_next_test(); michael@0: } michael@0: ); michael@0: }); michael@0: }); michael@0: michael@0: add_test(function test_getKeys() { michael@0: do_test_pending(); michael@0: let fxa = new MockFxAccounts(); michael@0: let user = getTestUser("eusebius"); michael@0: michael@0: // Once email has been verified, we will be able to get keys michael@0: user.verified = true; michael@0: michael@0: fxa.setSignedInUser(user).then(() => { michael@0: fxa.getSignedInUser().then((user) => { michael@0: // Before getKeys, we have no keys michael@0: do_check_eq(!!user.kA, false); michael@0: do_check_eq(!!user.kB, false); michael@0: // And we still have a key-fetch token to use michael@0: do_check_eq(!!user.keyFetchToken, true); michael@0: michael@0: fxa.internal.getKeys().then(() => { michael@0: fxa.getSignedInUser().then((user) => { michael@0: // Now we should have keys michael@0: do_check_eq(fxa.internal.isUserEmailVerified(user), true); michael@0: do_check_eq(!!user.verified, true); michael@0: do_check_eq(user.kA, expandHex("11")); michael@0: do_check_eq(user.kB, expandHex("66")); michael@0: do_check_eq(user.keyFetchToken, undefined); michael@0: do_test_finished(); michael@0: run_next_test(); michael@0: }); michael@0: }); michael@0: }); michael@0: }); michael@0: }); michael@0: michael@0: // fetchAndUnwrapKeys with no keyFetchToken should trigger signOut michael@0: add_test(function test_fetchAndUnwrapKeys_no_token() { michael@0: do_test_pending(); michael@0: michael@0: let fxa = new MockFxAccounts(); michael@0: let user = getTestUser("lettuce.protheroe"); michael@0: delete user.keyFetchToken michael@0: michael@0: makeObserver(ONLOGOUT_NOTIFICATION, function() { michael@0: log.debug("test_fetchAndUnwrapKeys_no_token observed logout"); michael@0: fxa.internal.getUserAccountData().then(user => { michael@0: do_test_finished(); michael@0: run_next_test(); michael@0: }); michael@0: }); michael@0: michael@0: fxa.setSignedInUser(user).then((user) => { michael@0: fxa.internal.fetchAndUnwrapKeys(); michael@0: }); michael@0: }); michael@0: michael@0: // Alice (User A) signs up but never verifies her email. Then Bob (User B) michael@0: // signs in with a verified email. Ensure that no sign-in events are triggered michael@0: // on Alice's behalf. In the end, Bob should be the signed-in user. michael@0: add_test(function test_overlapping_signins() { michael@0: do_test_pending(); michael@0: michael@0: let fxa = new MockFxAccounts(); michael@0: let alice = getTestUser("alice"); michael@0: let bob = getTestUser("bob"); michael@0: michael@0: makeObserver(ONVERIFIED_NOTIFICATION, function() { michael@0: log.debug("test_overlapping_signins observed onverified"); michael@0: // Once email verification is complete, we will observe onverified michael@0: fxa.internal.getUserAccountData().then(user => { michael@0: do_check_eq(user.email, bob.email); michael@0: do_check_eq(user.verified, true); michael@0: do_test_finished(); michael@0: run_next_test(); michael@0: }); michael@0: }); michael@0: michael@0: // Alice is the user signing in; her email is unverified. michael@0: fxa.setSignedInUser(alice).then(() => { michael@0: log.debug("Alice signing in ..."); michael@0: fxa.internal.getUserAccountData().then(user => { michael@0: do_check_eq(user.email, alice.email); michael@0: do_check_eq(user.verified, false); michael@0: log.debug("Alice has not verified her email ..."); michael@0: michael@0: // Now Bob signs in instead and actually verifies his email michael@0: log.debug("Bob signing in ..."); michael@0: fxa.setSignedInUser(bob).then(() => { michael@0: do_timeout(200, function() { michael@0: // Mock email verification ... michael@0: log.debug("Bob verifying his email ..."); michael@0: fxa.internal.fxAccountsClient._verified = true; michael@0: }); michael@0: }); michael@0: }); michael@0: }); michael@0: }); michael@0: michael@0: add_task(function test_getAssertion() { michael@0: let fxa = new MockFxAccounts(); michael@0: michael@0: do_check_throws(function() { michael@0: yield fxa.getAssertion("nonaudience"); michael@0: }); michael@0: michael@0: let creds = { michael@0: sessionToken: "sessionToken", michael@0: kA: expandHex("11"), michael@0: kB: expandHex("66"), michael@0: verified: true michael@0: }; michael@0: // By putting kA/kB/verified in "creds", we skip ahead michael@0: // to the "we're ready" stage. michael@0: yield fxa.setSignedInUser(creds); michael@0: michael@0: _("== ready to go\n"); michael@0: // Start with a nice arbitrary but realistic date. Here we use a nice RFC michael@0: // 1123 date string like we would get from an HTTP header. Over the course of michael@0: // the test, we will update 'now', but leave 'start' where it is. michael@0: let now = Date.parse("Mon, 13 Jan 2014 21:45:06 GMT"); michael@0: let start = now; michael@0: fxa.internal._now_is = now; michael@0: michael@0: let d = fxa.getAssertion("audience.example.com"); michael@0: // At this point, a thread has been spawned to generate the keys. michael@0: _("-- back from fxa.getAssertion\n"); michael@0: fxa.internal._d_signCertificate.resolve("cert1"); michael@0: let assertion = yield d; michael@0: do_check_eq(fxa.internal._getCertificateSigned_calls.length, 1); michael@0: do_check_eq(fxa.internal._getCertificateSigned_calls[0][0], "sessionToken"); michael@0: do_check_neq(assertion, null); michael@0: _("ASSERTION: " + assertion + "\n"); michael@0: let pieces = assertion.split("~"); michael@0: do_check_eq(pieces[0], "cert1"); michael@0: let keyPair = fxa.internal.currentAccountState.keyPair; michael@0: let cert = fxa.internal.currentAccountState.cert; michael@0: do_check_neq(keyPair, undefined); michael@0: _(keyPair.validUntil + "\n"); michael@0: let p2 = pieces[1].split("."); michael@0: let header = JSON.parse(atob(p2[0])); michael@0: _("HEADER: " + JSON.stringify(header) + "\n"); michael@0: do_check_eq(header.alg, "DS128"); michael@0: let payload = JSON.parse(atob(p2[1])); michael@0: _("PAYLOAD: " + JSON.stringify(payload) + "\n"); michael@0: do_check_eq(payload.aud, "audience.example.com"); michael@0: do_check_eq(keyPair.validUntil, start + KEY_LIFETIME); michael@0: do_check_eq(cert.validUntil, start + CERT_LIFETIME); michael@0: _("delta: " + Date.parse(payload.exp - start) + "\n"); michael@0: let exp = Number(payload.exp); michael@0: michael@0: do_check_eq(exp, now + ASSERTION_LIFETIME); michael@0: michael@0: // Reset for next call. michael@0: fxa.internal._d_signCertificate = Promise.defer(); michael@0: michael@0: // Getting a new assertion "soon" (i.e., w/o incrementing "now"), even for michael@0: // a new audience, should not provoke key generation or a signing request. michael@0: assertion = yield fxa.getAssertion("other.example.com"); michael@0: michael@0: // There were no additional calls - same number of getcert calls as before michael@0: do_check_eq(fxa.internal._getCertificateSigned_calls.length, 1); michael@0: michael@0: // Wait an hour; assertion use period expires, but not the certificate michael@0: now += ONE_HOUR_MS; michael@0: fxa.internal._now_is = now; michael@0: michael@0: // This won't block on anything - will make an assertion, but not get a michael@0: // new certificate. michael@0: assertion = yield fxa.getAssertion("third.example.com"); michael@0: michael@0: // Test will time out if that failed (i.e., if that had to go get a new cert) michael@0: pieces = assertion.split("~"); michael@0: do_check_eq(pieces[0], "cert1"); michael@0: p2 = pieces[1].split("."); michael@0: header = JSON.parse(atob(p2[0])); michael@0: payload = JSON.parse(atob(p2[1])); michael@0: do_check_eq(payload.aud, "third.example.com"); michael@0: michael@0: // The keypair and cert should have the same validity as before, but the michael@0: // expiration time of the assertion should be different. We compare this to michael@0: // the initial start time, to which they are relative, not the current value michael@0: // of "now". michael@0: michael@0: keyPair = fxa.internal.currentAccountState.keyPair; michael@0: cert = fxa.internal.currentAccountState.cert; michael@0: do_check_eq(keyPair.validUntil, start + KEY_LIFETIME); michael@0: do_check_eq(cert.validUntil, start + CERT_LIFETIME); michael@0: exp = Number(payload.exp); michael@0: do_check_eq(exp, now + ASSERTION_LIFETIME); michael@0: michael@0: // Now we wait even longer, and expect both assertion and cert to expire. So michael@0: // we will have to get a new keypair and cert. michael@0: now += ONE_DAY_MS; michael@0: fxa.internal._now_is = now; michael@0: d = fxa.getAssertion("fourth.example.com"); michael@0: fxa.internal._d_signCertificate.resolve("cert2"); michael@0: assertion = yield d; michael@0: do_check_eq(fxa.internal._getCertificateSigned_calls.length, 2); michael@0: do_check_eq(fxa.internal._getCertificateSigned_calls[1][0], "sessionToken"); michael@0: pieces = assertion.split("~"); michael@0: do_check_eq(pieces[0], "cert2"); michael@0: p2 = pieces[1].split("."); michael@0: header = JSON.parse(atob(p2[0])); michael@0: payload = JSON.parse(atob(p2[1])); michael@0: do_check_eq(payload.aud, "fourth.example.com"); michael@0: keyPair = fxa.internal.currentAccountState.keyPair; michael@0: cert = fxa.internal.currentAccountState.cert; michael@0: do_check_eq(keyPair.validUntil, now + KEY_LIFETIME); michael@0: do_check_eq(cert.validUntil, now + CERT_LIFETIME); michael@0: exp = Number(payload.exp); michael@0: michael@0: do_check_eq(exp, now + ASSERTION_LIFETIME); michael@0: _("----- DONE ----\n"); michael@0: }); michael@0: michael@0: add_task(function test_resend_email_not_signed_in() { michael@0: let fxa = new MockFxAccounts(); michael@0: michael@0: try { michael@0: yield fxa.resendVerificationEmail(); michael@0: } catch(err) { michael@0: do_check_eq(err.message, michael@0: "Cannot resend verification email; no signed-in user"); michael@0: return; michael@0: } michael@0: do_throw("Should not be able to resend email when nobody is signed in"); michael@0: }); michael@0: michael@0: add_test(function test_resend_email() { michael@0: let fxa = new MockFxAccounts(); michael@0: let alice = getTestUser("alice"); michael@0: michael@0: let initialState = fxa.internal.currentAccountState; michael@0: michael@0: // Alice is the user signing in; her email is unverified. michael@0: fxa.setSignedInUser(alice).then(() => { michael@0: log.debug("Alice signing in"); michael@0: michael@0: // We're polling for the first email michael@0: do_check_true(fxa.internal.currentAccountState !== initialState); michael@0: let aliceState = fxa.internal.currentAccountState; michael@0: michael@0: // The polling timer is ticking michael@0: do_check_true(fxa.internal.currentTimer > 0); michael@0: michael@0: fxa.internal.getUserAccountData().then(user => { michael@0: do_check_eq(user.email, alice.email); michael@0: do_check_eq(user.verified, false); michael@0: log.debug("Alice wants verification email resent"); michael@0: michael@0: fxa.resendVerificationEmail().then((result) => { michael@0: // Mock server response; ensures that the session token actually was michael@0: // passed to the client to make the hawk call michael@0: do_check_eq(result, "alice's session token"); michael@0: michael@0: // Timer was not restarted michael@0: do_check_true(fxa.internal.currentAccountState === aliceState); michael@0: michael@0: // Timer is still ticking michael@0: do_check_true(fxa.internal.currentTimer > 0); michael@0: michael@0: // Ok abort polling before we go on to the next test michael@0: fxa.internal.abortExistingFlow(); michael@0: run_next_test(); michael@0: }); michael@0: }); michael@0: }); michael@0: }); michael@0: michael@0: add_test(function test_sign_out() { michael@0: do_test_pending(); michael@0: let fxa = new MockFxAccounts(); michael@0: let remoteSignOutCalled = false; michael@0: let client = fxa.internal.fxAccountsClient; michael@0: client.signOut = function() { remoteSignOutCalled = true; return Promise.resolve(); }; michael@0: makeObserver(ONLOGOUT_NOTIFICATION, function() { michael@0: log.debug("test_sign_out_with_remote_error observed onlogout"); michael@0: // user should be undefined after sign out michael@0: fxa.internal.getUserAccountData().then(user => { michael@0: do_check_eq(user, null); michael@0: do_check_true(remoteSignOutCalled); michael@0: do_test_finished(); michael@0: run_next_test(); michael@0: }); michael@0: }); michael@0: fxa.signOut(); michael@0: }); michael@0: michael@0: add_test(function test_sign_out_with_remote_error() { michael@0: do_test_pending(); michael@0: let fxa = new MockFxAccounts(); michael@0: let client = fxa.internal.fxAccountsClient; michael@0: let remoteSignOutCalled = false; michael@0: // Force remote sign out to trigger an error michael@0: client.signOut = function() { remoteSignOutCalled = true; throw "Remote sign out error"; }; michael@0: makeObserver(ONLOGOUT_NOTIFICATION, function() { michael@0: log.debug("test_sign_out_with_remote_error observed onlogout"); michael@0: // user should be undefined after sign out michael@0: fxa.internal.getUserAccountData().then(user => { michael@0: do_check_eq(user, null); michael@0: do_check_true(remoteSignOutCalled); michael@0: do_test_finished(); michael@0: run_next_test(); michael@0: }); michael@0: }); michael@0: fxa.signOut(); michael@0: }); michael@0: michael@0: /* michael@0: * End of tests. michael@0: * Utility functions follow. michael@0: */ michael@0: michael@0: function expandHex(two_hex) { michael@0: // Return a 64-character hex string, encoding 32 identical bytes. michael@0: let eight_hex = two_hex + two_hex + two_hex + two_hex; michael@0: let thirtytwo_hex = eight_hex + eight_hex + eight_hex + eight_hex; michael@0: return thirtytwo_hex + thirtytwo_hex; michael@0: }; michael@0: michael@0: function expandBytes(two_hex) { michael@0: return CommonUtils.hexToBytes(expandHex(two_hex)); michael@0: }; michael@0: michael@0: function getTestUser(name) { michael@0: return { michael@0: email: name + "@example.com", michael@0: uid: "1ad7f502-4cc7-4ec1-a209-071fd2fae348", michael@0: sessionToken: name + "'s session token", michael@0: keyFetchToken: name + "'s keyfetch token", michael@0: unwrapBKey: expandHex("44"), michael@0: verified: false michael@0: }; michael@0: } michael@0: michael@0: function makeObserver(aObserveTopic, aObserveFunc) { michael@0: let observer = { michael@0: // nsISupports provides type management in C++ michael@0: // nsIObserver is to be an observer michael@0: QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports, Ci.nsIObserver]), michael@0: michael@0: observe: function (aSubject, aTopic, aData) { michael@0: log.debug("observed " + aTopic + " " + aData); michael@0: if (aTopic == aObserveTopic) { michael@0: removeMe(); michael@0: aObserveFunc(aSubject, aTopic, aData); michael@0: } michael@0: } michael@0: }; michael@0: michael@0: function removeMe() { michael@0: log.debug("removing observer for " + aObserveTopic); michael@0: Services.obs.removeObserver(observer, aObserveTopic); michael@0: } michael@0: michael@0: Services.obs.addObserver(observer, aObserveTopic, false); michael@0: return removeMe; michael@0: } michael@0: michael@0: function do_check_throws(func, result, stack) michael@0: { michael@0: if (!stack) michael@0: stack = Components.stack.caller; michael@0: michael@0: try { michael@0: func(); michael@0: } catch (ex) { michael@0: if (ex.name == result) { michael@0: return; michael@0: } michael@0: do_throw("Expected result " + result + ", caught " + ex, stack); michael@0: } michael@0: michael@0: if (result) { michael@0: do_throw("Expected result " + result + ", none thrown", stack); michael@0: } michael@0: }