services/sync/tests/unit/test_fxa_node_reassignment.js

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/services/sync/tests/unit/test_fxa_node_reassignment.js	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,321 @@
     1.4 +/* Any copyright is dedicated to the Public Domain.
     1.5 +   http://creativecommons.org/publicdomain/zero/1.0/ */
     1.6 +
     1.7 +_("Test that node reassignment happens correctly using the FxA identity mgr.");
     1.8 +// The node-reassignment logic is quite different for FxA than for the legacy
     1.9 +// provider.  In particular, there's no special request necessary for
    1.10 +// reassignment - it comes from the token server - so we need to ensure the
    1.11 +// Fxa cluster manager grabs a new token.
    1.12 +
    1.13 +Cu.import("resource://gre/modules/Log.jsm");
    1.14 +Cu.import("resource://services-common/rest.js");
    1.15 +Cu.import("resource://services-sync/constants.js");
    1.16 +Cu.import("resource://services-sync/service.js");
    1.17 +Cu.import("resource://services-sync/status.js");
    1.18 +Cu.import("resource://services-sync/util.js");
    1.19 +Cu.import("resource://testing-common/services/sync/rotaryengine.js");
    1.20 +Cu.import("resource://services-sync/browserid_identity.js");
    1.21 +Cu.import("resource://testing-common/services/sync/utils.js");
    1.22 +
    1.23 +Service.engineManager.clear();
    1.24 +
    1.25 +function run_test() {
    1.26 +  Log.repository.getLogger("Sync.AsyncResource").level = Log.Level.Trace;
    1.27 +  Log.repository.getLogger("Sync.ErrorHandler").level  = Log.Level.Trace;
    1.28 +  Log.repository.getLogger("Sync.Resource").level      = Log.Level.Trace;
    1.29 +  Log.repository.getLogger("Sync.RESTRequest").level   = Log.Level.Trace;
    1.30 +  Log.repository.getLogger("Sync.Service").level       = Log.Level.Trace;
    1.31 +  Log.repository.getLogger("Sync.SyncScheduler").level = Log.Level.Trace;
    1.32 +  initTestLogging();
    1.33 +
    1.34 +  Service.engineManager.register(RotaryEngine);
    1.35 +
    1.36 +  // Setup the FxA identity manager and cluster manager.
    1.37 +  Status.__authManager = Service.identity = new BrowserIDManager();
    1.38 +  Service._clusterManager = Service.identity.createClusterManager(Service);
    1.39 +
    1.40 +  // None of the failures in this file should result in a UI error.
    1.41 +  function onUIError() {
    1.42 +    do_throw("Errors should not be presented in the UI.");
    1.43 +  }
    1.44 +  Svc.Obs.add("weave:ui:login:error", onUIError);
    1.45 +  Svc.Obs.add("weave:ui:sync:error", onUIError);
    1.46 +
    1.47 +  run_next_test();
    1.48 +}
    1.49 +
    1.50 +
    1.51 +// API-compatible with SyncServer handler. Bind `handler` to something to use
    1.52 +// as a ServerCollection handler.
    1.53 +function handleReassign(handler, req, resp) {
    1.54 +  resp.setStatusLine(req.httpVersion, 401, "Node reassignment");
    1.55 +  resp.setHeader("Content-Type", "application/json");
    1.56 +  let reassignBody = JSON.stringify({error: "401inator in place"});
    1.57 +  resp.bodyOutputStream.write(reassignBody, reassignBody.length);
    1.58 +}
    1.59 +
    1.60 +let numTokenRequests = 0;
    1.61 +
    1.62 +function prepareServer(cbAfterTokenFetch) {
    1.63 +  let config = makeIdentityConfig({username: "johndoe"});
    1.64 +  let server = new SyncServer();
    1.65 +  server.registerUser("johndoe");
    1.66 +  server.start();
    1.67 +
    1.68 +  // Set the token endpoint for the initial token request that's done implicitly
    1.69 +  // via configureIdentity.
    1.70 +  config.fxaccount.token.endpoint = server.baseURI + "1.1/johndoe";
    1.71 +  // And future token fetches will do magic around numReassigns.
    1.72 +  let numReassigns = 0;
    1.73 +  return configureIdentity(config).then(() => {
    1.74 +    Service.identity._tokenServerClient = {
    1.75 +      getTokenFromBrowserIDAssertion: function(uri, assertion, cb) {
    1.76 +        // Build a new URL with trailing zeros for the SYNC_VERSION part - this
    1.77 +        // will still be seen as equivalent by the test server, but different
    1.78 +        // by sync itself.
    1.79 +        numReassigns += 1;
    1.80 +        let trailingZeros = new Array(numReassigns + 1).join('0');
    1.81 +        let token = config.fxaccount.token;
    1.82 +        token.endpoint = server.baseURI + "1.1" + trailingZeros + "/johndoe";
    1.83 +        token.uid = config.username;
    1.84 +        numTokenRequests += 1;
    1.85 +        cb(null, token);
    1.86 +        if (cbAfterTokenFetch) {
    1.87 +          cbAfterTokenFetch();
    1.88 +        }
    1.89 +      },
    1.90 +    };
    1.91 +    Service.clusterURL = config.fxaccount.token.endpoint;
    1.92 +    return server;
    1.93 +  });
    1.94 +}
    1.95 +
    1.96 +function getReassigned() {
    1.97 +  try {
    1.98 +    return Services.prefs.getBoolPref("services.sync.lastSyncReassigned");
    1.99 +  } catch (ex if (ex.result == Cr.NS_ERROR_UNEXPECTED)) {
   1.100 +    return false;
   1.101 +  } catch (ex) {
   1.102 +    do_throw("Got exception retrieving lastSyncReassigned: " +
   1.103 +             Utils.exceptionStr(ex));
   1.104 +  }
   1.105 +}
   1.106 +
   1.107 +/**
   1.108 + * Make a test request to `url`, then watch the result of two syncs
   1.109 + * to ensure that a node request was made.
   1.110 + * Runs `between` between the two. This can be used to undo deliberate failure
   1.111 + * setup, detach observers, etc.
   1.112 + */
   1.113 +function syncAndExpectNodeReassignment(server, firstNotification, between,
   1.114 +                                       secondNotification, url) {
   1.115 +  _("Starting syncAndExpectNodeReassignment\n");
   1.116 +  let deferred = Promise.defer();
   1.117 +  function onwards() {
   1.118 +    let numTokenRequestsBefore;
   1.119 +    function onFirstSync() {
   1.120 +      _("First sync completed.");
   1.121 +      Svc.Obs.remove(firstNotification, onFirstSync);
   1.122 +      Svc.Obs.add(secondNotification, onSecondSync);
   1.123 +
   1.124 +      do_check_eq(Service.clusterURL, "");
   1.125 +
   1.126 +      // Track whether we fetched a new token.
   1.127 +      numTokenRequestsBefore = numTokenRequests;
   1.128 +
   1.129 +      // Allow for tests to clean up error conditions.
   1.130 +      between();
   1.131 +    }
   1.132 +    function onSecondSync() {
   1.133 +      _("Second sync completed.");
   1.134 +      Svc.Obs.remove(secondNotification, onSecondSync);
   1.135 +      Service.scheduler.clearSyncTriggers();
   1.136 +
   1.137 +      // Make absolutely sure that any event listeners are done with their work
   1.138 +      // before we proceed.
   1.139 +      waitForZeroTimer(function () {
   1.140 +        _("Second sync nextTick.");
   1.141 +        do_check_eq(numTokenRequests, numTokenRequestsBefore + 1, "fetched a new token");
   1.142 +        Service.startOver();
   1.143 +        server.stop(deferred.resolve);
   1.144 +      });
   1.145 +    }
   1.146 +
   1.147 +    Svc.Obs.add(firstNotification, onFirstSync);
   1.148 +    Service.sync();
   1.149 +  }
   1.150 +
   1.151 +  // Make sure that it works!
   1.152 +  _("Making request to " + url + " which should 401");
   1.153 +  let request = new RESTRequest(url);
   1.154 +  request.get(function () {
   1.155 +    do_check_eq(request.response.status, 401);
   1.156 +    Utils.nextTick(onwards);
   1.157 +  });
   1.158 +  yield deferred.promise;
   1.159 +}
   1.160 +
   1.161 +add_task(function test_momentary_401_engine() {
   1.162 +  _("Test a failure for engine URLs that's resolved by reassignment.");
   1.163 +  let server = yield prepareServer();
   1.164 +  let john   = server.user("johndoe");
   1.165 +
   1.166 +  _("Enabling the Rotary engine.");
   1.167 +  let engine = Service.engineManager.get("rotary");
   1.168 +  engine.enabled = true;
   1.169 +
   1.170 +  // We need the server to be correctly set up prior to experimenting. Do this
   1.171 +  // through a sync.
   1.172 +  let global = {syncID: Service.syncID,
   1.173 +                storageVersion: STORAGE_VERSION,
   1.174 +                rotary: {version: engine.version,
   1.175 +                         syncID:  engine.syncID}}
   1.176 +  john.createCollection("meta").insert("global", global);
   1.177 +
   1.178 +  _("First sync to prepare server contents.");
   1.179 +  Service.sync();
   1.180 +
   1.181 +  _("Setting up Rotary collection to 401.");
   1.182 +  let rotary = john.createCollection("rotary");
   1.183 +  let oldHandler = rotary.collectionHandler;
   1.184 +  rotary.collectionHandler = handleReassign.bind(this, undefined);
   1.185 +
   1.186 +  // We want to verify that the clusterURL pref has been cleared after a 401
   1.187 +  // inside a sync. Flag the Rotary engine to need syncing.
   1.188 +  john.collection("rotary").timestamp += 1000;
   1.189 +
   1.190 +  function between() {
   1.191 +    _("Undoing test changes.");
   1.192 +    rotary.collectionHandler = oldHandler;
   1.193 +
   1.194 +    function onLoginStart() {
   1.195 +      // lastSyncReassigned shouldn't be cleared until a sync has succeeded.
   1.196 +      _("Ensuring that lastSyncReassigned is still set at next sync start.");
   1.197 +      Svc.Obs.remove("weave:service:login:start", onLoginStart);
   1.198 +      do_check_true(getReassigned());
   1.199 +    }
   1.200 +
   1.201 +    _("Adding observer that lastSyncReassigned is still set on login.");
   1.202 +    Svc.Obs.add("weave:service:login:start", onLoginStart);
   1.203 +  }
   1.204 +
   1.205 +  yield syncAndExpectNodeReassignment(server,
   1.206 +                                      "weave:service:sync:finish",
   1.207 +                                      between,
   1.208 +                                      "weave:service:sync:finish",
   1.209 +                                      Service.storageURL + "rotary");
   1.210 +});
   1.211 +
   1.212 +// This test ends up being a failing info fetch *after we're already logged in*.
   1.213 +add_task(function test_momentary_401_info_collections_loggedin() {
   1.214 +  _("Test a failure for info/collections after login that's resolved by reassignment.");
   1.215 +  let server = yield prepareServer();
   1.216 +
   1.217 +  _("First sync to prepare server contents.");
   1.218 +  Service.sync();
   1.219 +
   1.220 +  _("Arrange for info/collections to return a 401.");
   1.221 +  let oldHandler = server.toplevelHandlers.info;
   1.222 +  server.toplevelHandlers.info = handleReassign;
   1.223 +
   1.224 +  function undo() {
   1.225 +    _("Undoing test changes.");
   1.226 +    server.toplevelHandlers.info = oldHandler;
   1.227 +  }
   1.228 +
   1.229 +  do_check_true(Service.isLoggedIn, "already logged in");
   1.230 +
   1.231 +  yield syncAndExpectNodeReassignment(server,
   1.232 +                                      "weave:service:sync:error",
   1.233 +                                      undo,
   1.234 +                                      "weave:service:sync:finish",
   1.235 +                                      Service.infoURL);
   1.236 +});
   1.237 +
   1.238 +// This test ends up being a failing info fetch *before we're logged in*.
   1.239 +// In this case we expect to recover during the login phase - so the first
   1.240 +// sync succeeds.
   1.241 +add_task(function test_momentary_401_info_collections_loggedout() {
   1.242 +  _("Test a failure for info/collections before login that's resolved by reassignment.");
   1.243 +
   1.244 +  let oldHandler;
   1.245 +  let sawTokenFetch = false;
   1.246 +
   1.247 +  function afterTokenFetch() {
   1.248 +    // After a single token fetch, we undo our evil handleReassign hack, so
   1.249 +    // the next /info request returns the collection instead of a 401
   1.250 +    server.toplevelHandlers.info = oldHandler;
   1.251 +    sawTokenFetch = true;
   1.252 +  }
   1.253 +
   1.254 +  let server = yield prepareServer(afterTokenFetch);
   1.255 +
   1.256 +  // Return a 401 for the next /info request - it will be reset immediately
   1.257 +  // after a new token is fetched.
   1.258 +  oldHandler = server.toplevelHandlers.info
   1.259 +  server.toplevelHandlers.info = handleReassign;
   1.260 +
   1.261 +  do_check_false(Service.isLoggedIn, "not already logged in");
   1.262 +
   1.263 +  Service.sync();
   1.264 +  do_check_eq(Status.sync, SYNC_SUCCEEDED, "sync succeeded");
   1.265 +  // sync was successful - check we grabbed a new token.
   1.266 +  do_check_true(sawTokenFetch, "a new token was fetched by this test.")
   1.267 +  // and we are done.
   1.268 +  Service.startOver();
   1.269 +  let deferred = Promise.defer();
   1.270 +  server.stop(deferred.resolve);
   1.271 +  yield deferred.promise;
   1.272 +});
   1.273 +
   1.274 +// This test ends up being a failing meta/global fetch *after we're already logged in*.
   1.275 +add_task(function test_momentary_401_storage_loggedin() {
   1.276 +  _("Test a failure for any storage URL after login that's resolved by" +
   1.277 +    "reassignment.");
   1.278 +  let server = yield prepareServer();
   1.279 +
   1.280 +  _("First sync to prepare server contents.");
   1.281 +  Service.sync();
   1.282 +
   1.283 +  _("Arrange for meta/global to return a 401.");
   1.284 +  let oldHandler = server.toplevelHandlers.storage;
   1.285 +  server.toplevelHandlers.storage = handleReassign;
   1.286 +
   1.287 +  function undo() {
   1.288 +    _("Undoing test changes.");
   1.289 +    server.toplevelHandlers.storage = oldHandler;
   1.290 +  }
   1.291 +
   1.292 +  do_check_true(Service.isLoggedIn, "already logged in");
   1.293 +
   1.294 +  yield syncAndExpectNodeReassignment(server,
   1.295 +                                      "weave:service:sync:error",
   1.296 +                                      undo,
   1.297 +                                      "weave:service:sync:finish",
   1.298 +                                      Service.storageURL + "meta/global");
   1.299 +});
   1.300 +
   1.301 +// This test ends up being a failing meta/global fetch *before we've logged in*.
   1.302 +add_task(function test_momentary_401_storage_loggedout() {
   1.303 +  _("Test a failure for any storage URL before login, not just engine parts. " +
   1.304 +    "Resolved by reassignment.");
   1.305 +  let server = yield prepareServer();
   1.306 +
   1.307 +  // Return a 401 for all storage requests.
   1.308 +  let oldHandler = server.toplevelHandlers.storage;
   1.309 +  server.toplevelHandlers.storage = handleReassign;
   1.310 +
   1.311 +  function undo() {
   1.312 +    _("Undoing test changes.");
   1.313 +    server.toplevelHandlers.storage = oldHandler;
   1.314 +  }
   1.315 +
   1.316 +  do_check_false(Service.isLoggedIn, "already logged in");
   1.317 +
   1.318 +  yield syncAndExpectNodeReassignment(server,
   1.319 +                                      "weave:service:login:error",
   1.320 +                                      undo,
   1.321 +                                      "weave:service:sync:finish",
   1.322 +                                      Service.storageURL + "meta/global");
   1.323 +});
   1.324 +

mercurial