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