test_syncscheduler.js (39715B)
1 /* Any copyright is dedicated to the Public Domain. 2 http://creativecommons.org/publicdomain/zero/1.0/ */ 3 4 const { FxAccounts } = ChromeUtils.importESModule( 5 "resource://gre/modules/FxAccounts.sys.mjs" 6 ); 7 const { SyncAuthManager } = ChromeUtils.importESModule( 8 "resource://services-sync/sync_auth.sys.mjs" 9 ); 10 const { SyncScheduler } = ChromeUtils.importESModule( 11 "resource://services-sync/policies.sys.mjs" 12 ); 13 const { Service } = ChromeUtils.importESModule( 14 "resource://services-sync/service.sys.mjs" 15 ); 16 const { Status } = ChromeUtils.importESModule( 17 "resource://services-sync/status.sys.mjs" 18 ); 19 20 function CatapultEngine() { 21 SyncEngine.call(this, "Catapult", Service); 22 } 23 CatapultEngine.prototype = { 24 exception: null, // tests fill this in 25 async _sync() { 26 throw this.exception; 27 }, 28 }; 29 Object.setPrototypeOf(CatapultEngine.prototype, SyncEngine.prototype); 30 31 var scheduler = new SyncScheduler(Service); 32 let clientsEngine; 33 34 async function sync_httpd_setup() { 35 let clientsSyncID = await clientsEngine.resetLocalSyncID(); 36 let global = new ServerWBO("global", { 37 syncID: Service.syncID, 38 storageVersion: STORAGE_VERSION, 39 engines: { 40 clients: { version: clientsEngine.version, syncID: clientsSyncID }, 41 }, 42 }); 43 let clientsColl = new ServerCollection({}, true); 44 45 // Tracking info/collections. 46 let collectionsHelper = track_collections_helper(); 47 let upd = collectionsHelper.with_updated_collection; 48 49 return httpd_setup({ 50 "/1.1/johndoe@mozilla.com/storage/meta/global": upd( 51 "meta", 52 global.handler() 53 ), 54 "/1.1/johndoe@mozilla.com/info/collections": collectionsHelper.handler, 55 "/1.1/johndoe@mozilla.com/storage/crypto/keys": upd( 56 "crypto", 57 new ServerWBO("keys").handler() 58 ), 59 "/1.1/johndoe@mozilla.com/storage/clients": upd( 60 "clients", 61 clientsColl.handler() 62 ), 63 }); 64 } 65 66 async function setUp(server) { 67 await configureIdentity({ username: "johndoe@mozilla.com" }, server); 68 69 await generateNewKeys(Service.collectionKeys); 70 let serverKeys = Service.collectionKeys.asWBO("crypto", "keys"); 71 await serverKeys.encrypt(Service.identity.syncKeyBundle); 72 let result = ( 73 await serverKeys.upload(Service.resource(Service.cryptoKeysURL)) 74 ).success; 75 return result; 76 } 77 78 async function cleanUpAndGo(server) { 79 await Async.promiseYield(); 80 await clientsEngine._store.wipe(); 81 await Service.startOver(); 82 // Re-enable logging, which we just disabled. 83 syncTestLogging(); 84 if (server) { 85 await promiseStopServer(server); 86 } 87 } 88 89 add_task(async function setup() { 90 await Service.promiseInitialized; 91 clientsEngine = Service.clientsEngine; 92 // Don't remove stale clients when syncing. This is a test-only workaround 93 // that lets us add clients directly to the store, without losing them on 94 // the next sync. 95 clientsEngine._removeRemoteClient = async () => {}; 96 await Service.engineManager.clear(); 97 98 validate_all_future_pings(); 99 100 scheduler.setDefaults(); 101 102 await Service.engineManager.register(CatapultEngine); 103 }); 104 105 add_test(function test_prefAttributes() { 106 _("Test various attributes corresponding to preferences."); 107 108 const INTERVAL = 42 * 60 * 1000; // 42 minutes 109 const THRESHOLD = 3142; 110 const SCORE = 2718; 111 const TIMESTAMP1 = 1275493471649; 112 113 _( 114 "The 'nextSync' attribute stores a millisecond timestamp rounded down to the nearest second." 115 ); 116 Assert.equal(scheduler.nextSync, 0); 117 scheduler.nextSync = TIMESTAMP1; 118 Assert.equal(scheduler.nextSync, Math.floor(TIMESTAMP1 / 1000) * 1000); 119 120 _("'syncInterval' defaults to singleDeviceInterval."); 121 Assert.equal( 122 Svc.PrefBranch.getPrefType("syncInterval"), 123 Ci.nsIPrefBranch.PREF_INVALID 124 ); 125 Assert.equal(scheduler.syncInterval, scheduler.singleDeviceInterval); 126 127 _("'syncInterval' corresponds to a preference setting."); 128 scheduler.syncInterval = INTERVAL; 129 Assert.equal(scheduler.syncInterval, INTERVAL); 130 Assert.equal(Svc.PrefBranch.getIntPref("syncInterval"), INTERVAL); 131 132 _( 133 "'syncThreshold' corresponds to preference, defaults to SINGLE_USER_THRESHOLD" 134 ); 135 Assert.equal( 136 Svc.PrefBranch.getPrefType("syncThreshold"), 137 Ci.nsIPrefBranch.PREF_INVALID 138 ); 139 Assert.equal(scheduler.syncThreshold, SINGLE_USER_THRESHOLD); 140 scheduler.syncThreshold = THRESHOLD; 141 Assert.equal(scheduler.syncThreshold, THRESHOLD); 142 143 _("'globalScore' corresponds to preference, defaults to zero."); 144 Assert.equal(Svc.PrefBranch.getIntPref("globalScore"), 0); 145 Assert.equal(scheduler.globalScore, 0); 146 scheduler.globalScore = SCORE; 147 Assert.equal(scheduler.globalScore, SCORE); 148 Assert.equal(Svc.PrefBranch.getIntPref("globalScore"), SCORE); 149 150 _("Intervals correspond to default preferences."); 151 Assert.equal( 152 scheduler.singleDeviceInterval, 153 Svc.PrefBranch.getIntPref("scheduler.fxa.singleDeviceInterval") * 1000 154 ); 155 Assert.equal( 156 scheduler.idleInterval, 157 Svc.PrefBranch.getIntPref("scheduler.idleInterval") * 1000 158 ); 159 Assert.equal( 160 scheduler.activeInterval, 161 Svc.PrefBranch.getIntPref("scheduler.activeInterval") * 1000 162 ); 163 Assert.equal( 164 scheduler.immediateInterval, 165 Svc.PrefBranch.getIntPref("scheduler.immediateInterval") * 1000 166 ); 167 168 _("Custom values for prefs will take effect after a restart."); 169 Svc.PrefBranch.setIntPref("scheduler.fxa.singleDeviceInterval", 420); 170 Svc.PrefBranch.setIntPref("scheduler.idleInterval", 230); 171 Svc.PrefBranch.setIntPref("scheduler.activeInterval", 180); 172 Svc.PrefBranch.setIntPref("scheduler.immediateInterval", 31415); 173 scheduler.setDefaults(); 174 Assert.equal(scheduler.idleInterval, 230000); 175 Assert.equal(scheduler.singleDeviceInterval, 420000); 176 Assert.equal(scheduler.activeInterval, 180000); 177 Assert.equal(scheduler.immediateInterval, 31415000); 178 179 _("Custom values for interval prefs can't be less than 60 seconds."); 180 Svc.PrefBranch.setIntPref("scheduler.fxa.singleDeviceInterval", 42); 181 Svc.PrefBranch.setIntPref("scheduler.idleInterval", 50); 182 Svc.PrefBranch.setIntPref("scheduler.activeInterval", 50); 183 Svc.PrefBranch.setIntPref("scheduler.immediateInterval", 10); 184 scheduler.setDefaults(); 185 Assert.equal(scheduler.idleInterval, 60000); 186 Assert.equal(scheduler.singleDeviceInterval, 60000); 187 Assert.equal(scheduler.activeInterval, 60000); 188 Assert.equal(scheduler.immediateInterval, 60000); 189 190 for (const pref of Svc.PrefBranch.getChildList("")) { 191 Svc.PrefBranch.clearUserPref(pref); 192 } 193 scheduler.setDefaults(); 194 run_next_test(); 195 }); 196 197 add_task(async function test_sync_skipped_low_score_no_resync() { 198 enableValidationPrefs(); 199 let server = await sync_httpd_setup(); 200 201 function SkipEngine() { 202 SyncEngine.call(this, "Skip", Service); 203 this.syncs = 0; 204 } 205 206 SkipEngine.prototype = { 207 _sync() { 208 do_throw("Should have been skipped"); 209 }, 210 shouldSkipSync() { 211 return true; 212 }, 213 }; 214 Object.setPrototypeOf(SkipEngine.prototype, SyncEngine.prototype); 215 await Service.engineManager.register(SkipEngine); 216 217 let engine = Service.engineManager.get("skip"); 218 engine.enabled = true; 219 engine._tracker._score = 30; 220 221 Assert.equal(Status.sync, SYNC_SUCCEEDED); 222 223 Assert.ok(await setUp(server)); 224 225 let resyncDoneObserver = promiseOneObserver("weave:service:resyncs-finished"); 226 227 let synced = false; 228 function onSyncStarted() { 229 Assert.ok(!synced, "Only should sync once"); 230 synced = true; 231 } 232 233 await Service.sync(); 234 235 Assert.equal(Status.sync, SYNC_SUCCEEDED); 236 237 Svc.Obs.add("weave:service:sync:start", onSyncStarted); 238 await resyncDoneObserver; 239 240 Svc.Obs.remove("weave:service:sync:start", onSyncStarted); 241 engine._tracker._store = 0; 242 await cleanUpAndGo(server); 243 }); 244 245 add_task(async function test_updateClientMode() { 246 _( 247 "Test updateClientMode adjusts scheduling attributes based on # of clients appropriately" 248 ); 249 Assert.equal(scheduler.syncThreshold, SINGLE_USER_THRESHOLD); 250 Assert.equal(scheduler.syncInterval, scheduler.singleDeviceInterval); 251 Assert.equal(false, scheduler.numClients > 1); 252 Assert.ok(!scheduler.idle); 253 254 // Trigger a change in interval & threshold by noting there are multiple clients. 255 Svc.PrefBranch.setIntPref("clients.devices.desktop", 1); 256 Svc.PrefBranch.setIntPref("clients.devices.mobile", 1); 257 scheduler.updateClientMode(); 258 259 Assert.equal(scheduler.syncThreshold, MULTI_DEVICE_THRESHOLD); 260 Assert.equal(scheduler.syncInterval, scheduler.activeInterval); 261 Assert.greater(scheduler.numClients, 1); 262 Assert.ok(!scheduler.idle); 263 264 // Resets the number of clients to 0. 265 await clientsEngine.resetClient(); 266 Svc.PrefBranch.clearUserPref("clients.devices.mobile"); 267 scheduler.updateClientMode(); 268 269 // Goes back to single user if # clients is 1. 270 Assert.equal(scheduler.numClients, 1); 271 Assert.equal(scheduler.syncThreshold, SINGLE_USER_THRESHOLD); 272 Assert.equal(scheduler.syncInterval, scheduler.singleDeviceInterval); 273 Assert.equal(false, scheduler.numClients > 1); 274 Assert.ok(!scheduler.idle); 275 276 await cleanUpAndGo(); 277 }); 278 279 add_task(async function test_masterpassword_locked_retry_interval() { 280 enableValidationPrefs(); 281 282 _( 283 "Test Status.login = MASTER_PASSWORD_LOCKED results in reschedule at MASTER_PASSWORD interval" 284 ); 285 let loginFailed = false; 286 Svc.Obs.add("weave:service:login:error", function onLoginError() { 287 Svc.Obs.remove("weave:service:login:error", onLoginError); 288 loginFailed = true; 289 }); 290 291 let rescheduleInterval = false; 292 293 let oldScheduleAtInterval = SyncScheduler.prototype.scheduleAtInterval; 294 SyncScheduler.prototype.scheduleAtInterval = function (interval) { 295 rescheduleInterval = true; 296 Assert.equal(interval, MASTER_PASSWORD_LOCKED_RETRY_INTERVAL); 297 }; 298 299 let oldVerifyLogin = Service.verifyLogin; 300 Service.verifyLogin = async function () { 301 Status.login = MASTER_PASSWORD_LOCKED; 302 return false; 303 }; 304 305 let server = await sync_httpd_setup(); 306 await setUp(server); 307 308 await Service.sync(); 309 310 Assert.ok(loginFailed); 311 Assert.equal(Status.login, MASTER_PASSWORD_LOCKED); 312 Assert.ok(rescheduleInterval); 313 314 Service.verifyLogin = oldVerifyLogin; 315 SyncScheduler.prototype.scheduleAtInterval = oldScheduleAtInterval; 316 317 await cleanUpAndGo(server); 318 }); 319 320 add_task(async function test_calculateBackoff() { 321 Assert.equal(Status.backoffInterval, 0); 322 323 // Test no interval larger than the maximum backoff is used if 324 // Status.backoffInterval is smaller. 325 Status.backoffInterval = 5; 326 let backoffInterval = Utils.calculateBackoff( 327 50, 328 MAXIMUM_BACKOFF_INTERVAL, 329 Status.backoffInterval 330 ); 331 332 Assert.equal(backoffInterval, MAXIMUM_BACKOFF_INTERVAL); 333 334 // Test Status.backoffInterval is used if it is 335 // larger than MAXIMUM_BACKOFF_INTERVAL. 336 Status.backoffInterval = MAXIMUM_BACKOFF_INTERVAL + 10; 337 backoffInterval = Utils.calculateBackoff( 338 50, 339 MAXIMUM_BACKOFF_INTERVAL, 340 Status.backoffInterval 341 ); 342 343 Assert.equal(backoffInterval, MAXIMUM_BACKOFF_INTERVAL + 10); 344 345 await cleanUpAndGo(); 346 }); 347 348 add_task(async function test_scheduleNextSync_nowOrPast() { 349 enableValidationPrefs(); 350 351 let promiseObserved = promiseOneObserver("weave:service:sync:finish"); 352 353 let server = await sync_httpd_setup(); 354 await setUp(server); 355 356 // We're late for a sync... 357 scheduler.scheduleNextSync(-1); 358 await promiseObserved; 359 await cleanUpAndGo(server); 360 }); 361 362 add_task(async function test_scheduleNextSync_future_noBackoff() { 363 enableValidationPrefs(); 364 365 _( 366 "scheduleNextSync() uses the current syncInterval if no interval is provided." 367 ); 368 // Test backoffInterval is 0 as expected. 369 Assert.equal(Status.backoffInterval, 0); 370 371 _("Test setting sync interval when nextSync == 0"); 372 scheduler.nextSync = 0; 373 scheduler.scheduleNextSync(); 374 375 // nextSync - Date.now() might be smaller than expectedInterval 376 // since some time has passed since we called scheduleNextSync(). 377 Assert.lessOrEqual(scheduler.nextSync - Date.now(), scheduler.syncInterval); 378 Assert.equal(scheduler.syncTimer.delay, scheduler.syncInterval); 379 380 _("Test setting sync interval when nextSync != 0"); 381 scheduler.nextSync = Date.now() + scheduler.singleDeviceInterval; 382 scheduler.scheduleNextSync(); 383 384 // nextSync - Date.now() might be smaller than expectedInterval 385 // since some time has passed since we called scheduleNextSync(). 386 Assert.lessOrEqual(scheduler.nextSync - Date.now(), scheduler.syncInterval); 387 Assert.lessOrEqual(scheduler.syncTimer.delay, scheduler.syncInterval); 388 389 _( 390 "Scheduling requests for intervals larger than the current one will be ignored." 391 ); 392 // Request a sync at a longer interval. The sync that's already scheduled 393 // for sooner takes precedence. 394 let nextSync = scheduler.nextSync; 395 let timerDelay = scheduler.syncTimer.delay; 396 let requestedInterval = scheduler.syncInterval * 10; 397 scheduler.scheduleNextSync(requestedInterval); 398 Assert.equal(scheduler.nextSync, nextSync); 399 Assert.equal(scheduler.syncTimer.delay, timerDelay); 400 401 // We can schedule anything we want if there isn't a sync scheduled. 402 scheduler.nextSync = 0; 403 scheduler.scheduleNextSync(requestedInterval); 404 Assert.lessOrEqual(scheduler.nextSync, Date.now() + requestedInterval); 405 Assert.equal(scheduler.syncTimer.delay, requestedInterval); 406 407 // Request a sync at the smallest possible interval (0 triggers now). 408 scheduler.scheduleNextSync(1); 409 Assert.lessOrEqual(scheduler.nextSync, Date.now() + 1); 410 Assert.equal(scheduler.syncTimer.delay, 1); 411 412 await cleanUpAndGo(); 413 }); 414 415 add_task(async function test_scheduleNextSync_future_backoff() { 416 enableValidationPrefs(); 417 418 _("scheduleNextSync() will honour backoff in all scheduling requests."); 419 // Let's take a backoff interval that's bigger than the default sync interval. 420 const BACKOFF = 7337; 421 Status.backoffInterval = scheduler.syncInterval + BACKOFF; 422 423 _("Test setting sync interval when nextSync == 0"); 424 scheduler.nextSync = 0; 425 scheduler.scheduleNextSync(); 426 427 // nextSync - Date.now() might be smaller than expectedInterval 428 // since some time has passed since we called scheduleNextSync(). 429 Assert.lessOrEqual(scheduler.nextSync - Date.now(), Status.backoffInterval); 430 Assert.equal(scheduler.syncTimer.delay, Status.backoffInterval); 431 432 _("Test setting sync interval when nextSync != 0"); 433 scheduler.nextSync = Date.now() + scheduler.singleDeviceInterval; 434 scheduler.scheduleNextSync(); 435 436 // nextSync - Date.now() might be smaller than expectedInterval 437 // since some time has passed since we called scheduleNextSync(). 438 Assert.lessOrEqual(scheduler.nextSync - Date.now(), Status.backoffInterval); 439 Assert.lessOrEqual(scheduler.syncTimer.delay, Status.backoffInterval); 440 441 // Request a sync at a longer interval. The sync that's already scheduled 442 // for sooner takes precedence. 443 let nextSync = scheduler.nextSync; 444 let timerDelay = scheduler.syncTimer.delay; 445 let requestedInterval = scheduler.syncInterval * 10; 446 Assert.greater(requestedInterval, Status.backoffInterval); 447 scheduler.scheduleNextSync(requestedInterval); 448 Assert.equal(scheduler.nextSync, nextSync); 449 Assert.equal(scheduler.syncTimer.delay, timerDelay); 450 451 // We can schedule anything we want if there isn't a sync scheduled. 452 scheduler.nextSync = 0; 453 scheduler.scheduleNextSync(requestedInterval); 454 Assert.lessOrEqual(scheduler.nextSync, Date.now() + requestedInterval); 455 Assert.equal(scheduler.syncTimer.delay, requestedInterval); 456 457 // Request a sync at the smallest possible interval (0 triggers now). 458 scheduler.scheduleNextSync(1); 459 Assert.lessOrEqual(scheduler.nextSync, Date.now() + Status.backoffInterval); 460 Assert.equal(scheduler.syncTimer.delay, Status.backoffInterval); 461 462 await cleanUpAndGo(); 463 }); 464 465 add_task(async function test_handleSyncError() { 466 enableValidationPrefs(); 467 468 let server = await sync_httpd_setup(); 469 await setUp(server); 470 471 // Force sync to fail. 472 Svc.PrefBranch.setStringPref("firstSync", "notReady"); 473 474 _("Ensure expected initial environment."); 475 Assert.equal(scheduler._syncErrors, 0); 476 Assert.ok(!Status.enforceBackoff); 477 Assert.equal(scheduler.syncInterval, scheduler.singleDeviceInterval); 478 Assert.equal(Status.backoffInterval, 0); 479 480 // Trigger sync with an error several times & observe 481 // functionality of handleSyncError() 482 _("Test first error calls scheduleNextSync on default interval"); 483 await Service.sync(); 484 Assert.lessOrEqual( 485 scheduler.nextSync, 486 Date.now() + scheduler.singleDeviceInterval 487 ); 488 Assert.equal(scheduler.syncTimer.delay, scheduler.singleDeviceInterval); 489 Assert.equal(scheduler._syncErrors, 1); 490 Assert.ok(!Status.enforceBackoff); 491 scheduler.syncTimer.clear(); 492 493 _("Test second error still calls scheduleNextSync on default interval"); 494 await Service.sync(); 495 Assert.lessOrEqual( 496 scheduler.nextSync, 497 Date.now() + scheduler.singleDeviceInterval 498 ); 499 Assert.equal(scheduler.syncTimer.delay, scheduler.singleDeviceInterval); 500 Assert.equal(scheduler._syncErrors, 2); 501 Assert.ok(!Status.enforceBackoff); 502 scheduler.syncTimer.clear(); 503 504 _("Test third error sets Status.enforceBackoff and calls scheduleAtInterval"); 505 await Service.sync(); 506 let maxInterval = scheduler._syncErrors * (2 * MINIMUM_BACKOFF_INTERVAL); 507 Assert.equal(Status.backoffInterval, 0); 508 Assert.lessOrEqual(scheduler.nextSync, Date.now() + maxInterval); 509 Assert.lessOrEqual(scheduler.syncTimer.delay, maxInterval); 510 Assert.equal(scheduler._syncErrors, 3); 511 Assert.ok(Status.enforceBackoff); 512 513 // Status.enforceBackoff is false but there are still errors. 514 Status.resetBackoff(); 515 Assert.ok(!Status.enforceBackoff); 516 Assert.equal(scheduler._syncErrors, 3); 517 scheduler.syncTimer.clear(); 518 519 _( 520 "Test fourth error still calls scheduleAtInterval even if enforceBackoff was reset" 521 ); 522 await Service.sync(); 523 maxInterval = scheduler._syncErrors * (2 * MINIMUM_BACKOFF_INTERVAL); 524 Assert.lessOrEqual(scheduler.nextSync, Date.now() + maxInterval); 525 Assert.lessOrEqual(scheduler.syncTimer.delay, maxInterval); 526 Assert.equal(scheduler._syncErrors, 4); 527 Assert.ok(Status.enforceBackoff); 528 scheduler.syncTimer.clear(); 529 530 _("Arrange for a successful sync to reset the scheduler error count"); 531 let promiseObserved = promiseOneObserver("weave:service:sync:finish"); 532 Svc.PrefBranch.setStringPref("firstSync", "wipeRemote"); 533 scheduler.scheduleNextSync(-1); 534 await promiseObserved; 535 await cleanUpAndGo(server); 536 }); 537 538 add_task(async function test_client_sync_finish_updateClientMode() { 539 enableValidationPrefs(); 540 541 let server = await sync_httpd_setup(); 542 await setUp(server); 543 544 // Confirm defaults. 545 Assert.equal(scheduler.syncThreshold, SINGLE_USER_THRESHOLD); 546 Assert.equal(scheduler.syncInterval, scheduler.singleDeviceInterval); 547 Assert.ok(!scheduler.idle); 548 549 // Trigger a change in interval & threshold by adding a client. 550 await clientsEngine._store.create({ 551 id: "foo", 552 cleartext: { os: "mobile", version: "0.01", type: "desktop" }, 553 }); 554 Assert.equal(false, scheduler.numClients > 1); 555 scheduler.updateClientMode(); 556 await Service.sync(); 557 558 Assert.equal(scheduler.syncThreshold, MULTI_DEVICE_THRESHOLD); 559 Assert.equal(scheduler.syncInterval, scheduler.activeInterval); 560 Assert.greater(scheduler.numClients, 1); 561 Assert.ok(!scheduler.idle); 562 563 // Resets the number of clients to 0. 564 await clientsEngine.resetClient(); 565 // Also re-init the server, or we suck our "foo" client back down. 566 await setUp(server); 567 568 await Service.sync(); 569 570 // Goes back to single user if # clients is 1. 571 Assert.equal(scheduler.numClients, 1); 572 Assert.equal(scheduler.syncThreshold, SINGLE_USER_THRESHOLD); 573 Assert.equal(scheduler.syncInterval, scheduler.singleDeviceInterval); 574 Assert.equal(false, scheduler.numClients > 1); 575 Assert.ok(!scheduler.idle); 576 577 await cleanUpAndGo(server); 578 }); 579 580 add_task(async function test_autoconnect_nextSync_past() { 581 enableValidationPrefs(); 582 583 let promiseObserved = promiseOneObserver("weave:service:sync:finish"); 584 // nextSync will be 0 by default, so it's way in the past. 585 586 let server = await sync_httpd_setup(); 587 await setUp(server); 588 589 scheduler.autoConnect(); 590 await promiseObserved; 591 await cleanUpAndGo(server); 592 }); 593 594 add_task(async function test_autoconnect_nextSync_future() { 595 enableValidationPrefs(); 596 597 let previousSync = Date.now() + scheduler.syncInterval / 2; 598 scheduler.nextSync = previousSync; 599 // nextSync rounds to the nearest second. 600 let expectedSync = scheduler.nextSync; 601 let expectedInterval = expectedSync - Date.now() - 1000; 602 603 // Ensure we don't actually try to sync (or log in for that matter). 604 function onLoginStart() { 605 do_throw("Should not get here!"); 606 } 607 Svc.Obs.add("weave:service:login:start", onLoginStart); 608 609 await configureIdentity({ username: "johndoe@mozilla.com" }); 610 scheduler.autoConnect(); 611 await promiseZeroTimer(); 612 613 Assert.equal(scheduler.nextSync, expectedSync); 614 Assert.greaterOrEqual(scheduler.syncTimer.delay, expectedInterval); 615 616 Svc.Obs.remove("weave:service:login:start", onLoginStart); 617 await cleanUpAndGo(); 618 }); 619 620 add_task(async function test_autoconnect_mp_locked() { 621 let server = await sync_httpd_setup(); 622 await setUp(server); 623 624 // Pretend user did not unlock master password. 625 let origLocked = Utils.mpLocked; 626 Utils.mpLocked = () => true; 627 628 let origEnsureMPUnlocked = Utils.ensureMPUnlocked; 629 Utils.ensureMPUnlocked = () => { 630 _("Faking Master Password entry cancelation."); 631 return false; 632 }; 633 let origFxA = Service.identity._fxaService; 634 Service.identity._fxaService = new FxAccounts({ 635 currentAccountState: { 636 getUserAccountData(...args) { 637 return origFxA._internal.currentAccountState.getUserAccountData( 638 ...args 639 ); 640 }, 641 }, 642 keys: { 643 canGetKeyForScope() { 644 return false; 645 }, 646 }, 647 }); 648 // A locked master password will still trigger a sync, but then we'll hit 649 // MASTER_PASSWORD_LOCKED and hence MASTER_PASSWORD_LOCKED_RETRY_INTERVAL. 650 let promiseObserved = promiseOneObserver("weave:service:login:error"); 651 652 scheduler.autoConnect(); 653 await promiseObserved; 654 655 await Async.promiseYield(); 656 657 Assert.equal(Status.login, MASTER_PASSWORD_LOCKED); 658 659 Utils.mpLocked = origLocked; 660 Utils.ensureMPUnlocked = origEnsureMPUnlocked; 661 Service.identity._fxaService = origFxA; 662 663 await cleanUpAndGo(server); 664 }); 665 666 add_task(async function test_no_autoconnect_during_wizard() { 667 let server = await sync_httpd_setup(); 668 await setUp(server); 669 670 // Simulate the Sync setup wizard. 671 Svc.PrefBranch.setStringPref("firstSync", "notReady"); 672 673 // Ensure we don't actually try to sync (or log in for that matter). 674 function onLoginStart() { 675 do_throw("Should not get here!"); 676 } 677 Svc.Obs.add("weave:service:login:start", onLoginStart); 678 679 scheduler.autoConnect(0); 680 await promiseZeroTimer(); 681 Svc.Obs.remove("weave:service:login:start", onLoginStart); 682 await cleanUpAndGo(server); 683 }); 684 685 add_task(async function test_no_autoconnect_status_not_ok() { 686 let server = await sync_httpd_setup(); 687 Status.__authManager = Service.identity = new SyncAuthManager(); 688 689 // Ensure we don't actually try to sync (or log in for that matter). 690 function onLoginStart() { 691 do_throw("Should not get here!"); 692 } 693 Svc.Obs.add("weave:service:login:start", onLoginStart); 694 695 scheduler.autoConnect(); 696 await promiseZeroTimer(); 697 Svc.Obs.remove("weave:service:login:start", onLoginStart); 698 699 Assert.equal(Status.service, CLIENT_NOT_CONFIGURED); 700 Assert.equal(Status.login, LOGIN_FAILED_NO_USERNAME); 701 702 await cleanUpAndGo(server); 703 }); 704 705 add_task(async function test_idle_adjustSyncInterval() { 706 // Confirm defaults. 707 Assert.equal(scheduler.idle, false); 708 709 // Single device: nothing changes. 710 scheduler.observe( 711 null, 712 "idle", 713 Svc.PrefBranch.getIntPref("scheduler.idleTime") 714 ); 715 Assert.equal(scheduler.idle, true); 716 Assert.equal(scheduler.syncInterval, scheduler.singleDeviceInterval); 717 718 // Multiple devices: switch to idle interval. 719 scheduler.idle = false; 720 Svc.PrefBranch.setIntPref("clients.devices.desktop", 1); 721 Svc.PrefBranch.setIntPref("clients.devices.mobile", 1); 722 scheduler.updateClientMode(); 723 scheduler.observe( 724 null, 725 "idle", 726 Svc.PrefBranch.getIntPref("scheduler.idleTime") 727 ); 728 Assert.equal(scheduler.idle, true); 729 Assert.equal(scheduler.syncInterval, scheduler.idleInterval); 730 731 await cleanUpAndGo(); 732 }); 733 734 add_task(async function test_back_triggersSync() { 735 // Confirm defaults. 736 Assert.ok(!scheduler.idle); 737 Assert.equal(Status.backoffInterval, 0); 738 739 // Set up: Define 2 clients and put the system in idle. 740 Svc.PrefBranch.setIntPref("clients.devices.desktop", 1); 741 Svc.PrefBranch.setIntPref("clients.devices.mobile", 1); 742 scheduler.observe( 743 null, 744 "idle", 745 Svc.PrefBranch.getIntPref("scheduler.idleTime") 746 ); 747 Assert.ok(scheduler.idle); 748 749 // We don't actually expect the sync (or the login, for that matter) to 750 // succeed. We just want to ensure that it was attempted. 751 let promiseObserved = promiseOneObserver("weave:service:login:error"); 752 753 // Send an 'active' event to trigger sync soonish. 754 scheduler.observe( 755 null, 756 "active", 757 Svc.PrefBranch.getIntPref("scheduler.idleTime") 758 ); 759 await promiseObserved; 760 await cleanUpAndGo(); 761 }); 762 763 add_task(async function test_active_triggersSync_observesBackoff() { 764 // Confirm defaults. 765 Assert.ok(!scheduler.idle); 766 767 // Set up: Set backoff, define 2 clients and put the system in idle. 768 const BACKOFF = 7337; 769 Status.backoffInterval = scheduler.idleInterval + BACKOFF; 770 Svc.PrefBranch.setIntPref("clients.devices.desktop", 1); 771 Svc.PrefBranch.setIntPref("clients.devices.mobile", 1); 772 scheduler.observe( 773 null, 774 "idle", 775 Svc.PrefBranch.getIntPref("scheduler.idleTime") 776 ); 777 Assert.equal(scheduler.idle, true); 778 779 function onLoginStart() { 780 do_throw("Shouldn't have kicked off a sync!"); 781 } 782 Svc.Obs.add("weave:service:login:start", onLoginStart); 783 784 let promiseTimer = promiseNamedTimer( 785 IDLE_OBSERVER_BACK_DELAY * 1.5, 786 {}, 787 "timer" 788 ); 789 790 // Send an 'active' event to try to trigger sync soonish. 791 scheduler.observe( 792 null, 793 "active", 794 Svc.PrefBranch.getIntPref("scheduler.idleTime") 795 ); 796 await promiseTimer; 797 Svc.Obs.remove("weave:service:login:start", onLoginStart); 798 799 Assert.lessOrEqual(scheduler.nextSync, Date.now() + Status.backoffInterval); 800 Assert.equal(scheduler.syncTimer.delay, Status.backoffInterval); 801 802 await cleanUpAndGo(); 803 }); 804 805 add_task(async function test_back_debouncing() { 806 _( 807 "Ensure spurious back-then-idle events, as observed on OS X, don't trigger a sync." 808 ); 809 810 // Confirm defaults. 811 Assert.equal(scheduler.idle, false); 812 813 // Set up: Define 2 clients and put the system in idle. 814 Svc.PrefBranch.setIntPref("clients.devices.desktop", 1); 815 Svc.PrefBranch.setIntPref("clients.devices.mobile", 1); 816 scheduler.observe( 817 null, 818 "idle", 819 Svc.PrefBranch.getIntPref("scheduler.idleTime") 820 ); 821 Assert.equal(scheduler.idle, true); 822 823 function onLoginStart() { 824 do_throw("Shouldn't have kicked off a sync!"); 825 } 826 Svc.Obs.add("weave:service:login:start", onLoginStart); 827 828 // Create spurious back-then-idle events as observed on OS X: 829 scheduler.observe( 830 null, 831 "active", 832 Svc.PrefBranch.getIntPref("scheduler.idleTime") 833 ); 834 scheduler.observe( 835 null, 836 "idle", 837 Svc.PrefBranch.getIntPref("scheduler.idleTime") 838 ); 839 840 await promiseNamedTimer(IDLE_OBSERVER_BACK_DELAY * 1.5, {}, "timer"); 841 Svc.Obs.remove("weave:service:login:start", onLoginStart); 842 await cleanUpAndGo(); 843 }); 844 845 add_task(async function test_no_sync_node() { 846 enableValidationPrefs(); 847 848 // Test when Status.sync == NO_SYNC_NODE_FOUND 849 // it is not overwritten on sync:finish 850 let server = await sync_httpd_setup(); 851 await setUp(server); 852 853 let oldfc = Service.identity._findCluster; 854 Service.identity._findCluster = () => null; 855 Service.clusterURL = ""; 856 try { 857 await Service.sync(); 858 Assert.equal(Status.sync, NO_SYNC_NODE_FOUND); 859 Assert.equal(scheduler.syncTimer.delay, NO_SYNC_NODE_INTERVAL); 860 861 await cleanUpAndGo(server); 862 } finally { 863 Service.identity._findCluster = oldfc; 864 } 865 }); 866 867 add_task(async function test_sync_failed_partial_500s() { 868 enableValidationPrefs(); 869 870 _("Test a 5xx status calls handleSyncError."); 871 scheduler._syncErrors = MAX_ERROR_COUNT_BEFORE_BACKOFF; 872 let server = await sync_httpd_setup(); 873 874 let engine = Service.engineManager.get("catapult"); 875 engine.enabled = true; 876 engine.exception = { status: 500 }; 877 878 Assert.equal(Status.sync, SYNC_SUCCEEDED); 879 880 Assert.ok(await setUp(server)); 881 882 await Service.sync(); 883 884 Assert.equal(Status.service, SYNC_FAILED_PARTIAL); 885 886 let maxInterval = scheduler._syncErrors * (2 * MINIMUM_BACKOFF_INTERVAL); 887 Assert.equal(Status.backoffInterval, 0); 888 Assert.ok(Status.enforceBackoff); 889 Assert.equal(scheduler._syncErrors, 4); 890 Assert.lessOrEqual(scheduler.nextSync, Date.now() + maxInterval); 891 Assert.lessOrEqual(scheduler.syncTimer.delay, maxInterval); 892 893 await cleanUpAndGo(server); 894 }); 895 896 add_task(async function test_sync_failed_partial_noresync() { 897 enableValidationPrefs(); 898 let server = await sync_httpd_setup(); 899 900 let engine = Service.engineManager.get("catapult"); 901 engine.enabled = true; 902 engine.exception = "Bad news"; 903 engine._tracker._score = MULTI_DEVICE_THRESHOLD + 1; 904 905 Assert.equal(Status.sync, SYNC_SUCCEEDED); 906 907 Assert.ok(await setUp(server)); 908 909 let resyncDoneObserver = promiseOneObserver("weave:service:resyncs-finished"); 910 911 await Service.sync(); 912 913 Assert.equal(Status.service, SYNC_FAILED_PARTIAL); 914 915 function onSyncStarted() { 916 do_throw("Should not start resync when previous sync failed"); 917 } 918 919 Svc.Obs.add("weave:service:sync:start", onSyncStarted); 920 await resyncDoneObserver; 921 922 Svc.Obs.remove("weave:service:sync:start", onSyncStarted); 923 engine._tracker._store = 0; 924 await cleanUpAndGo(server); 925 }); 926 927 add_task(async function test_sync_failed_partial_400s() { 928 enableValidationPrefs(); 929 930 _("Test a non-5xx status doesn't call handleSyncError."); 931 scheduler._syncErrors = MAX_ERROR_COUNT_BEFORE_BACKOFF; 932 let server = await sync_httpd_setup(); 933 934 let engine = Service.engineManager.get("catapult"); 935 engine.enabled = true; 936 engine.exception = { status: 400 }; 937 938 // Have multiple devices for an active interval. 939 await clientsEngine._store.create({ 940 id: "foo", 941 cleartext: { os: "mobile", version: "0.01", type: "desktop" }, 942 }); 943 944 Assert.equal(Status.sync, SYNC_SUCCEEDED); 945 946 Assert.ok(await setUp(server)); 947 948 await Service.sync(); 949 950 Assert.equal(Status.service, SYNC_FAILED_PARTIAL); 951 Assert.equal(scheduler.syncInterval, scheduler.activeInterval); 952 953 Assert.equal(Status.backoffInterval, 0); 954 Assert.ok(!Status.enforceBackoff); 955 Assert.equal(scheduler._syncErrors, 0); 956 Assert.lessOrEqual(scheduler.nextSync, Date.now() + scheduler.activeInterval); 957 Assert.lessOrEqual(scheduler.syncTimer.delay, scheduler.activeInterval); 958 959 await cleanUpAndGo(server); 960 }); 961 962 add_task(async function test_sync_X_Weave_Backoff() { 963 enableValidationPrefs(); 964 965 let server = await sync_httpd_setup(); 966 await setUp(server); 967 968 // Use an odd value on purpose so that it doesn't happen to coincide with one 969 // of the sync intervals. 970 const BACKOFF = 7337; 971 972 // Extend info/collections so that we can put it into server maintenance mode. 973 const INFO_COLLECTIONS = "/1.1/johndoe@mozilla.com/info/collections"; 974 let infoColl = server._handler._overridePaths[INFO_COLLECTIONS]; 975 let serverBackoff = false; 976 function infoCollWithBackoff(request, response) { 977 if (serverBackoff) { 978 response.setHeader("X-Weave-Backoff", "" + BACKOFF); 979 } 980 infoColl(request, response); 981 } 982 server.registerPathHandler(INFO_COLLECTIONS, infoCollWithBackoff); 983 984 // Pretend we have two clients so that the regular sync interval is 985 // sufficiently low. 986 await clientsEngine._store.create({ 987 id: "foo", 988 cleartext: { os: "mobile", version: "0.01", type: "desktop" }, 989 }); 990 let rec = await clientsEngine._store.createRecord("foo", "clients"); 991 await rec.encrypt(Service.collectionKeys.keyForCollection("clients")); 992 await rec.upload(Service.resource(clientsEngine.engineURL + rec.id)); 993 994 // Sync once to log in and get everything set up. Let's verify our initial 995 // values. 996 await Service.sync(); 997 Assert.equal(Status.backoffInterval, 0); 998 Assert.equal(Status.minimumNextSync, 0); 999 Assert.equal(scheduler.syncInterval, scheduler.activeInterval); 1000 Assert.lessOrEqual(scheduler.nextSync, Date.now() + scheduler.syncInterval); 1001 // Sanity check that we picked the right value for BACKOFF: 1002 Assert.less(scheduler.syncInterval, BACKOFF * 1000); 1003 1004 // Turn on server maintenance and sync again. 1005 serverBackoff = true; 1006 await Service.sync(); 1007 1008 Assert.greaterOrEqual(Status.backoffInterval, BACKOFF * 1000); 1009 // Allowing 20 seconds worth of of leeway between when Status.minimumNextSync 1010 // was set and when this line gets executed. 1011 let minimumExpectedDelay = (BACKOFF - 20) * 1000; 1012 Assert.greaterOrEqual( 1013 Status.minimumNextSync, 1014 Date.now() + minimumExpectedDelay 1015 ); 1016 1017 // Verify that the next sync is actually going to wait that long. 1018 Assert.greaterOrEqual(scheduler.nextSync, Date.now() + minimumExpectedDelay); 1019 Assert.greaterOrEqual(scheduler.syncTimer.delay, minimumExpectedDelay); 1020 1021 await cleanUpAndGo(server); 1022 }); 1023 1024 add_task(async function test_sync_503_Retry_After() { 1025 enableValidationPrefs(); 1026 1027 let server = await sync_httpd_setup(); 1028 await setUp(server); 1029 1030 // Use an odd value on purpose so that it doesn't happen to coincide with one 1031 // of the sync intervals. 1032 const BACKOFF = 7337; 1033 1034 // Extend info/collections so that we can put it into server maintenance mode. 1035 const INFO_COLLECTIONS = "/1.1/johndoe@mozilla.com/info/collections"; 1036 let infoColl = server._handler._overridePaths[INFO_COLLECTIONS]; 1037 let serverMaintenance = false; 1038 function infoCollWithMaintenance(request, response) { 1039 if (!serverMaintenance) { 1040 infoColl(request, response); 1041 return; 1042 } 1043 response.setHeader("Retry-After", "" + BACKOFF); 1044 response.setStatusLine(request.httpVersion, 503, "Service Unavailable"); 1045 } 1046 server.registerPathHandler(INFO_COLLECTIONS, infoCollWithMaintenance); 1047 1048 // Pretend we have two clients so that the regular sync interval is 1049 // sufficiently low. 1050 await clientsEngine._store.create({ 1051 id: "foo", 1052 cleartext: { os: "mobile", version: "0.01", type: "desktop" }, 1053 }); 1054 let rec = await clientsEngine._store.createRecord("foo", "clients"); 1055 await rec.encrypt(Service.collectionKeys.keyForCollection("clients")); 1056 await rec.upload(Service.resource(clientsEngine.engineURL + rec.id)); 1057 1058 // Sync once to log in and get everything set up. Let's verify our initial 1059 // values. 1060 await Service.sync(); 1061 Assert.ok(!Status.enforceBackoff); 1062 Assert.equal(Status.backoffInterval, 0); 1063 Assert.equal(Status.minimumNextSync, 0); 1064 Assert.equal(scheduler.syncInterval, scheduler.activeInterval); 1065 Assert.lessOrEqual(scheduler.nextSync, Date.now() + scheduler.syncInterval); 1066 // Sanity check that we picked the right value for BACKOFF: 1067 Assert.less(scheduler.syncInterval, BACKOFF * 1000); 1068 1069 // Turn on server maintenance and sync again. 1070 serverMaintenance = true; 1071 await Service.sync(); 1072 1073 Assert.ok(Status.enforceBackoff); 1074 Assert.greaterOrEqual(Status.backoffInterval, BACKOFF * 1000); 1075 // Allowing 3 seconds worth of of leeway between when Status.minimumNextSync 1076 // was set and when this line gets executed. 1077 let minimumExpectedDelay = (BACKOFF - 3) * 1000; 1078 Assert.greaterOrEqual( 1079 Status.minimumNextSync, 1080 Date.now() + minimumExpectedDelay 1081 ); 1082 1083 // Verify that the next sync is actually going to wait that long. 1084 Assert.greaterOrEqual(scheduler.nextSync, Date.now() + minimumExpectedDelay); 1085 Assert.greaterOrEqual(scheduler.syncTimer.delay, minimumExpectedDelay); 1086 1087 await cleanUpAndGo(server); 1088 }); 1089 1090 add_task(async function test_loginError_recoverable_reschedules() { 1091 _("Verify that a recoverable login error schedules a new sync."); 1092 await configureIdentity({ username: "johndoe@mozilla.com" }); 1093 Service.clusterURL = "http://localhost:1234/"; 1094 Status.resetSync(); // reset Status.login 1095 1096 let promiseObserved = promiseOneObserver("weave:service:login:error"); 1097 1098 // Let's set it up so that a sync is overdue, both in terms of previously 1099 // scheduled syncs and the global score. We still do not expect an immediate 1100 // sync because we just tried (duh). 1101 scheduler.nextSync = Date.now() - 100000; 1102 scheduler.globalScore = SINGLE_USER_THRESHOLD + 1; 1103 function onSyncStart() { 1104 do_throw("Shouldn't have started a sync!"); 1105 } 1106 Svc.Obs.add("weave:service:sync:start", onSyncStart); 1107 1108 // Sanity check. 1109 Assert.equal(scheduler.syncTimer, null); 1110 Assert.equal(Status.checkSetup(), STATUS_OK); 1111 Assert.equal(Status.login, LOGIN_SUCCEEDED); 1112 1113 scheduler.scheduleNextSync(0); 1114 await promiseObserved; 1115 await Async.promiseYield(); 1116 1117 Assert.equal(Status.login, LOGIN_FAILED_NETWORK_ERROR); 1118 1119 let expectedNextSync = Date.now() + scheduler.syncInterval; 1120 Assert.greater(scheduler.nextSync, Date.now()); 1121 Assert.lessOrEqual(scheduler.nextSync, expectedNextSync); 1122 Assert.greater(scheduler.syncTimer.delay, 0); 1123 Assert.lessOrEqual(scheduler.syncTimer.delay, scheduler.syncInterval); 1124 1125 Svc.Obs.remove("weave:service:sync:start", onSyncStart); 1126 await cleanUpAndGo(); 1127 }); 1128 1129 add_task(async function test_loginError_fatal_clearsTriggers() { 1130 _("Verify that a fatal login error clears sync triggers."); 1131 await configureIdentity({ username: "johndoe@mozilla.com" }); 1132 1133 let server = httpd_setup({ 1134 "/1.1/johndoe@mozilla.com/info/collections": httpd_handler( 1135 401, 1136 "Unauthorized" 1137 ), 1138 }); 1139 1140 Service.clusterURL = server.baseURI + "/"; 1141 Status.resetSync(); // reset Status.login 1142 1143 let promiseObserved = promiseOneObserver("weave:service:login:error"); 1144 1145 // Sanity check. 1146 Assert.equal(scheduler.nextSync, 0); 1147 Assert.equal(scheduler.syncTimer, null); 1148 Assert.equal(Status.checkSetup(), STATUS_OK); 1149 Assert.equal(Status.login, LOGIN_SUCCEEDED); 1150 1151 scheduler.scheduleNextSync(0); 1152 await promiseObserved; 1153 await Async.promiseYield(); 1154 1155 // For the FxA identity, a 401 on info/collections means a transient 1156 // error, probably due to an inability to fetch a token. 1157 Assert.equal(Status.login, LOGIN_FAILED_NETWORK_ERROR); 1158 // syncs should still be scheduled. 1159 Assert.greater(scheduler.nextSync, Date.now()); 1160 Assert.greater(scheduler.syncTimer.delay, 0); 1161 1162 await cleanUpAndGo(server); 1163 }); 1164 1165 add_task(async function test_proper_interval_on_only_failing() { 1166 _("Ensure proper behavior when only failed records are applied."); 1167 1168 // If an engine reports that no records succeeded, we shouldn't decrease the 1169 // sync interval. 1170 Assert.ok(!scheduler.hasIncomingItems); 1171 const INTERVAL = 10000000; 1172 scheduler.syncInterval = INTERVAL; 1173 1174 Svc.Obs.notify("weave:service:sync:applied", { 1175 applied: 2, 1176 succeeded: 0, 1177 failed: 2, 1178 newFailed: 2, 1179 reconciled: 0, 1180 }); 1181 1182 await Async.promiseYield(); 1183 scheduler.adjustSyncInterval(); 1184 Assert.ok(!scheduler.hasIncomingItems); 1185 Assert.equal(scheduler.syncInterval, scheduler.singleDeviceInterval); 1186 }); 1187 1188 add_task(async function test_link_status_change() { 1189 _("Check that we only attempt to sync when link status is up"); 1190 try { 1191 sinon.spy(scheduler, "scheduleNextSync"); 1192 1193 Svc.Obs.notify("network:link-status-changed", null, "down"); 1194 equal(scheduler.scheduleNextSync.callCount, 0); 1195 1196 Svc.Obs.notify("network:link-status-changed", null, "change"); 1197 equal(scheduler.scheduleNextSync.callCount, 0); 1198 1199 Svc.Obs.notify("network:link-status-changed", null, "up"); 1200 equal(scheduler.scheduleNextSync.callCount, 1); 1201 1202 Svc.Obs.notify("network:link-status-changed", null, "change"); 1203 equal(scheduler.scheduleNextSync.callCount, 1); 1204 } finally { 1205 scheduler.scheduleNextSync.restore(); 1206 } 1207 });