Wed, 31 Dec 2014 07:22:50 +0100
Correct previous dual key logic pending first delivery installment.
michael@0 | 1 | /* Any copyright is dedicated to the Public Domain. |
michael@0 | 2 | http://creativecommons.org/publicdomain/zero/1.0/ */ |
michael@0 | 3 | |
michael@0 | 4 | _("Test that node reassignment happens correctly using the FxA identity mgr."); |
michael@0 | 5 | // The node-reassignment logic is quite different for FxA than for the legacy |
michael@0 | 6 | // provider. In particular, there's no special request necessary for |
michael@0 | 7 | // reassignment - it comes from the token server - so we need to ensure the |
michael@0 | 8 | // Fxa cluster manager grabs a new token. |
michael@0 | 9 | |
michael@0 | 10 | Cu.import("resource://gre/modules/Log.jsm"); |
michael@0 | 11 | Cu.import("resource://services-common/rest.js"); |
michael@0 | 12 | Cu.import("resource://services-sync/constants.js"); |
michael@0 | 13 | Cu.import("resource://services-sync/service.js"); |
michael@0 | 14 | Cu.import("resource://services-sync/status.js"); |
michael@0 | 15 | Cu.import("resource://services-sync/util.js"); |
michael@0 | 16 | Cu.import("resource://testing-common/services/sync/rotaryengine.js"); |
michael@0 | 17 | Cu.import("resource://services-sync/browserid_identity.js"); |
michael@0 | 18 | Cu.import("resource://testing-common/services/sync/utils.js"); |
michael@0 | 19 | |
michael@0 | 20 | Service.engineManager.clear(); |
michael@0 | 21 | |
michael@0 | 22 | function run_test() { |
michael@0 | 23 | Log.repository.getLogger("Sync.AsyncResource").level = Log.Level.Trace; |
michael@0 | 24 | Log.repository.getLogger("Sync.ErrorHandler").level = Log.Level.Trace; |
michael@0 | 25 | Log.repository.getLogger("Sync.Resource").level = Log.Level.Trace; |
michael@0 | 26 | Log.repository.getLogger("Sync.RESTRequest").level = Log.Level.Trace; |
michael@0 | 27 | Log.repository.getLogger("Sync.Service").level = Log.Level.Trace; |
michael@0 | 28 | Log.repository.getLogger("Sync.SyncScheduler").level = Log.Level.Trace; |
michael@0 | 29 | initTestLogging(); |
michael@0 | 30 | |
michael@0 | 31 | Service.engineManager.register(RotaryEngine); |
michael@0 | 32 | |
michael@0 | 33 | // Setup the FxA identity manager and cluster manager. |
michael@0 | 34 | Status.__authManager = Service.identity = new BrowserIDManager(); |
michael@0 | 35 | Service._clusterManager = Service.identity.createClusterManager(Service); |
michael@0 | 36 | |
michael@0 | 37 | // None of the failures in this file should result in a UI error. |
michael@0 | 38 | function onUIError() { |
michael@0 | 39 | do_throw("Errors should not be presented in the UI."); |
michael@0 | 40 | } |
michael@0 | 41 | Svc.Obs.add("weave:ui:login:error", onUIError); |
michael@0 | 42 | Svc.Obs.add("weave:ui:sync:error", onUIError); |
michael@0 | 43 | |
michael@0 | 44 | run_next_test(); |
michael@0 | 45 | } |
michael@0 | 46 | |
michael@0 | 47 | |
michael@0 | 48 | // API-compatible with SyncServer handler. Bind `handler` to something to use |
michael@0 | 49 | // as a ServerCollection handler. |
michael@0 | 50 | function handleReassign(handler, req, resp) { |
michael@0 | 51 | resp.setStatusLine(req.httpVersion, 401, "Node reassignment"); |
michael@0 | 52 | resp.setHeader("Content-Type", "application/json"); |
michael@0 | 53 | let reassignBody = JSON.stringify({error: "401inator in place"}); |
michael@0 | 54 | resp.bodyOutputStream.write(reassignBody, reassignBody.length); |
michael@0 | 55 | } |
michael@0 | 56 | |
michael@0 | 57 | let numTokenRequests = 0; |
michael@0 | 58 | |
michael@0 | 59 | function prepareServer(cbAfterTokenFetch) { |
michael@0 | 60 | let config = makeIdentityConfig({username: "johndoe"}); |
michael@0 | 61 | let server = new SyncServer(); |
michael@0 | 62 | server.registerUser("johndoe"); |
michael@0 | 63 | server.start(); |
michael@0 | 64 | |
michael@0 | 65 | // Set the token endpoint for the initial token request that's done implicitly |
michael@0 | 66 | // via configureIdentity. |
michael@0 | 67 | config.fxaccount.token.endpoint = server.baseURI + "1.1/johndoe"; |
michael@0 | 68 | // And future token fetches will do magic around numReassigns. |
michael@0 | 69 | let numReassigns = 0; |
michael@0 | 70 | return configureIdentity(config).then(() => { |
michael@0 | 71 | Service.identity._tokenServerClient = { |
michael@0 | 72 | getTokenFromBrowserIDAssertion: function(uri, assertion, cb) { |
michael@0 | 73 | // Build a new URL with trailing zeros for the SYNC_VERSION part - this |
michael@0 | 74 | // will still be seen as equivalent by the test server, but different |
michael@0 | 75 | // by sync itself. |
michael@0 | 76 | numReassigns += 1; |
michael@0 | 77 | let trailingZeros = new Array(numReassigns + 1).join('0'); |
michael@0 | 78 | let token = config.fxaccount.token; |
michael@0 | 79 | token.endpoint = server.baseURI + "1.1" + trailingZeros + "/johndoe"; |
michael@0 | 80 | token.uid = config.username; |
michael@0 | 81 | numTokenRequests += 1; |
michael@0 | 82 | cb(null, token); |
michael@0 | 83 | if (cbAfterTokenFetch) { |
michael@0 | 84 | cbAfterTokenFetch(); |
michael@0 | 85 | } |
michael@0 | 86 | }, |
michael@0 | 87 | }; |
michael@0 | 88 | Service.clusterURL = config.fxaccount.token.endpoint; |
michael@0 | 89 | return server; |
michael@0 | 90 | }); |
michael@0 | 91 | } |
michael@0 | 92 | |
michael@0 | 93 | function getReassigned() { |
michael@0 | 94 | try { |
michael@0 | 95 | return Services.prefs.getBoolPref("services.sync.lastSyncReassigned"); |
michael@0 | 96 | } catch (ex if (ex.result == Cr.NS_ERROR_UNEXPECTED)) { |
michael@0 | 97 | return false; |
michael@0 | 98 | } catch (ex) { |
michael@0 | 99 | do_throw("Got exception retrieving lastSyncReassigned: " + |
michael@0 | 100 | Utils.exceptionStr(ex)); |
michael@0 | 101 | } |
michael@0 | 102 | } |
michael@0 | 103 | |
michael@0 | 104 | /** |
michael@0 | 105 | * Make a test request to `url`, then watch the result of two syncs |
michael@0 | 106 | * to ensure that a node request was made. |
michael@0 | 107 | * Runs `between` between the two. This can be used to undo deliberate failure |
michael@0 | 108 | * setup, detach observers, etc. |
michael@0 | 109 | */ |
michael@0 | 110 | function syncAndExpectNodeReassignment(server, firstNotification, between, |
michael@0 | 111 | secondNotification, url) { |
michael@0 | 112 | _("Starting syncAndExpectNodeReassignment\n"); |
michael@0 | 113 | let deferred = Promise.defer(); |
michael@0 | 114 | function onwards() { |
michael@0 | 115 | let numTokenRequestsBefore; |
michael@0 | 116 | function onFirstSync() { |
michael@0 | 117 | _("First sync completed."); |
michael@0 | 118 | Svc.Obs.remove(firstNotification, onFirstSync); |
michael@0 | 119 | Svc.Obs.add(secondNotification, onSecondSync); |
michael@0 | 120 | |
michael@0 | 121 | do_check_eq(Service.clusterURL, ""); |
michael@0 | 122 | |
michael@0 | 123 | // Track whether we fetched a new token. |
michael@0 | 124 | numTokenRequestsBefore = numTokenRequests; |
michael@0 | 125 | |
michael@0 | 126 | // Allow for tests to clean up error conditions. |
michael@0 | 127 | between(); |
michael@0 | 128 | } |
michael@0 | 129 | function onSecondSync() { |
michael@0 | 130 | _("Second sync completed."); |
michael@0 | 131 | Svc.Obs.remove(secondNotification, onSecondSync); |
michael@0 | 132 | Service.scheduler.clearSyncTriggers(); |
michael@0 | 133 | |
michael@0 | 134 | // Make absolutely sure that any event listeners are done with their work |
michael@0 | 135 | // before we proceed. |
michael@0 | 136 | waitForZeroTimer(function () { |
michael@0 | 137 | _("Second sync nextTick."); |
michael@0 | 138 | do_check_eq(numTokenRequests, numTokenRequestsBefore + 1, "fetched a new token"); |
michael@0 | 139 | Service.startOver(); |
michael@0 | 140 | server.stop(deferred.resolve); |
michael@0 | 141 | }); |
michael@0 | 142 | } |
michael@0 | 143 | |
michael@0 | 144 | Svc.Obs.add(firstNotification, onFirstSync); |
michael@0 | 145 | Service.sync(); |
michael@0 | 146 | } |
michael@0 | 147 | |
michael@0 | 148 | // Make sure that it works! |
michael@0 | 149 | _("Making request to " + url + " which should 401"); |
michael@0 | 150 | let request = new RESTRequest(url); |
michael@0 | 151 | request.get(function () { |
michael@0 | 152 | do_check_eq(request.response.status, 401); |
michael@0 | 153 | Utils.nextTick(onwards); |
michael@0 | 154 | }); |
michael@0 | 155 | yield deferred.promise; |
michael@0 | 156 | } |
michael@0 | 157 | |
michael@0 | 158 | add_task(function test_momentary_401_engine() { |
michael@0 | 159 | _("Test a failure for engine URLs that's resolved by reassignment."); |
michael@0 | 160 | let server = yield prepareServer(); |
michael@0 | 161 | let john = server.user("johndoe"); |
michael@0 | 162 | |
michael@0 | 163 | _("Enabling the Rotary engine."); |
michael@0 | 164 | let engine = Service.engineManager.get("rotary"); |
michael@0 | 165 | engine.enabled = true; |
michael@0 | 166 | |
michael@0 | 167 | // We need the server to be correctly set up prior to experimenting. Do this |
michael@0 | 168 | // through a sync. |
michael@0 | 169 | let global = {syncID: Service.syncID, |
michael@0 | 170 | storageVersion: STORAGE_VERSION, |
michael@0 | 171 | rotary: {version: engine.version, |
michael@0 | 172 | syncID: engine.syncID}} |
michael@0 | 173 | john.createCollection("meta").insert("global", global); |
michael@0 | 174 | |
michael@0 | 175 | _("First sync to prepare server contents."); |
michael@0 | 176 | Service.sync(); |
michael@0 | 177 | |
michael@0 | 178 | _("Setting up Rotary collection to 401."); |
michael@0 | 179 | let rotary = john.createCollection("rotary"); |
michael@0 | 180 | let oldHandler = rotary.collectionHandler; |
michael@0 | 181 | rotary.collectionHandler = handleReassign.bind(this, undefined); |
michael@0 | 182 | |
michael@0 | 183 | // We want to verify that the clusterURL pref has been cleared after a 401 |
michael@0 | 184 | // inside a sync. Flag the Rotary engine to need syncing. |
michael@0 | 185 | john.collection("rotary").timestamp += 1000; |
michael@0 | 186 | |
michael@0 | 187 | function between() { |
michael@0 | 188 | _("Undoing test changes."); |
michael@0 | 189 | rotary.collectionHandler = oldHandler; |
michael@0 | 190 | |
michael@0 | 191 | function onLoginStart() { |
michael@0 | 192 | // lastSyncReassigned shouldn't be cleared until a sync has succeeded. |
michael@0 | 193 | _("Ensuring that lastSyncReassigned is still set at next sync start."); |
michael@0 | 194 | Svc.Obs.remove("weave:service:login:start", onLoginStart); |
michael@0 | 195 | do_check_true(getReassigned()); |
michael@0 | 196 | } |
michael@0 | 197 | |
michael@0 | 198 | _("Adding observer that lastSyncReassigned is still set on login."); |
michael@0 | 199 | Svc.Obs.add("weave:service:login:start", onLoginStart); |
michael@0 | 200 | } |
michael@0 | 201 | |
michael@0 | 202 | yield syncAndExpectNodeReassignment(server, |
michael@0 | 203 | "weave:service:sync:finish", |
michael@0 | 204 | between, |
michael@0 | 205 | "weave:service:sync:finish", |
michael@0 | 206 | Service.storageURL + "rotary"); |
michael@0 | 207 | }); |
michael@0 | 208 | |
michael@0 | 209 | // This test ends up being a failing info fetch *after we're already logged in*. |
michael@0 | 210 | add_task(function test_momentary_401_info_collections_loggedin() { |
michael@0 | 211 | _("Test a failure for info/collections after login that's resolved by reassignment."); |
michael@0 | 212 | let server = yield prepareServer(); |
michael@0 | 213 | |
michael@0 | 214 | _("First sync to prepare server contents."); |
michael@0 | 215 | Service.sync(); |
michael@0 | 216 | |
michael@0 | 217 | _("Arrange for info/collections to return a 401."); |
michael@0 | 218 | let oldHandler = server.toplevelHandlers.info; |
michael@0 | 219 | server.toplevelHandlers.info = handleReassign; |
michael@0 | 220 | |
michael@0 | 221 | function undo() { |
michael@0 | 222 | _("Undoing test changes."); |
michael@0 | 223 | server.toplevelHandlers.info = oldHandler; |
michael@0 | 224 | } |
michael@0 | 225 | |
michael@0 | 226 | do_check_true(Service.isLoggedIn, "already logged in"); |
michael@0 | 227 | |
michael@0 | 228 | yield syncAndExpectNodeReassignment(server, |
michael@0 | 229 | "weave:service:sync:error", |
michael@0 | 230 | undo, |
michael@0 | 231 | "weave:service:sync:finish", |
michael@0 | 232 | Service.infoURL); |
michael@0 | 233 | }); |
michael@0 | 234 | |
michael@0 | 235 | // This test ends up being a failing info fetch *before we're logged in*. |
michael@0 | 236 | // In this case we expect to recover during the login phase - so the first |
michael@0 | 237 | // sync succeeds. |
michael@0 | 238 | add_task(function test_momentary_401_info_collections_loggedout() { |
michael@0 | 239 | _("Test a failure for info/collections before login that's resolved by reassignment."); |
michael@0 | 240 | |
michael@0 | 241 | let oldHandler; |
michael@0 | 242 | let sawTokenFetch = false; |
michael@0 | 243 | |
michael@0 | 244 | function afterTokenFetch() { |
michael@0 | 245 | // After a single token fetch, we undo our evil handleReassign hack, so |
michael@0 | 246 | // the next /info request returns the collection instead of a 401 |
michael@0 | 247 | server.toplevelHandlers.info = oldHandler; |
michael@0 | 248 | sawTokenFetch = true; |
michael@0 | 249 | } |
michael@0 | 250 | |
michael@0 | 251 | let server = yield prepareServer(afterTokenFetch); |
michael@0 | 252 | |
michael@0 | 253 | // Return a 401 for the next /info request - it will be reset immediately |
michael@0 | 254 | // after a new token is fetched. |
michael@0 | 255 | oldHandler = server.toplevelHandlers.info |
michael@0 | 256 | server.toplevelHandlers.info = handleReassign; |
michael@0 | 257 | |
michael@0 | 258 | do_check_false(Service.isLoggedIn, "not already logged in"); |
michael@0 | 259 | |
michael@0 | 260 | Service.sync(); |
michael@0 | 261 | do_check_eq(Status.sync, SYNC_SUCCEEDED, "sync succeeded"); |
michael@0 | 262 | // sync was successful - check we grabbed a new token. |
michael@0 | 263 | do_check_true(sawTokenFetch, "a new token was fetched by this test.") |
michael@0 | 264 | // and we are done. |
michael@0 | 265 | Service.startOver(); |
michael@0 | 266 | let deferred = Promise.defer(); |
michael@0 | 267 | server.stop(deferred.resolve); |
michael@0 | 268 | yield deferred.promise; |
michael@0 | 269 | }); |
michael@0 | 270 | |
michael@0 | 271 | // This test ends up being a failing meta/global fetch *after we're already logged in*. |
michael@0 | 272 | add_task(function test_momentary_401_storage_loggedin() { |
michael@0 | 273 | _("Test a failure for any storage URL after login that's resolved by" + |
michael@0 | 274 | "reassignment."); |
michael@0 | 275 | let server = yield prepareServer(); |
michael@0 | 276 | |
michael@0 | 277 | _("First sync to prepare server contents."); |
michael@0 | 278 | Service.sync(); |
michael@0 | 279 | |
michael@0 | 280 | _("Arrange for meta/global to return a 401."); |
michael@0 | 281 | let oldHandler = server.toplevelHandlers.storage; |
michael@0 | 282 | server.toplevelHandlers.storage = handleReassign; |
michael@0 | 283 | |
michael@0 | 284 | function undo() { |
michael@0 | 285 | _("Undoing test changes."); |
michael@0 | 286 | server.toplevelHandlers.storage = oldHandler; |
michael@0 | 287 | } |
michael@0 | 288 | |
michael@0 | 289 | do_check_true(Service.isLoggedIn, "already logged in"); |
michael@0 | 290 | |
michael@0 | 291 | yield syncAndExpectNodeReassignment(server, |
michael@0 | 292 | "weave:service:sync:error", |
michael@0 | 293 | undo, |
michael@0 | 294 | "weave:service:sync:finish", |
michael@0 | 295 | Service.storageURL + "meta/global"); |
michael@0 | 296 | }); |
michael@0 | 297 | |
michael@0 | 298 | // This test ends up being a failing meta/global fetch *before we've logged in*. |
michael@0 | 299 | add_task(function test_momentary_401_storage_loggedout() { |
michael@0 | 300 | _("Test a failure for any storage URL before login, not just engine parts. " + |
michael@0 | 301 | "Resolved by reassignment."); |
michael@0 | 302 | let server = yield prepareServer(); |
michael@0 | 303 | |
michael@0 | 304 | // Return a 401 for all storage requests. |
michael@0 | 305 | let oldHandler = server.toplevelHandlers.storage; |
michael@0 | 306 | server.toplevelHandlers.storage = handleReassign; |
michael@0 | 307 | |
michael@0 | 308 | function undo() { |
michael@0 | 309 | _("Undoing test changes."); |
michael@0 | 310 | server.toplevelHandlers.storage = oldHandler; |
michael@0 | 311 | } |
michael@0 | 312 | |
michael@0 | 313 | do_check_false(Service.isLoggedIn, "already logged in"); |
michael@0 | 314 | |
michael@0 | 315 | yield syncAndExpectNodeReassignment(server, |
michael@0 | 316 | "weave:service:login:error", |
michael@0 | 317 | undo, |
michael@0 | 318 | "weave:service:sync:finish", |
michael@0 | 319 | Service.storageURL + "meta/global"); |
michael@0 | 320 | }); |
michael@0 | 321 |