diff options
Diffstat (limited to 'services/sync/tests/unit/test_fxa_migration.js')
-rw-r--r-- | services/sync/tests/unit/test_fxa_migration.js | 279 |
1 files changed, 279 insertions, 0 deletions
diff --git a/services/sync/tests/unit/test_fxa_migration.js b/services/sync/tests/unit/test_fxa_migration.js new file mode 100644 index 000000000..7c65d5996 --- /dev/null +++ b/services/sync/tests/unit/test_fxa_migration.js @@ -0,0 +1,279 @@ +// Test the FxAMigration module +Cu.import("resource://services-sync/FxaMigrator.jsm"); +Cu.import("resource://gre/modules/Promise.jsm"); +Cu.import("resource://gre/modules/FxAccounts.jsm"); +Cu.import("resource://gre/modules/FxAccountsCommon.js"); +Cu.import("resource://services-sync/browserid_identity.js"); + +// Set our username pref early so sync initializes with the legacy provider. +Services.prefs.setCharPref("services.sync.username", "foo"); +// And ensure all debug messages end up being printed. +Services.prefs.setCharPref("services.sync.log.appender.dump", "Debug"); + +// Now import sync +Cu.import("resource://services-sync/service.js"); +Cu.import("resource://services-sync/record.js"); +Cu.import("resource://services-sync/util.js"); + +// And reset the username. +Services.prefs.clearUserPref("services.sync.username"); + +Cu.import("resource://testing-common/services/sync/utils.js"); +Cu.import("resource://testing-common/services/common/logging.js"); +Cu.import("resource://testing-common/services/sync/rotaryengine.js"); + +const FXA_USERNAME = "someone@somewhere"; + +// Utilities +function promiseOneObserver(topic) { + return new Promise((resolve, reject) => { + let observer = function(subject, topic, data) { + Services.obs.removeObserver(observer, topic); + resolve({ subject: subject, data: data }); + } + Services.obs.addObserver(observer, topic, false); + }); +} + +function promiseStopServer(server) { + return new Promise((resolve, reject) => { + server.stop(resolve); + }); +} + + +// Helpers +function configureLegacySync() { + let engine = new RotaryEngine(Service); + engine.enabled = true; + Svc.Prefs.set("registerEngines", engine.name); + Svc.Prefs.set("log.logger.engine.rotary", "Trace"); + + let contents = { + meta: {global: {engines: {rotary: {version: engine.version, + syncID: engine.syncID}}}}, + crypto: {}, + rotary: {} + }; + + const USER = "foo"; + const PASSPHRASE = "abcdeabcdeabcdeabcdeabcdea"; + + setBasicCredentials(USER, "password", PASSPHRASE); + + let onRequest = function(request, response) { + // ideally we'd only do this while a legacy user is configured, but WTH. + response.setHeader("x-weave-alert", JSON.stringify({code: "soft-eol"})); + } + let server = new SyncServer({onRequest: onRequest}); + server.registerUser(USER, "password"); + server.createContents(USER, contents); + server.start(); + + Service.serverURL = server.baseURI; + Service.clusterURL = server.baseURI; + Service.identity.username = USER; + Service._updateCachedURLs(); + + Service.engineManager._engines[engine.name] = engine; + + return [engine, server]; +} + +function configureFxa() { + Services.prefs.setCharPref("identity.fxaccounts.auth.uri", "http://localhost"); +} + +add_task(function *testMigration() { + configureFxa(); + + // when we do a .startOver we want the new provider. + let oldValue = Services.prefs.getBoolPref("services.sync-testing.startOverKeepIdentity"); + Services.prefs.setBoolPref("services.sync-testing.startOverKeepIdentity", false); + + // disable the addons engine - this engine choice is arbitrary, but we + // want to check it remains disabled after migration. + Services.prefs.setBoolPref("services.sync.engine.addons", false); + + do_register_cleanup(() => { + Services.prefs.setBoolPref("services.sync-testing.startOverKeepIdentity", oldValue) + Services.prefs.setBoolPref("services.sync.engine.addons", true); + }); + + // No sync user - that should report no user-action necessary. + Assert.deepEqual((yield fxaMigrator._queueCurrentUserState()), null, + "no user state when complete"); + + // Arrange for a legacy sync user and manually bump the migrator + let [engine, server] = configureLegacySync(); + + // Check our disabling of the "addons" engine worked, and for good measure, + // that the "passwords" engine is enabled. + Assert.ok(!Service.engineManager.get("addons").enabled, "addons is disabled"); + Assert.ok(Service.engineManager.get("passwords").enabled, "passwords is enabled"); + + // monkey-patch the migration sentinel code so we know it was called. + let haveStartedSentinel = false; + let origSetFxAMigrationSentinel = Service.setFxAMigrationSentinel; + let promiseSentinelWritten = new Promise((resolve, reject) => { + Service.setFxAMigrationSentinel = function(arg) { + haveStartedSentinel = true; + return origSetFxAMigrationSentinel.call(Service, arg).then(result => { + Service.setFxAMigrationSentinel = origSetFxAMigrationSentinel; + resolve(result); + return result; + }); + } + }); + + // We are now configured for legacy sync, but we aren't in an EOL state yet, + // so should still be not waiting for a user. + Assert.deepEqual((yield fxaMigrator._queueCurrentUserState()), null, + "no user state before server EOL"); + + // Start a sync - this will cause an EOL notification which the migrator's + // observer will notice. + let promise = promiseOneObserver("fxa-migration:state-changed"); + _("Starting sync"); + Service.sync(); + _("Finished sync"); + + // We should have seen the observer, so be waiting for an FxA user. + Assert.equal((yield promise).data, fxaMigrator.STATE_USER_FXA, "now waiting for FxA.") + + // Re-calling our user-state promise should also reflect the same state. + Assert.equal((yield fxaMigrator._queueCurrentUserState()), + fxaMigrator.STATE_USER_FXA, + "still waiting for FxA."); + + // arrange for an unverified FxA user. + let config = makeIdentityConfig({username: FXA_USERNAME}); + let fxa = new FxAccounts({}); + config.fxaccount.user.email = config.username; + delete config.fxaccount.user.verified; + // *sob* - shouldn't need this boilerplate + fxa.internal.currentAccountState.getCertificate = function(data, keyPair, mustBeValidUntil) { + this.cert = { + validUntil: fxa.internal.now() + CERT_LIFETIME, + cert: "certificate", + }; + return Promise.resolve(this.cert.cert); + }; + + // As soon as we set the FxA user the observers should fire and magically + // transition. + promise = promiseOneObserver("fxa-migration:state-changed"); + fxAccounts.setSignedInUser(config.fxaccount.user); + + let observerInfo = yield promise; + Assert.equal(observerInfo.data, + fxaMigrator.STATE_USER_FXA_VERIFIED, + "now waiting for verification"); + Assert.ok(observerInfo.subject instanceof Ci.nsISupportsString, + "email was passed to observer"); + Assert.equal(observerInfo.subject.data, + FXA_USERNAME, + "email passed to observer is correct"); + + // should have seen the user set, so state should automatically update. + Assert.equal((yield fxaMigrator._queueCurrentUserState()), + fxaMigrator.STATE_USER_FXA_VERIFIED, + "now waiting for verification"); + + // Before we verify the user, fire off a sync that calls us back during + // the sync and before it completes - this way we can ensure we do the right + // thing in terms of blocking sync and waiting for it to complete. + + let wasWaiting = false; + // This is a PITA as sync is pseudo-blocking. + engine._syncFinish = function () { + // We aren't in a generator here, so use a helper to block on promises. + function getState() { + let cb = Async.makeSpinningCallback(); + fxaMigrator._queueCurrentUserState().then(state => cb(null, state)); + return cb.wait(); + } + // should still be waiting for verification. + Assert.equal(getState(), fxaMigrator.STATE_USER_FXA_VERIFIED, + "still waiting for verification"); + + // arrange for the user to be verified. The fxAccount's mock story is + // broken, so go behind its back. + config.fxaccount.user.verified = true; + fxAccounts.setSignedInUser(config.fxaccount.user); + Services.obs.notifyObservers(null, ONVERIFIED_NOTIFICATION, null); + + // spinningly wait for the migrator to catch up - sync is running so + // we should be in a 'null' user-state as there is no user-action + // necessary. + let cb = Async.makeSpinningCallback(); + promiseOneObserver("fxa-migration:state-changed").then(({ data: state }) => cb(null, state)); + Assert.equal(cb.wait(), null, "no user action necessary while sync completes."); + + // We must not have started writing the sentinel yet. + Assert.ok(!haveStartedSentinel, "haven't written a sentinel yet"); + + // sync should be blocked from continuing + Assert.ok(Service.scheduler.isBlocked, "sync is blocked.") + + wasWaiting = true; + throw ex; + }; + + _("Starting sync"); + Service.sync(); + _("Finished sync"); + + // mock sync so we can ensure the final sync is scheduled with the FxA user. + // (letting a "normal" sync complete is a PITA without mocking huge amounts + // of FxA infra) + let promiseFinalSync = new Promise((resolve, reject) => { + let oldSync = Service.sync; + Service.sync = function() { + Service.sync = oldSync; + resolve(); + } + }); + + Assert.ok(wasWaiting, "everything was good while sync was running.") + + // The migration is now going to run to completion. + // sync should still be "blocked" + Assert.ok(Service.scheduler.isBlocked, "sync is blocked."); + + // We should see the migration sentinel written and it should return true. + Assert.ok((yield promiseSentinelWritten), "wrote the sentinel"); + + // And we should see a new sync start + yield promiseFinalSync; + + // and we should be configured for FxA + let WeaveService = Cc["@mozilla.org/weave/service;1"] + .getService(Components.interfaces.nsISupports) + .wrappedJSObject; + Assert.ok(WeaveService.fxAccountsEnabled, "FxA is enabled"); + Assert.ok(Service.identity instanceof BrowserIDManager, + "sync is configured with the browserid_identity provider."); + Assert.equal(Service.identity.username, config.username, "correct user configured") + Assert.ok(!Service.scheduler.isBlocked, "sync is not blocked.") + // and the user state should remain null. + Assert.deepEqual((yield fxaMigrator._queueCurrentUserState()), + null, + "still no user action necessary"); + // and our engines should be in the same enabled/disabled state as before. + Assert.ok(!Service.engineManager.get("addons").enabled, "addons is still disabled"); + Assert.ok(Service.engineManager.get("passwords").enabled, "passwords is still enabled"); + + // aaaand, we are done - clean up. + yield promiseStopServer(server); +}); + + +function run_test() { + initTestLogging(); + do_register_cleanup(() => { + fxaMigrator.finalize(); + Svc.Prefs.resetBranch(""); + }); + run_next_test(); +} |