Wed, 31 Dec 2014 07:22:50 +0100
Correct previous dual key logic pending first delivery installment.
1 /* Any copyright is dedicated to the Public Domain.
2 http://creativecommons.org/publicdomain/zero/1.0/ */
4 Cu.import("resource://services-sync/constants.js");
5 Cu.import("resource://services-sync/engines.js");
6 Cu.import("resource://services-sync/engines/clients.js");
7 Cu.import("resource://services-sync/policies.js");
8 Cu.import("resource://services-sync/record.js");
9 Cu.import("resource://services-sync/service.js");
10 Cu.import("resource://services-sync/status.js");
11 Cu.import("resource://services-sync/util.js");
12 Cu.import("resource://testing-common/services/sync/utils.js");
14 Service.engineManager.clear();
16 function CatapultEngine() {
17 SyncEngine.call(this, "Catapult", Service);
18 }
19 CatapultEngine.prototype = {
20 __proto__: SyncEngine.prototype,
21 exception: null, // tests fill this in
22 _sync: function _sync() {
23 throw this.exception;
24 }
25 };
27 Service.engineManager.register(CatapultEngine);
29 let scheduler = new SyncScheduler(Service);
30 let clientsEngine = Service.clientsEngine;
32 function sync_httpd_setup() {
33 let global = new ServerWBO("global", {
34 syncID: Service.syncID,
35 storageVersion: STORAGE_VERSION,
36 engines: {clients: {version: clientsEngine.version,
37 syncID: clientsEngine.syncID}}
38 });
39 let clientsColl = new ServerCollection({}, true);
41 // Tracking info/collections.
42 let collectionsHelper = track_collections_helper();
43 let upd = collectionsHelper.with_updated_collection;
45 return httpd_setup({
46 "/1.1/johndoe/storage/meta/global": upd("meta", global.handler()),
47 "/1.1/johndoe/info/collections": collectionsHelper.handler,
48 "/1.1/johndoe/storage/crypto/keys":
49 upd("crypto", (new ServerWBO("keys")).handler()),
50 "/1.1/johndoe/storage/clients": upd("clients", clientsColl.handler()),
51 "/user/1.0/johndoe/node/weave": httpd_handler(200, "OK", "null")
52 });
53 }
55 function setUp(server) {
56 let deferred = Promise.defer();
57 configureIdentity({username: "johndoe"}).then(() => {
58 Service.clusterURL = server.baseURI + "/";
60 generateNewKeys(Service.collectionKeys);
61 let serverKeys = Service.collectionKeys.asWBO("crypto", "keys");
62 serverKeys.encrypt(Service.identity.syncKeyBundle);
63 let result = serverKeys.upload(Service.resource(Service.cryptoKeysURL)).success;
64 deferred.resolve(result);
65 });
66 return deferred.promise;
67 }
69 function cleanUpAndGo(server) {
70 let deferred = Promise.defer();
71 Utils.nextTick(function () {
72 Service.startOver();
73 if (server) {
74 server.stop(deferred.resolve);
75 } else {
76 deferred.resolve();
77 }
78 });
79 return deferred.promise;
80 }
82 function run_test() {
83 initTestLogging("Trace");
85 Log.repository.getLogger("Sync.Service").level = Log.Level.Trace;
86 Log.repository.getLogger("Sync.scheduler").level = Log.Level.Trace;
88 // The scheduler checks Weave.fxaEnabled to determine whether to use
89 // FxA defaults or legacy defaults. As .fxaEnabled checks the username, we
90 // set a username here then reset the default to ensure they are used.
91 ensureLegacyIdentityManager();
92 setBasicCredentials("johndoe");
93 scheduler.setDefaults();
95 run_next_test();
96 }
98 add_test(function test_prefAttributes() {
99 _("Test various attributes corresponding to preferences.");
101 const INTERVAL = 42 * 60 * 1000; // 42 minutes
102 const THRESHOLD = 3142;
103 const SCORE = 2718;
104 const TIMESTAMP1 = 1275493471649;
106 _("The 'nextSync' attribute stores a millisecond timestamp rounded down to the nearest second.");
107 do_check_eq(scheduler.nextSync, 0);
108 scheduler.nextSync = TIMESTAMP1;
109 do_check_eq(scheduler.nextSync, Math.floor(TIMESTAMP1 / 1000) * 1000);
111 _("'syncInterval' defaults to singleDeviceInterval.");
112 do_check_eq(Svc.Prefs.get('syncInterval'), undefined);
113 do_check_eq(scheduler.syncInterval, scheduler.singleDeviceInterval);
115 _("'syncInterval' corresponds to a preference setting.");
116 scheduler.syncInterval = INTERVAL;
117 do_check_eq(scheduler.syncInterval, INTERVAL);
118 do_check_eq(Svc.Prefs.get('syncInterval'), INTERVAL);
120 _("'syncThreshold' corresponds to preference, defaults to SINGLE_USER_THRESHOLD");
121 do_check_eq(Svc.Prefs.get('syncThreshold'), undefined);
122 do_check_eq(scheduler.syncThreshold, SINGLE_USER_THRESHOLD);
123 scheduler.syncThreshold = THRESHOLD;
124 do_check_eq(scheduler.syncThreshold, THRESHOLD);
126 _("'globalScore' corresponds to preference, defaults to zero.");
127 do_check_eq(Svc.Prefs.get('globalScore'), 0);
128 do_check_eq(scheduler.globalScore, 0);
129 scheduler.globalScore = SCORE;
130 do_check_eq(scheduler.globalScore, SCORE);
131 do_check_eq(Svc.Prefs.get('globalScore'), SCORE);
133 _("Intervals correspond to default preferences.");
134 do_check_eq(scheduler.singleDeviceInterval,
135 Svc.Prefs.get("scheduler.sync11.singleDeviceInterval") * 1000);
136 do_check_eq(scheduler.idleInterval,
137 Svc.Prefs.get("scheduler.idleInterval") * 1000);
138 do_check_eq(scheduler.activeInterval,
139 Svc.Prefs.get("scheduler.activeInterval") * 1000);
140 do_check_eq(scheduler.immediateInterval,
141 Svc.Prefs.get("scheduler.immediateInterval") * 1000);
143 _("Custom values for prefs will take effect after a restart.");
144 Svc.Prefs.set("scheduler.sync11.singleDeviceInterval", 42);
145 Svc.Prefs.set("scheduler.idleInterval", 23);
146 Svc.Prefs.set("scheduler.activeInterval", 18);
147 Svc.Prefs.set("scheduler.immediateInterval", 31415);
148 scheduler.setDefaults();
149 do_check_eq(scheduler.idleInterval, 23000);
150 do_check_eq(scheduler.singleDeviceInterval, 42000);
151 do_check_eq(scheduler.activeInterval, 18000);
152 do_check_eq(scheduler.immediateInterval, 31415000);
154 Svc.Prefs.resetBranch("");
155 scheduler.setDefaults();
156 run_next_test();
157 });
159 add_identity_test(this, function test_updateClientMode() {
160 _("Test updateClientMode adjusts scheduling attributes based on # of clients appropriately");
161 do_check_eq(scheduler.syncThreshold, SINGLE_USER_THRESHOLD);
162 do_check_eq(scheduler.syncInterval, scheduler.singleDeviceInterval);
163 do_check_false(scheduler.numClients > 1);
164 do_check_false(scheduler.idle);
166 // Trigger a change in interval & threshold by adding a client.
167 clientsEngine._store.create({id: "foo", cleartext: "bar"});
168 scheduler.updateClientMode();
170 do_check_eq(scheduler.syncThreshold, MULTI_DEVICE_THRESHOLD);
171 do_check_eq(scheduler.syncInterval, scheduler.activeInterval);
172 do_check_true(scheduler.numClients > 1);
173 do_check_false(scheduler.idle);
175 // Resets the number of clients to 0.
176 clientsEngine.resetClient();
177 scheduler.updateClientMode();
179 // Goes back to single user if # clients is 1.
180 do_check_eq(scheduler.numClients, 1);
181 do_check_eq(scheduler.syncThreshold, SINGLE_USER_THRESHOLD);
182 do_check_eq(scheduler.syncInterval, scheduler.singleDeviceInterval);
183 do_check_false(scheduler.numClients > 1);
184 do_check_false(scheduler.idle);
186 yield cleanUpAndGo();
187 });
189 add_identity_test(this, function test_masterpassword_locked_retry_interval() {
190 _("Test Status.login = MASTER_PASSWORD_LOCKED results in reschedule at MASTER_PASSWORD interval");
191 let loginFailed = false;
192 Svc.Obs.add("weave:service:login:error", function onLoginError() {
193 Svc.Obs.remove("weave:service:login:error", onLoginError);
194 loginFailed = true;
195 });
197 let rescheduleInterval = false;
199 let oldScheduleAtInterval = SyncScheduler.prototype.scheduleAtInterval;
200 SyncScheduler.prototype.scheduleAtInterval = function (interval) {
201 rescheduleInterval = true;
202 do_check_eq(interval, MASTER_PASSWORD_LOCKED_RETRY_INTERVAL);
203 };
205 let oldVerifyLogin = Service.verifyLogin;
206 Service.verifyLogin = function () {
207 Status.login = MASTER_PASSWORD_LOCKED;
208 return false;
209 };
211 let server = sync_httpd_setup();
212 yield setUp(server);
214 Service.sync();
216 do_check_true(loginFailed);
217 do_check_eq(Status.login, MASTER_PASSWORD_LOCKED);
218 do_check_true(rescheduleInterval);
220 Service.verifyLogin = oldVerifyLogin;
221 SyncScheduler.prototype.scheduleAtInterval = oldScheduleAtInterval;
223 yield cleanUpAndGo(server);
224 });
226 add_identity_test(this, function test_calculateBackoff() {
227 do_check_eq(Status.backoffInterval, 0);
229 // Test no interval larger than the maximum backoff is used if
230 // Status.backoffInterval is smaller.
231 Status.backoffInterval = 5;
232 let backoffInterval = Utils.calculateBackoff(50, MAXIMUM_BACKOFF_INTERVAL,
233 Status.backoffInterval);
235 do_check_eq(backoffInterval, MAXIMUM_BACKOFF_INTERVAL);
237 // Test Status.backoffInterval is used if it is
238 // larger than MAXIMUM_BACKOFF_INTERVAL.
239 Status.backoffInterval = MAXIMUM_BACKOFF_INTERVAL + 10;
240 backoffInterval = Utils.calculateBackoff(50, MAXIMUM_BACKOFF_INTERVAL,
241 Status.backoffInterval);
243 do_check_eq(backoffInterval, MAXIMUM_BACKOFF_INTERVAL + 10);
245 yield cleanUpAndGo();
246 });
248 add_identity_test(this, function test_scheduleNextSync_nowOrPast() {
249 let deferred = Promise.defer();
250 Svc.Obs.add("weave:service:sync:finish", function onSyncFinish() {
251 Svc.Obs.remove("weave:service:sync:finish", onSyncFinish);
252 cleanUpAndGo(server).then(deferred.resolve);
253 });
255 let server = sync_httpd_setup();
256 yield setUp(server);
258 // We're late for a sync...
259 scheduler.scheduleNextSync(-1);
260 yield deferred.promise;
261 });
263 add_identity_test(this, function test_scheduleNextSync_future_noBackoff() {
264 _("scheduleNextSync() uses the current syncInterval if no interval is provided.");
265 // Test backoffInterval is 0 as expected.
266 do_check_eq(Status.backoffInterval, 0);
268 _("Test setting sync interval when nextSync == 0");
269 scheduler.nextSync = 0;
270 scheduler.scheduleNextSync();
272 // nextSync - Date.now() might be smaller than expectedInterval
273 // since some time has passed since we called scheduleNextSync().
274 do_check_true(scheduler.nextSync - Date.now()
275 <= scheduler.syncInterval);
276 do_check_eq(scheduler.syncTimer.delay, scheduler.syncInterval);
278 _("Test setting sync interval when nextSync != 0");
279 scheduler.nextSync = Date.now() + scheduler.singleDeviceInterval;
280 scheduler.scheduleNextSync();
282 // nextSync - Date.now() might be smaller than expectedInterval
283 // since some time has passed since we called scheduleNextSync().
284 do_check_true(scheduler.nextSync - Date.now()
285 <= scheduler.syncInterval);
286 do_check_true(scheduler.syncTimer.delay <= scheduler.syncInterval);
288 _("Scheduling requests for intervals larger than the current one will be ignored.");
289 // Request a sync at a longer interval. The sync that's already scheduled
290 // for sooner takes precedence.
291 let nextSync = scheduler.nextSync;
292 let timerDelay = scheduler.syncTimer.delay;
293 let requestedInterval = scheduler.syncInterval * 10;
294 scheduler.scheduleNextSync(requestedInterval);
295 do_check_eq(scheduler.nextSync, nextSync);
296 do_check_eq(scheduler.syncTimer.delay, timerDelay);
298 // We can schedule anything we want if there isn't a sync scheduled.
299 scheduler.nextSync = 0;
300 scheduler.scheduleNextSync(requestedInterval);
301 do_check_true(scheduler.nextSync <= Date.now() + requestedInterval);
302 do_check_eq(scheduler.syncTimer.delay, requestedInterval);
304 // Request a sync at the smallest possible interval (0 triggers now).
305 scheduler.scheduleNextSync(1);
306 do_check_true(scheduler.nextSync <= Date.now() + 1);
307 do_check_eq(scheduler.syncTimer.delay, 1);
309 yield cleanUpAndGo();
310 });
312 add_identity_test(this, function test_scheduleNextSync_future_backoff() {
313 _("scheduleNextSync() will honour backoff in all scheduling requests.");
314 // Let's take a backoff interval that's bigger than the default sync interval.
315 const BACKOFF = 7337;
316 Status.backoffInterval = scheduler.syncInterval + BACKOFF;
318 _("Test setting sync interval when nextSync == 0");
319 scheduler.nextSync = 0;
320 scheduler.scheduleNextSync();
322 // nextSync - Date.now() might be smaller than expectedInterval
323 // since some time has passed since we called scheduleNextSync().
324 do_check_true(scheduler.nextSync - Date.now()
325 <= Status.backoffInterval);
326 do_check_eq(scheduler.syncTimer.delay, Status.backoffInterval);
328 _("Test setting sync interval when nextSync != 0");
329 scheduler.nextSync = Date.now() + scheduler.singleDeviceInterval;
330 scheduler.scheduleNextSync();
332 // nextSync - Date.now() might be smaller than expectedInterval
333 // since some time has passed since we called scheduleNextSync().
334 do_check_true(scheduler.nextSync - Date.now()
335 <= Status.backoffInterval);
336 do_check_true(scheduler.syncTimer.delay <= Status.backoffInterval);
338 // Request a sync at a longer interval. The sync that's already scheduled
339 // for sooner takes precedence.
340 let nextSync = scheduler.nextSync;
341 let timerDelay = scheduler.syncTimer.delay;
342 let requestedInterval = scheduler.syncInterval * 10;
343 do_check_true(requestedInterval > Status.backoffInterval);
344 scheduler.scheduleNextSync(requestedInterval);
345 do_check_eq(scheduler.nextSync, nextSync);
346 do_check_eq(scheduler.syncTimer.delay, timerDelay);
348 // We can schedule anything we want if there isn't a sync scheduled.
349 scheduler.nextSync = 0;
350 scheduler.scheduleNextSync(requestedInterval);
351 do_check_true(scheduler.nextSync <= Date.now() + requestedInterval);
352 do_check_eq(scheduler.syncTimer.delay, requestedInterval);
354 // Request a sync at the smallest possible interval (0 triggers now).
355 scheduler.scheduleNextSync(1);
356 do_check_true(scheduler.nextSync <= Date.now() + Status.backoffInterval);
357 do_check_eq(scheduler.syncTimer.delay, Status.backoffInterval);
359 yield cleanUpAndGo();
360 });
362 add_identity_test(this, function test_handleSyncError() {
363 let server = sync_httpd_setup();
364 yield setUp(server);
366 // Force sync to fail.
367 Svc.Prefs.set("firstSync", "notReady");
369 _("Ensure expected initial environment.");
370 do_check_eq(scheduler._syncErrors, 0);
371 do_check_false(Status.enforceBackoff);
372 do_check_eq(scheduler.syncInterval, scheduler.singleDeviceInterval);
373 do_check_eq(Status.backoffInterval, 0);
375 // Trigger sync with an error several times & observe
376 // functionality of handleSyncError()
377 _("Test first error calls scheduleNextSync on default interval");
378 Service.sync();
379 do_check_true(scheduler.nextSync <= Date.now() + scheduler.singleDeviceInterval);
380 do_check_eq(scheduler.syncTimer.delay, scheduler.singleDeviceInterval);
381 do_check_eq(scheduler._syncErrors, 1);
382 do_check_false(Status.enforceBackoff);
383 scheduler.syncTimer.clear();
385 _("Test second error still calls scheduleNextSync on default interval");
386 Service.sync();
387 do_check_true(scheduler.nextSync <= Date.now() + scheduler.singleDeviceInterval);
388 do_check_eq(scheduler.syncTimer.delay, scheduler.singleDeviceInterval);
389 do_check_eq(scheduler._syncErrors, 2);
390 do_check_false(Status.enforceBackoff);
391 scheduler.syncTimer.clear();
393 _("Test third error sets Status.enforceBackoff and calls scheduleAtInterval");
394 Service.sync();
395 let maxInterval = scheduler._syncErrors * (2 * MINIMUM_BACKOFF_INTERVAL);
396 do_check_eq(Status.backoffInterval, 0);
397 do_check_true(scheduler.nextSync <= (Date.now() + maxInterval));
398 do_check_true(scheduler.syncTimer.delay <= maxInterval);
399 do_check_eq(scheduler._syncErrors, 3);
400 do_check_true(Status.enforceBackoff);
402 // Status.enforceBackoff is false but there are still errors.
403 Status.resetBackoff();
404 do_check_false(Status.enforceBackoff);
405 do_check_eq(scheduler._syncErrors, 3);
406 scheduler.syncTimer.clear();
408 _("Test fourth error still calls scheduleAtInterval even if enforceBackoff was reset");
409 Service.sync();
410 maxInterval = scheduler._syncErrors * (2 * MINIMUM_BACKOFF_INTERVAL);
411 do_check_true(scheduler.nextSync <= Date.now() + maxInterval);
412 do_check_true(scheduler.syncTimer.delay <= maxInterval);
413 do_check_eq(scheduler._syncErrors, 4);
414 do_check_true(Status.enforceBackoff);
415 scheduler.syncTimer.clear();
417 _("Arrange for a successful sync to reset the scheduler error count");
418 let deferred = Promise.defer();
419 Svc.Obs.add("weave:service:sync:finish", function onSyncFinish() {
420 Svc.Obs.remove("weave:service:sync:finish", onSyncFinish);
421 cleanUpAndGo(server).then(deferred.resolve);
422 });
423 Svc.Prefs.set("firstSync", "wipeRemote");
424 scheduler.scheduleNextSync(-1);
425 yield deferred.promise;
426 });
428 add_identity_test(this, function test_client_sync_finish_updateClientMode() {
429 let server = sync_httpd_setup();
430 yield setUp(server);
432 // Confirm defaults.
433 do_check_eq(scheduler.syncThreshold, SINGLE_USER_THRESHOLD);
434 do_check_eq(scheduler.syncInterval, scheduler.singleDeviceInterval);
435 do_check_false(scheduler.idle);
437 // Trigger a change in interval & threshold by adding a client.
438 clientsEngine._store.create({id: "foo", cleartext: "bar"});
439 do_check_false(scheduler.numClients > 1);
440 scheduler.updateClientMode();
441 Service.sync();
443 do_check_eq(scheduler.syncThreshold, MULTI_DEVICE_THRESHOLD);
444 do_check_eq(scheduler.syncInterval, scheduler.activeInterval);
445 do_check_true(scheduler.numClients > 1);
446 do_check_false(scheduler.idle);
448 // Resets the number of clients to 0.
449 clientsEngine.resetClient();
450 Service.sync();
452 // Goes back to single user if # clients is 1.
453 do_check_eq(scheduler.numClients, 1);
454 do_check_eq(scheduler.syncThreshold, SINGLE_USER_THRESHOLD);
455 do_check_eq(scheduler.syncInterval, scheduler.singleDeviceInterval);
456 do_check_false(scheduler.numClients > 1);
457 do_check_false(scheduler.idle);
459 yield cleanUpAndGo(server);
460 });
462 add_identity_test(this, function test_autoconnect_nextSync_past() {
463 let deferred = Promise.defer();
464 // nextSync will be 0 by default, so it's way in the past.
466 Svc.Obs.add("weave:service:sync:finish", function onSyncFinish() {
467 Svc.Obs.remove("weave:service:sync:finish", onSyncFinish);
468 cleanUpAndGo(server).then(deferred.resolve);
469 });
471 let server = sync_httpd_setup();
472 yield setUp(server);
474 scheduler.delayedAutoConnect(0);
475 yield deferred.promise;
476 });
478 add_identity_test(this, function test_autoconnect_nextSync_future() {
479 let deferred = Promise.defer();
480 let previousSync = Date.now() + scheduler.syncInterval / 2;
481 scheduler.nextSync = previousSync;
482 // nextSync rounds to the nearest second.
483 let expectedSync = scheduler.nextSync;
484 let expectedInterval = expectedSync - Date.now() - 1000;
486 // Ensure we don't actually try to sync (or log in for that matter).
487 function onLoginStart() {
488 do_throw("Should not get here!");
489 }
490 Svc.Obs.add("weave:service:login:start", onLoginStart);
492 waitForZeroTimer(function () {
493 do_check_eq(scheduler.nextSync, expectedSync);
494 do_check_true(scheduler.syncTimer.delay >= expectedInterval);
496 Svc.Obs.remove("weave:service:login:start", onLoginStart);
497 cleanUpAndGo().then(deferred.resolve);
498 });
500 yield configureIdentity({username: "johndoe"});
501 scheduler.delayedAutoConnect(0);
502 yield deferred.promise;
503 });
505 // XXX - this test can't be run with the browserid identity as it relies
506 // on the syncKey getter behaving in a certain way...
507 add_task(function test_autoconnect_mp_locked() {
508 let server = sync_httpd_setup();
509 yield setUp(server);
511 // Pretend user did not unlock master password.
512 let origLocked = Utils.mpLocked;
513 Utils.mpLocked = function() true;
515 let origGetter = Service.identity.__lookupGetter__("syncKey");
516 let origSetter = Service.identity.__lookupSetter__("syncKey");
517 delete Service.identity.syncKey;
518 Service.identity.__defineGetter__("syncKey", function() {
519 _("Faking Master Password entry cancelation.");
520 throw "User canceled Master Password entry";
521 });
523 let deferred = Promise.defer();
524 // A locked master password will still trigger a sync, but then we'll hit
525 // MASTER_PASSWORD_LOCKED and hence MASTER_PASSWORD_LOCKED_RETRY_INTERVAL.
526 Svc.Obs.add("weave:service:login:error", function onLoginError() {
527 Svc.Obs.remove("weave:service:login:error", onLoginError);
528 Utils.nextTick(function aLittleBitAfterLoginError() {
529 do_check_eq(Status.login, MASTER_PASSWORD_LOCKED);
531 Utils.mpLocked = origLocked;
532 delete Service.identity.syncKey;
533 Service.identity.__defineGetter__("syncKey", origGetter);
534 Service.identity.__defineSetter__("syncKey", origSetter);
536 cleanUpAndGo(server).then(deferred.resolve);
537 });
538 });
540 scheduler.delayedAutoConnect(0);
541 yield deferred.promise;
542 });
544 add_identity_test(this, function test_no_autoconnect_during_wizard() {
545 let server = sync_httpd_setup();
546 yield setUp(server);
548 // Simulate the Sync setup wizard.
549 Svc.Prefs.set("firstSync", "notReady");
551 // Ensure we don't actually try to sync (or log in for that matter).
552 function onLoginStart() {
553 do_throw("Should not get here!");
554 }
555 Svc.Obs.add("weave:service:login:start", onLoginStart);
557 let deferred = Promise.defer();
558 waitForZeroTimer(function () {
559 Svc.Obs.remove("weave:service:login:start", onLoginStart);
560 cleanUpAndGo(server).then(deferred.resolve);
561 });
563 scheduler.delayedAutoConnect(0);
564 yield deferred.promise;
565 });
567 add_identity_test(this, function test_no_autoconnect_status_not_ok() {
568 let server = sync_httpd_setup();
570 // Ensure we don't actually try to sync (or log in for that matter).
571 function onLoginStart() {
572 do_throw("Should not get here!");
573 }
574 Svc.Obs.add("weave:service:login:start", onLoginStart);
576 let deferred = Promise.defer();
577 waitForZeroTimer(function () {
578 Svc.Obs.remove("weave:service:login:start", onLoginStart);
580 do_check_eq(Status.service, CLIENT_NOT_CONFIGURED);
581 do_check_eq(Status.login, LOGIN_FAILED_NO_USERNAME);
583 cleanUpAndGo(server).then(deferred.resolve);
584 });
586 scheduler.delayedAutoConnect(0);
587 yield deferred.promise;
588 });
590 add_identity_test(this, function test_autoconnectDelay_pref() {
591 let deferred = Promise.defer();
592 Svc.Obs.add("weave:service:sync:finish", function onSyncFinish() {
593 Svc.Obs.remove("weave:service:sync:finish", onSyncFinish);
594 cleanUpAndGo(server).then(deferred.resolve);
595 });
597 Svc.Prefs.set("autoconnectDelay", 1);
599 let server = sync_httpd_setup();
600 yield setUp(server);
602 Svc.Obs.notify("weave:service:ready");
604 // autoconnectDelay pref is multiplied by 1000.
605 do_check_eq(scheduler._autoTimer.delay, 1000);
606 do_check_eq(Status.service, STATUS_OK);
607 yield deferred.promise;
608 });
610 add_identity_test(this, function test_idle_adjustSyncInterval() {
611 // Confirm defaults.
612 do_check_eq(scheduler.idle, false);
614 // Single device: nothing changes.
615 scheduler.observe(null, "idle", Svc.Prefs.get("scheduler.idleTime"));
616 do_check_eq(scheduler.idle, true);
617 do_check_eq(scheduler.syncInterval, scheduler.singleDeviceInterval);
619 // Multiple devices: switch to idle interval.
620 scheduler.idle = false;
621 clientsEngine._store.create({id: "foo", cleartext: "bar"});
622 scheduler.updateClientMode();
623 scheduler.observe(null, "idle", Svc.Prefs.get("scheduler.idleTime"));
624 do_check_eq(scheduler.idle, true);
625 do_check_eq(scheduler.syncInterval, scheduler.idleInterval);
627 yield cleanUpAndGo();
628 });
630 add_identity_test(this, function test_back_triggersSync() {
631 // Confirm defaults.
632 do_check_false(scheduler.idle);
633 do_check_eq(Status.backoffInterval, 0);
635 // Set up: Define 2 clients and put the system in idle.
636 scheduler.numClients = 2;
637 scheduler.observe(null, "idle", Svc.Prefs.get("scheduler.idleTime"));
638 do_check_true(scheduler.idle);
640 let deferred = Promise.defer();
641 // We don't actually expect the sync (or the login, for that matter) to
642 // succeed. We just want to ensure that it was attempted.
643 Svc.Obs.add("weave:service:login:error", function onLoginError() {
644 Svc.Obs.remove("weave:service:login:error", onLoginError);
645 cleanUpAndGo().then(deferred.resolve);
646 });
648 // Send an 'active' event to trigger sync soonish.
649 scheduler.observe(null, "active", Svc.Prefs.get("scheduler.idleTime"));
650 yield deferred.promise;
651 });
653 add_identity_test(this, function test_active_triggersSync_observesBackoff() {
654 // Confirm defaults.
655 do_check_false(scheduler.idle);
657 // Set up: Set backoff, define 2 clients and put the system in idle.
658 const BACKOFF = 7337;
659 Status.backoffInterval = scheduler.idleInterval + BACKOFF;
660 scheduler.numClients = 2;
661 scheduler.observe(null, "idle", Svc.Prefs.get("scheduler.idleTime"));
662 do_check_eq(scheduler.idle, true);
664 function onLoginStart() {
665 do_throw("Shouldn't have kicked off a sync!");
666 }
667 Svc.Obs.add("weave:service:login:start", onLoginStart);
669 let deferred = Promise.defer();
670 timer = Utils.namedTimer(function () {
671 Svc.Obs.remove("weave:service:login:start", onLoginStart);
673 do_check_true(scheduler.nextSync <= Date.now() + Status.backoffInterval);
674 do_check_eq(scheduler.syncTimer.delay, Status.backoffInterval);
676 cleanUpAndGo().then(deferred.resolve);
677 }, IDLE_OBSERVER_BACK_DELAY * 1.5, {}, "timer");
679 // Send an 'active' event to try to trigger sync soonish.
680 scheduler.observe(null, "active", Svc.Prefs.get("scheduler.idleTime"));
681 yield deferred.promise;
682 });
684 add_identity_test(this, function test_back_debouncing() {
685 _("Ensure spurious back-then-idle events, as observed on OS X, don't trigger a sync.");
687 // Confirm defaults.
688 do_check_eq(scheduler.idle, false);
690 // Set up: Define 2 clients and put the system in idle.
691 scheduler.numClients = 2;
692 scheduler.observe(null, "idle", Svc.Prefs.get("scheduler.idleTime"));
693 do_check_eq(scheduler.idle, true);
695 function onLoginStart() {
696 do_throw("Shouldn't have kicked off a sync!");
697 }
698 Svc.Obs.add("weave:service:login:start", onLoginStart);
700 // Create spurious back-then-idle events as observed on OS X:
701 scheduler.observe(null, "active", Svc.Prefs.get("scheduler.idleTime"));
702 scheduler.observe(null, "idle", Svc.Prefs.get("scheduler.idleTime"));
704 let deferred = Promise.defer();
705 timer = Utils.namedTimer(function () {
706 Svc.Obs.remove("weave:service:login:start", onLoginStart);
707 cleanUpAndGo().then(deferred.resolve);
708 }, IDLE_OBSERVER_BACK_DELAY * 1.5, {}, "timer");
709 yield deferred.promise;
710 });
712 add_identity_test(this, function test_no_sync_node() {
713 // Test when Status.sync == NO_SYNC_NODE_FOUND
714 // it is not overwritten on sync:finish
715 let server = sync_httpd_setup();
716 yield setUp(server);
718 Service.serverURL = server.baseURI + "/";
720 Service.sync();
721 do_check_eq(Status.sync, NO_SYNC_NODE_FOUND);
722 do_check_eq(scheduler.syncTimer.delay, NO_SYNC_NODE_INTERVAL);
724 yield cleanUpAndGo(server);
725 });
727 add_identity_test(this, function test_sync_failed_partial_500s() {
728 _("Test a 5xx status calls handleSyncError.");
729 scheduler._syncErrors = MAX_ERROR_COUNT_BEFORE_BACKOFF;
730 let server = sync_httpd_setup();
732 let engine = Service.engineManager.get("catapult");
733 engine.enabled = true;
734 engine.exception = {status: 500};
736 do_check_eq(Status.sync, SYNC_SUCCEEDED);
738 do_check_true(yield setUp(server));
740 Service.sync();
742 do_check_eq(Status.service, SYNC_FAILED_PARTIAL);
744 let maxInterval = scheduler._syncErrors * (2 * MINIMUM_BACKOFF_INTERVAL);
745 do_check_eq(Status.backoffInterval, 0);
746 do_check_true(Status.enforceBackoff);
747 do_check_eq(scheduler._syncErrors, 4);
748 do_check_true(scheduler.nextSync <= (Date.now() + maxInterval));
749 do_check_true(scheduler.syncTimer.delay <= maxInterval);
751 yield cleanUpAndGo(server);
752 });
754 add_identity_test(this, function test_sync_failed_partial_400s() {
755 _("Test a non-5xx status doesn't call handleSyncError.");
756 scheduler._syncErrors = MAX_ERROR_COUNT_BEFORE_BACKOFF;
757 let server = sync_httpd_setup();
759 let engine = Service.engineManager.get("catapult");
760 engine.enabled = true;
761 engine.exception = {status: 400};
763 // Have multiple devices for an active interval.
764 clientsEngine._store.create({id: "foo", cleartext: "bar"});
766 do_check_eq(Status.sync, SYNC_SUCCEEDED);
768 do_check_true(yield setUp(server));
770 Service.sync();
772 do_check_eq(Status.service, SYNC_FAILED_PARTIAL);
773 do_check_eq(scheduler.syncInterval, scheduler.activeInterval);
775 do_check_eq(Status.backoffInterval, 0);
776 do_check_false(Status.enforceBackoff);
777 do_check_eq(scheduler._syncErrors, 0);
778 do_check_true(scheduler.nextSync <= (Date.now() + scheduler.activeInterval));
779 do_check_true(scheduler.syncTimer.delay <= scheduler.activeInterval);
781 yield cleanUpAndGo(server);
782 });
784 add_identity_test(this, function test_sync_X_Weave_Backoff() {
785 let server = sync_httpd_setup();
786 yield setUp(server);
788 // Use an odd value on purpose so that it doesn't happen to coincide with one
789 // of the sync intervals.
790 const BACKOFF = 7337;
792 // Extend info/collections so that we can put it into server maintenance mode.
793 const INFO_COLLECTIONS = "/1.1/johndoe/info/collections";
794 let infoColl = server._handler._overridePaths[INFO_COLLECTIONS];
795 let serverBackoff = false;
796 function infoCollWithBackoff(request, response) {
797 if (serverBackoff) {
798 response.setHeader("X-Weave-Backoff", "" + BACKOFF);
799 }
800 infoColl(request, response);
801 }
802 server.registerPathHandler(INFO_COLLECTIONS, infoCollWithBackoff);
804 // Pretend we have two clients so that the regular sync interval is
805 // sufficiently low.
806 clientsEngine._store.create({id: "foo", cleartext: "bar"});
807 let rec = clientsEngine._store.createRecord("foo", "clients");
808 rec.encrypt(Service.collectionKeys.keyForCollection("clients"));
809 rec.upload(Service.resource(clientsEngine.engineURL + rec.id));
811 // Sync once to log in and get everything set up. Let's verify our initial
812 // values.
813 Service.sync();
814 do_check_eq(Status.backoffInterval, 0);
815 do_check_eq(Status.minimumNextSync, 0);
816 do_check_eq(scheduler.syncInterval, scheduler.activeInterval);
817 do_check_true(scheduler.nextSync <=
818 Date.now() + scheduler.syncInterval);
819 // Sanity check that we picked the right value for BACKOFF:
820 do_check_true(scheduler.syncInterval < BACKOFF * 1000);
822 // Turn on server maintenance and sync again.
823 serverBackoff = true;
824 Service.sync();
826 do_check_true(Status.backoffInterval >= BACKOFF * 1000);
827 // Allowing 1 second worth of of leeway between when Status.minimumNextSync
828 // was set and when this line gets executed.
829 let minimumExpectedDelay = (BACKOFF - 1) * 1000;
830 do_check_true(Status.minimumNextSync >= Date.now() + minimumExpectedDelay);
832 // Verify that the next sync is actually going to wait that long.
833 do_check_true(scheduler.nextSync >= Date.now() + minimumExpectedDelay);
834 do_check_true(scheduler.syncTimer.delay >= minimumExpectedDelay);
836 yield cleanUpAndGo(server);
837 });
839 add_identity_test(this, function test_sync_503_Retry_After() {
840 let server = sync_httpd_setup();
841 yield setUp(server);
843 // Use an odd value on purpose so that it doesn't happen to coincide with one
844 // of the sync intervals.
845 const BACKOFF = 7337;
847 // Extend info/collections so that we can put it into server maintenance mode.
848 const INFO_COLLECTIONS = "/1.1/johndoe/info/collections";
849 let infoColl = server._handler._overridePaths[INFO_COLLECTIONS];
850 let serverMaintenance = false;
851 function infoCollWithMaintenance(request, response) {
852 if (!serverMaintenance) {
853 infoColl(request, response);
854 return;
855 }
856 response.setHeader("Retry-After", "" + BACKOFF);
857 response.setStatusLine(request.httpVersion, 503, "Service Unavailable");
858 }
859 server.registerPathHandler(INFO_COLLECTIONS, infoCollWithMaintenance);
861 // Pretend we have two clients so that the regular sync interval is
862 // sufficiently low.
863 clientsEngine._store.create({id: "foo", cleartext: "bar"});
864 let rec = clientsEngine._store.createRecord("foo", "clients");
865 rec.encrypt(Service.collectionKeys.keyForCollection("clients"));
866 rec.upload(Service.resource(clientsEngine.engineURL + rec.id));
868 // Sync once to log in and get everything set up. Let's verify our initial
869 // values.
870 Service.sync();
871 do_check_false(Status.enforceBackoff);
872 do_check_eq(Status.backoffInterval, 0);
873 do_check_eq(Status.minimumNextSync, 0);
874 do_check_eq(scheduler.syncInterval, scheduler.activeInterval);
875 do_check_true(scheduler.nextSync <=
876 Date.now() + scheduler.syncInterval);
877 // Sanity check that we picked the right value for BACKOFF:
878 do_check_true(scheduler.syncInterval < BACKOFF * 1000);
880 // Turn on server maintenance and sync again.
881 serverMaintenance = true;
882 Service.sync();
884 do_check_true(Status.enforceBackoff);
885 do_check_true(Status.backoffInterval >= BACKOFF * 1000);
886 // Allowing 1 second worth of of leeway between when Status.minimumNextSync
887 // was set and when this line gets executed.
888 let minimumExpectedDelay = (BACKOFF - 1) * 1000;
889 do_check_true(Status.minimumNextSync >= Date.now() + minimumExpectedDelay);
891 // Verify that the next sync is actually going to wait that long.
892 do_check_true(scheduler.nextSync >= Date.now() + minimumExpectedDelay);
893 do_check_true(scheduler.syncTimer.delay >= minimumExpectedDelay);
895 yield cleanUpAndGo(server);
896 });
898 add_identity_test(this, function test_loginError_recoverable_reschedules() {
899 _("Verify that a recoverable login error schedules a new sync.");
900 yield configureIdentity({username: "johndoe"});
901 Service.serverURL = "http://localhost:1234/";
902 Service.clusterURL = Service.serverURL;
903 Service.persistLogin();
904 Status.resetSync(); // reset Status.login
906 let deferred = Promise.defer();
907 Svc.Obs.add("weave:service:login:error", function onLoginError() {
908 Svc.Obs.remove("weave:service:login:error", onLoginError);
909 Utils.nextTick(function aLittleBitAfterLoginError() {
910 do_check_eq(Status.login, LOGIN_FAILED_NETWORK_ERROR);
912 let expectedNextSync = Date.now() + scheduler.syncInterval;
913 do_check_true(scheduler.nextSync > Date.now());
914 do_check_true(scheduler.nextSync <= expectedNextSync);
915 do_check_true(scheduler.syncTimer.delay > 0);
916 do_check_true(scheduler.syncTimer.delay <= scheduler.syncInterval);
918 Svc.Obs.remove("weave:service:sync:start", onSyncStart);
919 cleanUpAndGo().then(deferred.resolve);
920 });
921 });
923 // Let's set it up so that a sync is overdue, both in terms of previously
924 // scheduled syncs and the global score. We still do not expect an immediate
925 // sync because we just tried (duh).
926 scheduler.nextSync = Date.now() - 100000;
927 scheduler.globalScore = SINGLE_USER_THRESHOLD + 1;
928 function onSyncStart() {
929 do_throw("Shouldn't have started a sync!");
930 }
931 Svc.Obs.add("weave:service:sync:start", onSyncStart);
933 // Sanity check.
934 do_check_eq(scheduler.syncTimer, null);
935 do_check_eq(Status.checkSetup(), STATUS_OK);
936 do_check_eq(Status.login, LOGIN_SUCCEEDED);
938 scheduler.scheduleNextSync(0);
939 yield deferred.promise;
940 });
942 add_identity_test(this, function test_loginError_fatal_clearsTriggers() {
943 _("Verify that a fatal login error clears sync triggers.");
944 yield configureIdentity({username: "johndoe"});
946 let server = httpd_setup({
947 "/1.1/johndoe/info/collections": httpd_handler(401, "Unauthorized")
948 });
950 Service.serverURL = server.baseURI + "/";
951 Service.clusterURL = Service.serverURL;
952 Service.persistLogin();
953 Status.resetSync(); // reset Status.login
955 let deferred = Promise.defer();
956 Svc.Obs.add("weave:service:login:error", function onLoginError() {
957 Svc.Obs.remove("weave:service:login:error", onLoginError);
958 Utils.nextTick(function aLittleBitAfterLoginError() {
959 do_check_eq(Status.login, LOGIN_FAILED_LOGIN_REJECTED);
961 do_check_eq(scheduler.nextSync, 0);
962 do_check_eq(scheduler.syncTimer, null);
964 cleanUpAndGo(server).then(deferred.resolve);
965 });
966 });
968 // Sanity check.
969 do_check_eq(scheduler.nextSync, 0);
970 do_check_eq(scheduler.syncTimer, null);
971 do_check_eq(Status.checkSetup(), STATUS_OK);
972 do_check_eq(Status.login, LOGIN_SUCCEEDED);
974 scheduler.scheduleNextSync(0);
975 yield deferred.promise;
976 });
978 add_identity_test(this, function test_proper_interval_on_only_failing() {
979 _("Ensure proper behavior when only failed records are applied.");
981 // If an engine reports that no records succeeded, we shouldn't decrease the
982 // sync interval.
983 do_check_false(scheduler.hasIncomingItems);
984 const INTERVAL = 10000000;
985 scheduler.syncInterval = INTERVAL;
987 Svc.Obs.notify("weave:service:sync:applied", {
988 applied: 2,
989 succeeded: 0,
990 failed: 2,
991 newFailed: 2,
992 reconciled: 0
993 });
995 let deferred = Promise.defer();
996 Utils.nextTick(function() {
997 scheduler.adjustSyncInterval();
998 do_check_false(scheduler.hasIncomingItems);
999 do_check_eq(scheduler.syncInterval, scheduler.singleDeviceInterval);
1001 deferred.resolve();
1002 });
1003 yield deferred.promise;
1004 });