diff options
Diffstat (limited to 'services/sync/tests/unit/test_fxa_node_reassignment.js')
-rw-r--r-- | services/sync/tests/unit/test_fxa_node_reassignment.js | 689 |
1 files changed, 321 insertions, 368 deletions
diff --git a/services/sync/tests/unit/test_fxa_node_reassignment.js b/services/sync/tests/unit/test_fxa_node_reassignment.js index 3e4cefd53..2f61afd6f 100644 --- a/services/sync/tests/unit/test_fxa_node_reassignment.js +++ b/services/sync/tests/unit/test_fxa_node_reassignment.js @@ -1,368 +1,321 @@ -/* Any copyright is dedicated to the Public Domain. - http://creativecommons.org/publicdomain/zero/1.0/ */ - -_("Test that node reassignment happens correctly using the FxA identity mgr."); -// The node-reassignment logic is quite different for FxA than for the legacy -// provider. In particular, there's no special request necessary for -// reassignment - it comes from the token server - so we need to ensure the -// Fxa cluster manager grabs a new token. - -Cu.import("resource://gre/modules/Log.jsm"); -Cu.import("resource://services-common/rest.js"); -Cu.import("resource://services-sync/constants.js"); -Cu.import("resource://services-sync/service.js"); -Cu.import("resource://services-sync/status.js"); -Cu.import("resource://services-sync/util.js"); -Cu.import("resource://testing-common/services/sync/rotaryengine.js"); -Cu.import("resource://services-sync/browserid_identity.js"); -Cu.import("resource://testing-common/services/sync/utils.js"); - -Service.engineManager.clear(); - -function run_test() { - Log.repository.getLogger("Sync.AsyncResource").level = Log.Level.Trace; - Log.repository.getLogger("Sync.ErrorHandler").level = Log.Level.Trace; - Log.repository.getLogger("Sync.Resource").level = Log.Level.Trace; - Log.repository.getLogger("Sync.RESTRequest").level = Log.Level.Trace; - Log.repository.getLogger("Sync.Service").level = Log.Level.Trace; - Log.repository.getLogger("Sync.SyncScheduler").level = Log.Level.Trace; - initTestLogging(); - - Service.engineManager.register(RotaryEngine); - - // Setup the FxA identity manager and cluster manager. - Status.__authManager = Service.identity = new BrowserIDManager(); - Service._clusterManager = Service.identity.createClusterManager(Service); - - // None of the failures in this file should result in a UI error. - function onUIError() { - do_throw("Errors should not be presented in the UI."); - } - Svc.Obs.add("weave:ui:login:error", onUIError); - Svc.Obs.add("weave:ui:sync:error", onUIError); - - run_next_test(); -} - - -// API-compatible with SyncServer handler. Bind `handler` to something to use -// as a ServerCollection handler. -function handleReassign(handler, req, resp) { - resp.setStatusLine(req.httpVersion, 401, "Node reassignment"); - resp.setHeader("Content-Type", "application/json"); - let reassignBody = JSON.stringify({error: "401inator in place"}); - resp.bodyOutputStream.write(reassignBody, reassignBody.length); -} - -var numTokenRequests = 0; - -function prepareServer(cbAfterTokenFetch) { - let config = makeIdentityConfig({username: "johndoe"}); - // A server callback to ensure we don't accidentally hit the wrong endpoint - // after a node reassignment. - let callback = { - __proto__: SyncServerCallback, - onRequest(req, resp) { - let full = `${req.scheme}://${req.host}:${req.port}${req.path}`; - do_check_true(full.startsWith(config.fxaccount.token.endpoint), - `request made to ${full}`); - } - } - let server = new SyncServer(callback); - server.registerUser("johndoe"); - server.start(); - - // Set the token endpoint for the initial token request that's done implicitly - // via configureIdentity. - config.fxaccount.token.endpoint = server.baseURI + "1.1/johndoe/"; - // And future token fetches will do magic around numReassigns. - let numReassigns = 0; - return configureIdentity(config).then(() => { - Service.identity._tokenServerClient = { - getTokenFromBrowserIDAssertion: function(uri, assertion, cb) { - // Build a new URL with trailing zeros for the SYNC_VERSION part - this - // will still be seen as equivalent by the test server, but different - // by sync itself. - numReassigns += 1; - let trailingZeros = new Array(numReassigns + 1).join('0'); - let token = config.fxaccount.token; - token.endpoint = server.baseURI + "1.1" + trailingZeros + "/johndoe"; - token.uid = config.username; - numTokenRequests += 1; - cb(null, token); - if (cbAfterTokenFetch) { - cbAfterTokenFetch(); - } - }, - }; - return server; - }); -} - -function getReassigned() { - try { - return Services.prefs.getBoolPref("services.sync.lastSyncReassigned"); - } catch (ex) { - if (ex.result == Cr.NS_ERROR_UNEXPECTED) { - return false; - } - do_throw("Got exception retrieving lastSyncReassigned: " + - Log.exceptionStr(ex)); - } -} - -/** - * Make a test request to `url`, then watch the result of two syncs - * to ensure that a node request was made. - * Runs `between` between the two. This can be used to undo deliberate failure - * setup, detach observers, etc. - */ -function* syncAndExpectNodeReassignment(server, firstNotification, between, - secondNotification, url) { - _("Starting syncAndExpectNodeReassignment\n"); - let deferred = Promise.defer(); - function onwards() { - let numTokenRequestsBefore; - function onFirstSync() { - _("First sync completed."); - Svc.Obs.remove(firstNotification, onFirstSync); - Svc.Obs.add(secondNotification, onSecondSync); - - do_check_eq(Service.clusterURL, ""); - - // Track whether we fetched a new token. - numTokenRequestsBefore = numTokenRequests; - - // Allow for tests to clean up error conditions. - between(); - } - function onSecondSync() { - _("Second sync completed."); - Svc.Obs.remove(secondNotification, onSecondSync); - Service.scheduler.clearSyncTriggers(); - - // Make absolutely sure that any event listeners are done with their work - // before we proceed. - waitForZeroTimer(function () { - _("Second sync nextTick."); - do_check_eq(numTokenRequests, numTokenRequestsBefore + 1, "fetched a new token"); - Service.startOver(); - server.stop(deferred.resolve); - }); - } - - Svc.Obs.add(firstNotification, onFirstSync); - Service.sync(); - } - - // Make sure that we really do get a 401 (but we can only do that if we are - // already logged in, as the login process is what sets up the URLs) - if (Service.isLoggedIn) { - _("Making request to " + url + " which should 401"); - let request = new RESTRequest(url); - request.get(function () { - do_check_eq(request.response.status, 401); - Utils.nextTick(onwards); - }); - } else { - _("Skipping preliminary validation check for a 401 as we aren't logged in"); - Utils.nextTick(onwards); - } - yield deferred.promise; -} - -// Check that when we sync we don't request a new token by default - our -// test setup has configured the client with a valid token, and that token -// should be used to form the cluster URL. -add_task(function* test_single_token_fetch() { - _("Test a normal sync only fetches 1 token"); - - let numTokenFetches = 0; - - function afterTokenFetch() { - numTokenFetches++; - } - - // Set the cluster URL to an "old" version - this is to ensure we don't - // use that old cached version for the first sync but prefer the value - // we got from the token (and as above, we are also checking we don't grab - // a new token). If the test actually attempts to connect to this URL - // it will crash. - Service.clusterURL = "http://example.com/"; - - let server = yield prepareServer(afterTokenFetch); - - do_check_false(Service.isLoggedIn, "not already logged in"); - Service.sync(); - do_check_eq(Status.sync, SYNC_SUCCEEDED, "sync succeeded"); - do_check_eq(numTokenFetches, 0, "didn't fetch a new token"); - // A bit hacky, but given we know how prepareServer works we can deduce - // that clusterURL we expect. - let expectedClusterURL = server.baseURI + "1.1/johndoe/"; - do_check_eq(Service.clusterURL, expectedClusterURL); - yield new Promise(resolve => server.stop(resolve)); -}); - -add_task(function* test_momentary_401_engine() { - _("Test a failure for engine URLs that's resolved by reassignment."); - let server = yield prepareServer(); - let john = server.user("johndoe"); - - _("Enabling the Rotary engine."); - let engine = Service.engineManager.get("rotary"); - engine.enabled = true; - - // We need the server to be correctly set up prior to experimenting. Do this - // through a sync. - let global = {syncID: Service.syncID, - storageVersion: STORAGE_VERSION, - rotary: {version: engine.version, - syncID: engine.syncID}} - john.createCollection("meta").insert("global", global); - - _("First sync to prepare server contents."); - Service.sync(); - - _("Setting up Rotary collection to 401."); - let rotary = john.createCollection("rotary"); - let oldHandler = rotary.collectionHandler; - rotary.collectionHandler = handleReassign.bind(this, undefined); - - // We want to verify that the clusterURL pref has been cleared after a 401 - // inside a sync. Flag the Rotary engine to need syncing. - john.collection("rotary").timestamp += 1000; - - function between() { - _("Undoing test changes."); - rotary.collectionHandler = oldHandler; - - function onLoginStart() { - // lastSyncReassigned shouldn't be cleared until a sync has succeeded. - _("Ensuring that lastSyncReassigned is still set at next sync start."); - Svc.Obs.remove("weave:service:login:start", onLoginStart); - do_check_true(getReassigned()); - } - - _("Adding observer that lastSyncReassigned is still set on login."); - Svc.Obs.add("weave:service:login:start", onLoginStart); - } - - yield syncAndExpectNodeReassignment(server, - "weave:service:sync:finish", - between, - "weave:service:sync:finish", - Service.storageURL + "rotary"); -}); - -// This test ends up being a failing info fetch *after we're already logged in*. -add_task(function* test_momentary_401_info_collections_loggedin() { - _("Test a failure for info/collections after login that's resolved by reassignment."); - let server = yield prepareServer(); - - _("First sync to prepare server contents."); - Service.sync(); - - _("Arrange for info/collections to return a 401."); - let oldHandler = server.toplevelHandlers.info; - server.toplevelHandlers.info = handleReassign; - - function undo() { - _("Undoing test changes."); - server.toplevelHandlers.info = oldHandler; - } - - do_check_true(Service.isLoggedIn, "already logged in"); - - yield syncAndExpectNodeReassignment(server, - "weave:service:sync:error", - undo, - "weave:service:sync:finish", - Service.infoURL); -}); - -// This test ends up being a failing info fetch *before we're logged in*. -// In this case we expect to recover during the login phase - so the first -// sync succeeds. -add_task(function* test_momentary_401_info_collections_loggedout() { - _("Test a failure for info/collections before login that's resolved by reassignment."); - - let oldHandler; - let sawTokenFetch = false; - - function afterTokenFetch() { - // After a single token fetch, we undo our evil handleReassign hack, so - // the next /info request returns the collection instead of a 401 - server.toplevelHandlers.info = oldHandler; - sawTokenFetch = true; - } - - let server = yield prepareServer(afterTokenFetch); - - // Return a 401 for the next /info request - it will be reset immediately - // after a new token is fetched. - oldHandler = server.toplevelHandlers.info - server.toplevelHandlers.info = handleReassign; - - do_check_false(Service.isLoggedIn, "not already logged in"); - - Service.sync(); - do_check_eq(Status.sync, SYNC_SUCCEEDED, "sync succeeded"); - // sync was successful - check we grabbed a new token. - do_check_true(sawTokenFetch, "a new token was fetched by this test.") - // and we are done. - Service.startOver(); - let deferred = Promise.defer(); - server.stop(deferred.resolve); - yield deferred.promise; -}); - -// This test ends up being a failing meta/global fetch *after we're already logged in*. -add_task(function* test_momentary_401_storage_loggedin() { - _("Test a failure for any storage URL after login that's resolved by" + - "reassignment."); - let server = yield prepareServer(); - - _("First sync to prepare server contents."); - Service.sync(); - - _("Arrange for meta/global to return a 401."); - let oldHandler = server.toplevelHandlers.storage; - server.toplevelHandlers.storage = handleReassign; - - function undo() { - _("Undoing test changes."); - server.toplevelHandlers.storage = oldHandler; - } - - do_check_true(Service.isLoggedIn, "already logged in"); - - yield syncAndExpectNodeReassignment(server, - "weave:service:sync:error", - undo, - "weave:service:sync:finish", - Service.storageURL + "meta/global"); -}); - -// This test ends up being a failing meta/global fetch *before we've logged in*. -add_task(function* test_momentary_401_storage_loggedout() { - _("Test a failure for any storage URL before login, not just engine parts. " + - "Resolved by reassignment."); - let server = yield prepareServer(); - - // Return a 401 for all storage requests. - let oldHandler = server.toplevelHandlers.storage; - server.toplevelHandlers.storage = handleReassign; - - function undo() { - _("Undoing test changes."); - server.toplevelHandlers.storage = oldHandler; - } - - do_check_false(Service.isLoggedIn, "already logged in"); - - yield syncAndExpectNodeReassignment(server, - "weave:service:login:error", - undo, - "weave:service:sync:finish", - Service.storageURL + "meta/global"); -}); +/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+_("Test that node reassignment happens correctly using the FxA identity mgr.");
+// The node-reassignment logic is quite different for FxA than for the legacy
+// provider. In particular, there's no special request necessary for
+// reassignment - it comes from the token server - so we need to ensure the
+// Fxa cluster manager grabs a new token.
+
+Cu.import("resource://gre/modules/Log.jsm");
+Cu.import("resource://services-common/rest.js");
+Cu.import("resource://services-sync/constants.js");
+Cu.import("resource://services-sync/service.js");
+Cu.import("resource://services-sync/status.js");
+Cu.import("resource://services-sync/util.js");
+Cu.import("resource://testing-common/services/sync/rotaryengine.js");
+Cu.import("resource://services-sync/browserid_identity.js");
+Cu.import("resource://testing-common/services/sync/utils.js");
+
+Service.engineManager.clear();
+
+function run_test() {
+ Log.repository.getLogger("Sync.AsyncResource").level = Log.Level.Trace;
+ Log.repository.getLogger("Sync.ErrorHandler").level = Log.Level.Trace;
+ Log.repository.getLogger("Sync.Resource").level = Log.Level.Trace;
+ Log.repository.getLogger("Sync.RESTRequest").level = Log.Level.Trace;
+ Log.repository.getLogger("Sync.Service").level = Log.Level.Trace;
+ Log.repository.getLogger("Sync.SyncScheduler").level = Log.Level.Trace;
+ initTestLogging();
+
+ Service.engineManager.register(RotaryEngine);
+
+ // Setup the FxA identity manager and cluster manager.
+ Status.__authManager = Service.identity = new BrowserIDManager();
+ Service._clusterManager = Service.identity.createClusterManager(Service);
+
+ // None of the failures in this file should result in a UI error.
+ function onUIError() {
+ do_throw("Errors should not be presented in the UI.");
+ }
+ Svc.Obs.add("weave:ui:login:error", onUIError);
+ Svc.Obs.add("weave:ui:sync:error", onUIError);
+
+ run_next_test();
+}
+
+
+// API-compatible with SyncServer handler. Bind `handler` to something to use
+// as a ServerCollection handler.
+function handleReassign(handler, req, resp) {
+ resp.setStatusLine(req.httpVersion, 401, "Node reassignment");
+ resp.setHeader("Content-Type", "application/json");
+ let reassignBody = JSON.stringify({error: "401inator in place"});
+ resp.bodyOutputStream.write(reassignBody, reassignBody.length);
+}
+
+let numTokenRequests = 0;
+
+function prepareServer(cbAfterTokenFetch) {
+ let config = makeIdentityConfig({username: "johndoe"});
+ let server = new SyncServer();
+ server.registerUser("johndoe");
+ server.start();
+
+ // Set the token endpoint for the initial token request that's done implicitly
+ // via configureIdentity.
+ config.fxaccount.token.endpoint = server.baseURI + "1.1/johndoe";
+ // And future token fetches will do magic around numReassigns.
+ let numReassigns = 0;
+ return configureIdentity(config).then(() => {
+ Service.identity._tokenServerClient = {
+ getTokenFromBrowserIDAssertion: function(uri, assertion, cb) {
+ // Build a new URL with trailing zeros for the SYNC_VERSION part - this
+ // will still be seen as equivalent by the test server, but different
+ // by sync itself.
+ numReassigns += 1;
+ let trailingZeros = new Array(numReassigns + 1).join('0');
+ let token = config.fxaccount.token;
+ token.endpoint = server.baseURI + "1.1" + trailingZeros + "/johndoe";
+ token.uid = config.username;
+ numTokenRequests += 1;
+ cb(null, token);
+ if (cbAfterTokenFetch) {
+ cbAfterTokenFetch();
+ }
+ },
+ };
+ Service.clusterURL = config.fxaccount.token.endpoint;
+ return server;
+ });
+}
+
+function getReassigned() {
+ try {
+ return Services.prefs.getBoolPref("services.sync.lastSyncReassigned");
+ } catch (ex if (ex.result == Cr.NS_ERROR_UNEXPECTED)) {
+ return false;
+ } catch (ex) {
+ do_throw("Got exception retrieving lastSyncReassigned: " +
+ Utils.exceptionStr(ex));
+ }
+}
+
+/**
+ * Make a test request to `url`, then watch the result of two syncs
+ * to ensure that a node request was made.
+ * Runs `between` between the two. This can be used to undo deliberate failure
+ * setup, detach observers, etc.
+ */
+function syncAndExpectNodeReassignment(server, firstNotification, between,
+ secondNotification, url) {
+ _("Starting syncAndExpectNodeReassignment\n");
+ let deferred = Promise.defer();
+ function onwards() {
+ let numTokenRequestsBefore;
+ function onFirstSync() {
+ _("First sync completed.");
+ Svc.Obs.remove(firstNotification, onFirstSync);
+ Svc.Obs.add(secondNotification, onSecondSync);
+
+ do_check_eq(Service.clusterURL, "");
+
+ // Track whether we fetched a new token.
+ numTokenRequestsBefore = numTokenRequests;
+
+ // Allow for tests to clean up error conditions.
+ between();
+ }
+ function onSecondSync() {
+ _("Second sync completed.");
+ Svc.Obs.remove(secondNotification, onSecondSync);
+ Service.scheduler.clearSyncTriggers();
+
+ // Make absolutely sure that any event listeners are done with their work
+ // before we proceed.
+ waitForZeroTimer(function () {
+ _("Second sync nextTick.");
+ do_check_eq(numTokenRequests, numTokenRequestsBefore + 1, "fetched a new token");
+ Service.startOver();
+ server.stop(deferred.resolve);
+ });
+ }
+
+ Svc.Obs.add(firstNotification, onFirstSync);
+ Service.sync();
+ }
+
+ // Make sure that it works!
+ _("Making request to " + url + " which should 401");
+ let request = new RESTRequest(url);
+ request.get(function () {
+ do_check_eq(request.response.status, 401);
+ Utils.nextTick(onwards);
+ });
+ yield deferred.promise;
+}
+
+add_task(function test_momentary_401_engine() {
+ _("Test a failure for engine URLs that's resolved by reassignment.");
+ let server = yield prepareServer();
+ let john = server.user("johndoe");
+
+ _("Enabling the Rotary engine.");
+ let engine = Service.engineManager.get("rotary");
+ engine.enabled = true;
+
+ // We need the server to be correctly set up prior to experimenting. Do this
+ // through a sync.
+ let global = {syncID: Service.syncID,
+ storageVersion: STORAGE_VERSION,
+ rotary: {version: engine.version,
+ syncID: engine.syncID}}
+ john.createCollection("meta").insert("global", global);
+
+ _("First sync to prepare server contents.");
+ Service.sync();
+
+ _("Setting up Rotary collection to 401.");
+ let rotary = john.createCollection("rotary");
+ let oldHandler = rotary.collectionHandler;
+ rotary.collectionHandler = handleReassign.bind(this, undefined);
+
+ // We want to verify that the clusterURL pref has been cleared after a 401
+ // inside a sync. Flag the Rotary engine to need syncing.
+ john.collection("rotary").timestamp += 1000;
+
+ function between() {
+ _("Undoing test changes.");
+ rotary.collectionHandler = oldHandler;
+
+ function onLoginStart() {
+ // lastSyncReassigned shouldn't be cleared until a sync has succeeded.
+ _("Ensuring that lastSyncReassigned is still set at next sync start.");
+ Svc.Obs.remove("weave:service:login:start", onLoginStart);
+ do_check_true(getReassigned());
+ }
+
+ _("Adding observer that lastSyncReassigned is still set on login.");
+ Svc.Obs.add("weave:service:login:start", onLoginStart);
+ }
+
+ yield syncAndExpectNodeReassignment(server,
+ "weave:service:sync:finish",
+ between,
+ "weave:service:sync:finish",
+ Service.storageURL + "rotary");
+});
+
+// This test ends up being a failing info fetch *after we're already logged in*.
+add_task(function test_momentary_401_info_collections_loggedin() {
+ _("Test a failure for info/collections after login that's resolved by reassignment.");
+ let server = yield prepareServer();
+
+ _("First sync to prepare server contents.");
+ Service.sync();
+
+ _("Arrange for info/collections to return a 401.");
+ let oldHandler = server.toplevelHandlers.info;
+ server.toplevelHandlers.info = handleReassign;
+
+ function undo() {
+ _("Undoing test changes.");
+ server.toplevelHandlers.info = oldHandler;
+ }
+
+ do_check_true(Service.isLoggedIn, "already logged in");
+
+ yield syncAndExpectNodeReassignment(server,
+ "weave:service:sync:error",
+ undo,
+ "weave:service:sync:finish",
+ Service.infoURL);
+});
+
+// This test ends up being a failing info fetch *before we're logged in*.
+// In this case we expect to recover during the login phase - so the first
+// sync succeeds.
+add_task(function test_momentary_401_info_collections_loggedout() {
+ _("Test a failure for info/collections before login that's resolved by reassignment.");
+
+ let oldHandler;
+ let sawTokenFetch = false;
+
+ function afterTokenFetch() {
+ // After a single token fetch, we undo our evil handleReassign hack, so
+ // the next /info request returns the collection instead of a 401
+ server.toplevelHandlers.info = oldHandler;
+ sawTokenFetch = true;
+ }
+
+ let server = yield prepareServer(afterTokenFetch);
+
+ // Return a 401 for the next /info request - it will be reset immediately
+ // after a new token is fetched.
+ oldHandler = server.toplevelHandlers.info
+ server.toplevelHandlers.info = handleReassign;
+
+ do_check_false(Service.isLoggedIn, "not already logged in");
+
+ Service.sync();
+ do_check_eq(Status.sync, SYNC_SUCCEEDED, "sync succeeded");
+ // sync was successful - check we grabbed a new token.
+ do_check_true(sawTokenFetch, "a new token was fetched by this test.")
+ // and we are done.
+ Service.startOver();
+ let deferred = Promise.defer();
+ server.stop(deferred.resolve);
+ yield deferred.promise;
+});
+
+// This test ends up being a failing meta/global fetch *after we're already logged in*.
+add_task(function test_momentary_401_storage_loggedin() {
+ _("Test a failure for any storage URL after login that's resolved by" +
+ "reassignment.");
+ let server = yield prepareServer();
+
+ _("First sync to prepare server contents.");
+ Service.sync();
+
+ _("Arrange for meta/global to return a 401.");
+ let oldHandler = server.toplevelHandlers.storage;
+ server.toplevelHandlers.storage = handleReassign;
+
+ function undo() {
+ _("Undoing test changes.");
+ server.toplevelHandlers.storage = oldHandler;
+ }
+
+ do_check_true(Service.isLoggedIn, "already logged in");
+
+ yield syncAndExpectNodeReassignment(server,
+ "weave:service:sync:error",
+ undo,
+ "weave:service:sync:finish",
+ Service.storageURL + "meta/global");
+});
+
+// This test ends up being a failing meta/global fetch *before we've logged in*.
+add_task(function test_momentary_401_storage_loggedout() {
+ _("Test a failure for any storage URL before login, not just engine parts. " +
+ "Resolved by reassignment.");
+ let server = yield prepareServer();
+
+ // Return a 401 for all storage requests.
+ let oldHandler = server.toplevelHandlers.storage;
+ server.toplevelHandlers.storage = handleReassign;
+
+ function undo() {
+ _("Undoing test changes.");
+ server.toplevelHandlers.storage = oldHandler;
+ }
+
+ do_check_false(Service.isLoggedIn, "already logged in");
+
+ yield syncAndExpectNodeReassignment(server,
+ "weave:service:login:error",
+ undo,
+ "weave:service:sync:finish",
+ Service.storageURL + "meta/global");
+});
+
|