/* Any copyright is dedicated to the Public Domain. http://creativecommons.org/publicdomain/zero/1.0/ */ Svc.PrefBranch.setStringPref("registerEngines", ""); const { Service } = ChromeUtils.importESModule( "resource://services-sync/service.sys.mjs" ); let scheduler; let clientsEngine; async function sync_httpd_setup() { let clientsSyncID = await clientsEngine.resetLocalSyncID(); let global = new ServerWBO("global", { syncID: Service.syncID, storageVersion: STORAGE_VERSION, engines: { clients: { version: clientsEngine.version, syncID: clientsSyncID }, }, }); let clientsColl = new ServerCollection({}, true); // Tracking info/collections. let collectionsHelper = track_collections_helper(); let upd = collectionsHelper.with_updated_collection; return httpd_setup({ "/1.1/johndoe/storage/meta/global": upd("meta", global.handler()), "/1.1/johndoe/info/collections": collectionsHelper.handler, "/1.1/johndoe/storage/crypto/keys": upd( "crypto", new ServerWBO("keys").handler() ), "/1.1/johndoe/storage/clients": upd("clients", clientsColl.handler()), }); } async function setUp(server) { syncTestLogging(); await configureIdentity({ username: "johndoe" }, server); await generateNewKeys(Service.collectionKeys); let serverKeys = Service.collectionKeys.asWBO("crypto", "keys"); await serverKeys.encrypt(Service.identity.syncKeyBundle); await serverKeys.upload(Service.resource(Service.cryptoKeysURL)); } add_task(async function setup() { scheduler = Service.scheduler; clientsEngine = Service.clientsEngine; // Don't remove stale clients when syncing. This is a test-only workaround // that lets us add clients directly to the store, without losing them on // the next sync. clientsEngine._removeRemoteClient = async () => {}; }); add_task(async function test_successful_sync_adjustSyncInterval() { enableValidationPrefs(); _("Test successful sync calling adjustSyncInterval"); let syncSuccesses = 0; function onSyncFinish() { _("Sync success."); syncSuccesses++; } Svc.Obs.add("weave:service:sync:finish", onSyncFinish); let server = await sync_httpd_setup(); await setUp(server); // Confirm defaults Assert.ok(!scheduler.idle); Assert.equal(false, scheduler.numClients > 1); Assert.equal(scheduler.syncInterval, scheduler.singleDeviceInterval); Assert.ok(!scheduler.hasIncomingItems); _("Test as long as numClients <= 1 our sync interval is SINGLE_USER."); // idle == true && numClients <= 1 && hasIncomingItems == false scheduler.idle = true; await Service.sync(); Assert.equal(syncSuccesses, 1); Assert.ok(scheduler.idle); Assert.equal(false, scheduler.numClients > 1); Assert.ok(!scheduler.hasIncomingItems); Assert.equal(scheduler.syncInterval, scheduler.singleDeviceInterval); // idle == false && numClients <= 1 && hasIncomingItems == false scheduler.idle = false; await Service.sync(); Assert.equal(syncSuccesses, 2); Assert.ok(!scheduler.idle); Assert.equal(false, scheduler.numClients > 1); Assert.ok(!scheduler.hasIncomingItems); Assert.equal(scheduler.syncInterval, scheduler.singleDeviceInterval); // idle == false && numClients <= 1 && hasIncomingItems == true scheduler.hasIncomingItems = true; await Service.sync(); Assert.equal(syncSuccesses, 3); Assert.ok(!scheduler.idle); Assert.equal(false, scheduler.numClients > 1); Assert.ok(scheduler.hasIncomingItems); Assert.equal(scheduler.syncInterval, scheduler.singleDeviceInterval); // idle == true && numClients <= 1 && hasIncomingItems == true scheduler.idle = true; await Service.sync(); Assert.equal(syncSuccesses, 4); Assert.ok(scheduler.idle); Assert.equal(false, scheduler.numClients > 1); Assert.ok(scheduler.hasIncomingItems); Assert.equal(scheduler.syncInterval, scheduler.singleDeviceInterval); _( "Test as long as idle && numClients > 1 our sync interval is idleInterval." ); // idle == true && numClients > 1 && hasIncomingItems == true await Service.clientsEngine._store.create({ id: "foo", cleartext: { name: "bar", type: "mobile" }, }); await Service.sync(); Assert.equal(syncSuccesses, 5); Assert.ok(scheduler.idle); Assert.ok(scheduler.numClients > 1); Assert.ok(scheduler.hasIncomingItems); Assert.equal(scheduler.syncInterval, scheduler.idleInterval); // idle == true && numClients > 1 && hasIncomingItems == false scheduler.hasIncomingItems = false; await Service.sync(); Assert.equal(syncSuccesses, 6); Assert.ok(scheduler.idle); Assert.ok(scheduler.numClients > 1); Assert.ok(!scheduler.hasIncomingItems); Assert.equal(scheduler.syncInterval, scheduler.idleInterval); _("Test non-idle, numClients > 1, no incoming items => activeInterval."); // idle == false && numClients > 1 && hasIncomingItems == false scheduler.idle = false; await Service.sync(); Assert.equal(syncSuccesses, 7); Assert.ok(!scheduler.idle); Assert.ok(scheduler.numClients > 1); Assert.ok(!scheduler.hasIncomingItems); Assert.equal(scheduler.syncInterval, scheduler.activeInterval); _("Test non-idle, numClients > 1, incoming items => immediateInterval."); // idle == false && numClients > 1 && hasIncomingItems == true scheduler.hasIncomingItems = true; await Service.sync(); Assert.equal(syncSuccesses, 8); Assert.ok(!scheduler.idle); Assert.ok(scheduler.numClients > 1); Assert.ok(!scheduler.hasIncomingItems); // gets reset to false Assert.equal(scheduler.syncInterval, scheduler.immediateInterval); Svc.Obs.remove("weave:service:sync:finish", onSyncFinish); await Service.startOver(); await promiseStopServer(server); }); add_task(async function test_unsuccessful_sync_adjustSyncInterval() { enableValidationPrefs(); _("Test unsuccessful sync calling adjustSyncInterval"); let syncFailures = 0; function onSyncError() { _("Sync error."); syncFailures++; } Svc.Obs.add("weave:service:sync:error", onSyncError); _("Test unsuccessful sync calls adjustSyncInterval"); // Force sync to fail. Svc.PrefBranch.setStringPref("firstSync", "notReady"); let server = await sync_httpd_setup(); await setUp(server); // Confirm defaults Assert.ok(!scheduler.idle); Assert.equal(false, scheduler.numClients > 1); Assert.equal(scheduler.syncInterval, scheduler.singleDeviceInterval); Assert.ok(!scheduler.hasIncomingItems); _("Test as long as numClients <= 1 our sync interval is SINGLE_USER."); // idle == true && numClients <= 1 && hasIncomingItems == false scheduler.idle = true; await Service.sync(); Assert.equal(syncFailures, 1); Assert.ok(scheduler.idle); Assert.equal(false, scheduler.numClients > 1); Assert.ok(!scheduler.hasIncomingItems); Assert.equal(scheduler.syncInterval, scheduler.singleDeviceInterval); // idle == false && numClients <= 1 && hasIncomingItems == false scheduler.idle = false; await Service.sync(); Assert.equal(syncFailures, 2); Assert.ok(!scheduler.idle); Assert.equal(false, scheduler.numClients > 1); Assert.ok(!scheduler.hasIncomingItems); Assert.equal(scheduler.syncInterval, scheduler.singleDeviceInterval); // idle == false && numClients <= 1 && hasIncomingItems == true scheduler.hasIncomingItems = true; await Service.sync(); Assert.equal(syncFailures, 3); Assert.ok(!scheduler.idle); Assert.equal(false, scheduler.numClients > 1); Assert.ok(scheduler.hasIncomingItems); Assert.equal(scheduler.syncInterval, scheduler.singleDeviceInterval); // idle == true && numClients <= 1 && hasIncomingItems == true scheduler.idle = true; await Service.sync(); Assert.equal(syncFailures, 4); Assert.ok(scheduler.idle); Assert.equal(false, scheduler.numClients > 1); Assert.ok(scheduler.hasIncomingItems); Assert.equal(scheduler.syncInterval, scheduler.singleDeviceInterval); _( "Test as long as idle && numClients > 1 our sync interval is idleInterval." ); // idle == true && numClients > 1 && hasIncomingItems == true Svc.PrefBranch.setIntPref("clients.devices.mobile", 2); scheduler.updateClientMode(); await Service.sync(); Assert.equal(syncFailures, 5); Assert.ok(scheduler.idle); Assert.ok(scheduler.numClients > 1); Assert.ok(scheduler.hasIncomingItems); Assert.equal(scheduler.syncInterval, scheduler.idleInterval); // idle == true && numClients > 1 && hasIncomingItems == false scheduler.hasIncomingItems = false; await Service.sync(); Assert.equal(syncFailures, 6); Assert.ok(scheduler.idle); Assert.ok(scheduler.numClients > 1); Assert.ok(!scheduler.hasIncomingItems); Assert.equal(scheduler.syncInterval, scheduler.idleInterval); _("Test non-idle, numClients > 1, no incoming items => activeInterval."); // idle == false && numClients > 1 && hasIncomingItems == false scheduler.idle = false; await Service.sync(); Assert.equal(syncFailures, 7); Assert.ok(!scheduler.idle); Assert.ok(scheduler.numClients > 1); Assert.ok(!scheduler.hasIncomingItems); Assert.equal(scheduler.syncInterval, scheduler.activeInterval); _("Test non-idle, numClients > 1, incoming items => immediateInterval."); // idle == false && numClients > 1 && hasIncomingItems == true scheduler.hasIncomingItems = true; await Service.sync(); Assert.equal(syncFailures, 8); Assert.ok(!scheduler.idle); Assert.ok(scheduler.numClients > 1); Assert.ok(!scheduler.hasIncomingItems); // gets reset to false Assert.equal(scheduler.syncInterval, scheduler.immediateInterval); await Service.startOver(); Svc.Obs.remove("weave:service:sync:error", onSyncError); await promiseStopServer(server); }); add_task(async function test_back_triggers_sync() { enableValidationPrefs(); let server = await sync_httpd_setup(); await setUp(server); // Single device: no sync triggered. scheduler.idle = true; scheduler.observe( null, "active", Svc.PrefBranch.getIntPref("scheduler.idleTime") ); Assert.ok(!scheduler.idle); // Multiple devices: sync is triggered. Svc.PrefBranch.setIntPref("clients.devices.mobile", 2); scheduler.updateClientMode(); let promiseDone = promiseOneObserver("weave:service:sync:finish"); scheduler.idle = true; scheduler.observe( null, "active", Svc.PrefBranch.getIntPref("scheduler.idleTime") ); Assert.ok(!scheduler.idle); await promiseDone; Service.recordManager.clearCache(); for (const pref of Svc.PrefBranch.getChildList("")) { Svc.PrefBranch.clearUserPref(pref); } scheduler.setDefaults(); await clientsEngine.resetClient(); await Service.startOver(); await promiseStopServer(server); }); add_task(async function test_adjust_interval_on_sync_error() { enableValidationPrefs(); let server = await sync_httpd_setup(); await setUp(server); let syncFailures = 0; function onSyncError() { _("Sync error."); syncFailures++; } Svc.Obs.add("weave:service:sync:error", onSyncError); _("Test unsuccessful sync updates client mode & sync intervals"); // Force a sync fail. Svc.PrefBranch.setStringPref("firstSync", "notReady"); Assert.equal(syncFailures, 0); Assert.equal(false, scheduler.numClients > 1); Assert.equal(scheduler.syncInterval, scheduler.singleDeviceInterval); Svc.PrefBranch.setIntPref("clients.devices.mobile", 2); await Service.sync(); Assert.equal(syncFailures, 1); Assert.ok(scheduler.numClients > 1); Assert.equal(scheduler.syncInterval, scheduler.activeInterval); Svc.Obs.remove("weave:service:sync:error", onSyncError); await Service.startOver(); await promiseStopServer(server); }); add_task(async function test_bug671378_scenario() { enableValidationPrefs(); // Test scenario similar to bug 671378. This bug appeared when a score // update occurred that wasn't large enough to trigger a sync so // scheduleNextSync() was called without a time interval parameter, // setting nextSync to a non-zero value and preventing the timer from // being adjusted in the next call to scheduleNextSync(). let server = await sync_httpd_setup(); await setUp(server); let syncSuccesses = 0; function onSyncFinish() { _("Sync success."); syncSuccesses++; } Svc.Obs.add("weave:service:sync:finish", onSyncFinish); // After first sync call, syncInterval & syncTimer are singleDeviceInterval. await Service.sync(); Assert.equal(syncSuccesses, 1); Assert.equal(false, scheduler.numClients > 1); Assert.equal(scheduler.syncInterval, scheduler.singleDeviceInterval); Assert.equal(scheduler.syncTimer.delay, scheduler.singleDeviceInterval); let promiseDone = new Promise(resolve => { // Wrap scheduleNextSync so we are notified when it is finished. scheduler._scheduleNextSync = scheduler.scheduleNextSync; scheduler.scheduleNextSync = function () { scheduler._scheduleNextSync(); // Check on sync:finish scheduleNextSync sets the appropriate // syncInterval and syncTimer values. if (syncSuccesses == 2) { Assert.notEqual(scheduler.nextSync, 0); Assert.equal(scheduler.syncInterval, scheduler.activeInterval); Assert.ok(scheduler.syncTimer.delay <= scheduler.activeInterval); scheduler.scheduleNextSync = scheduler._scheduleNextSync; Svc.Obs.remove("weave:service:sync:finish", onSyncFinish); Service.startOver().then(() => { server.stop(resolve); }); } }; }); // Set nextSync != 0 // syncInterval still hasn't been set by call to updateClientMode. // Explicitly trying to invoke scheduleNextSync during a sync // (to immitate a score update that isn't big enough to trigger a sync). Svc.Obs.add("weave:service:sync:start", function onSyncStart() { // Wait for other sync:start observers to be called so that // nextSync is set to 0. CommonUtils.nextTick(function () { Svc.Obs.remove("weave:service:sync:start", onSyncStart); scheduler.scheduleNextSync(); Assert.notEqual(scheduler.nextSync, 0); Assert.equal(scheduler.syncInterval, scheduler.singleDeviceInterval); Assert.equal(scheduler.syncTimer.delay, scheduler.singleDeviceInterval); }); }); await Service.clientsEngine._store.create({ id: "foo", cleartext: { name: "bar", type: "mobile" }, }); await Service.sync(); await promiseDone; }); add_task(async function test_adjust_timer_larger_syncInterval() { _( "Test syncInterval > current timout period && nextSync != 0, syncInterval is NOT used." ); Svc.PrefBranch.setIntPref("clients.devices.mobile", 2); scheduler.updateClientMode(); Assert.equal(scheduler.syncInterval, scheduler.activeInterval); scheduler.scheduleNextSync(); // Ensure we have a small interval. Assert.notEqual(scheduler.nextSync, 0); Assert.equal(scheduler.syncTimer.delay, scheduler.activeInterval); // Make interval large again await clientsEngine._wipeClient(); Svc.PrefBranch.clearUserPref("clients.devices.mobile"); scheduler.updateClientMode(); Assert.equal(scheduler.syncInterval, scheduler.singleDeviceInterval); scheduler.scheduleNextSync(); // Ensure timer delay remains as the small interval. Assert.notEqual(scheduler.nextSync, 0); Assert.ok(scheduler.syncTimer.delay <= scheduler.activeInterval); // SyncSchedule. await Service.startOver(); }); add_task(async function test_adjust_timer_smaller_syncInterval() { _( "Test current timout > syncInterval period && nextSync != 0, syncInterval is used." ); scheduler.scheduleNextSync(); // Ensure we have a large interval. Assert.notEqual(scheduler.nextSync, 0); Assert.equal(scheduler.syncTimer.delay, scheduler.singleDeviceInterval); // Make interval smaller Svc.PrefBranch.setIntPref("clients.devices.mobile", 2); scheduler.updateClientMode(); Assert.equal(scheduler.syncInterval, scheduler.activeInterval); scheduler.scheduleNextSync(); // Ensure smaller timer delay is used. Assert.notEqual(scheduler.nextSync, 0); Assert.ok(scheduler.syncTimer.delay <= scheduler.activeInterval); // SyncSchedule. await Service.startOver(); });