summaryrefslogtreecommitdiffstats
path: root/services/sync/tests/unit/test_fxa_migration.js
diff options
context:
space:
mode:
Diffstat (limited to 'services/sync/tests/unit/test_fxa_migration.js')
-rw-r--r--services/sync/tests/unit/test_fxa_migration.js279
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();
+}