gecko/services/sync/tests/unit/test_syncscheduler.js
Gregory Szorc 58bcd2801d Bug 787273 - Part 2: Refactor Resource and Record to not rely on singletons; r=rnewman
Resource currently relies on the Identity singleton to perform
authentication. This is bad magic behavior. Resource instances should
authenticate according to the service instance they are associated with.

This patch removes Identity magic from Resource. Everything using
Resource now explicitly assigns an authenticator which comes from
the service instance/singleton. This required API changes to Collection
and Record.

The preferred method to obtain a Resource instance is to call
getResource() on a service instance.

The end result of this patch looks a little weird, especially in test
code. You have things like Service.resource(Service.cryptoKeysURL).
This ugliness will go away when a unified storage service client is
used.
2012-09-14 16:02:32 -07:00

954 lines
33 KiB
JavaScript

/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
Cu.import("resource://services-sync/constants.js");
Cu.import("resource://services-sync/identity.js");
Cu.import("resource://services-sync/engines.js");
Cu.import("resource://services-sync/engines/clients.js");
Cu.import("resource://services-sync/policies.js");
Cu.import("resource://services-sync/record.js");
Cu.import("resource://services-sync/service.js");
Cu.import("resource://services-sync/status.js");
Service.engineManager.clear();
function CatapultEngine() {
SyncEngine.call(this, "Catapult", Service);
}
CatapultEngine.prototype = {
__proto__: SyncEngine.prototype,
exception: null, // tests fill this in
_sync: function _sync() {
throw this.exception;
}
};
Service.engineManager.register(CatapultEngine);
let scheduler = new SyncScheduler(Service);
let clientsEngine = Service.clientsEngine;
function sync_httpd_setup() {
let global = new ServerWBO("global", {
syncID: Service.syncID,
storageVersion: STORAGE_VERSION,
engines: {clients: {version: clientsEngine.version,
syncID: clientsEngine.syncID}}
});
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()),
"/user/1.0/johndoe/node/weave": httpd_handler(200, "OK", "null")
});
}
function setUp() {
setBasicCredentials("johndoe", "ilovejane", "abcdeabcdeabcdeabcdeabcdea");
Service.clusterURL = TEST_CLUSTER_URL;
generateNewKeys();
let serverKeys = CollectionKeys.asWBO("crypto", "keys");
serverKeys.encrypt(Identity.syncKeyBundle);
return serverKeys.upload(Service.resource(Service.cryptoKeysURL)).success;
}
function cleanUpAndGo(server) {
Utils.nextTick(function () {
Service.startOver();
if (server) {
server.stop(run_next_test);
} else {
run_next_test();
}
});
}
function run_test() {
initTestLogging("Trace");
Log4Moz.repository.getLogger("Sync.Service").level = Log4Moz.Level.Trace;
Log4Moz.repository.getLogger("Sync.scheduler").level = Log4Moz.Level.Trace;
run_next_test();
}
add_test(function test_prefAttributes() {
_("Test various attributes corresponding to preferences.");
const INTERVAL = 42 * 60 * 1000; // 42 minutes
const THRESHOLD = 3142;
const SCORE = 2718;
const TIMESTAMP1 = 1275493471649;
_("The 'nextSync' attribute stores a millisecond timestamp rounded down to the nearest second.");
do_check_eq(scheduler.nextSync, 0);
scheduler.nextSync = TIMESTAMP1;
do_check_eq(scheduler.nextSync, Math.floor(TIMESTAMP1 / 1000) * 1000);
_("'syncInterval' defaults to singleDeviceInterval.");
do_check_eq(Svc.Prefs.get('syncInterval'), undefined);
do_check_eq(scheduler.syncInterval, scheduler.singleDeviceInterval);
_("'syncInterval' corresponds to a preference setting.");
scheduler.syncInterval = INTERVAL;
do_check_eq(scheduler.syncInterval, INTERVAL);
do_check_eq(Svc.Prefs.get('syncInterval'), INTERVAL);
_("'syncThreshold' corresponds to preference, defaults to SINGLE_USER_THRESHOLD");
do_check_eq(Svc.Prefs.get('syncThreshold'), undefined);
do_check_eq(scheduler.syncThreshold, SINGLE_USER_THRESHOLD);
scheduler.syncThreshold = THRESHOLD;
do_check_eq(scheduler.syncThreshold, THRESHOLD);
_("'globalScore' corresponds to preference, defaults to zero.");
do_check_eq(Svc.Prefs.get('globalScore'), 0);
do_check_eq(scheduler.globalScore, 0);
scheduler.globalScore = SCORE;
do_check_eq(scheduler.globalScore, SCORE);
do_check_eq(Svc.Prefs.get('globalScore'), SCORE);
_("Intervals correspond to default preferences.");
do_check_eq(scheduler.singleDeviceInterval,
Svc.Prefs.get("scheduler.singleDeviceInterval") * 1000);
do_check_eq(scheduler.idleInterval,
Svc.Prefs.get("scheduler.idleInterval") * 1000);
do_check_eq(scheduler.activeInterval,
Svc.Prefs.get("scheduler.activeInterval") * 1000);
do_check_eq(scheduler.immediateInterval,
Svc.Prefs.get("scheduler.immediateInterval") * 1000);
_("Custom values for prefs will take effect after a restart.");
Svc.Prefs.set("scheduler.singleDeviceInterval", 42);
Svc.Prefs.set("scheduler.idleInterval", 23);
Svc.Prefs.set("scheduler.activeInterval", 18);
Svc.Prefs.set("scheduler.immediateInterval", 31415);
scheduler.setDefaults();
do_check_eq(scheduler.idleInterval, 23000);
do_check_eq(scheduler.singleDeviceInterval, 42000);
do_check_eq(scheduler.activeInterval, 18000);
do_check_eq(scheduler.immediateInterval, 31415000);
Svc.Prefs.resetBranch("");
scheduler.setDefaults();
run_next_test();
});
add_test(function test_updateClientMode() {
_("Test updateClientMode adjusts scheduling attributes based on # of clients appropriately");
do_check_eq(scheduler.syncThreshold, SINGLE_USER_THRESHOLD);
do_check_eq(scheduler.syncInterval, scheduler.singleDeviceInterval);
do_check_false(scheduler.numClients > 1);
do_check_false(scheduler.idle);
// Trigger a change in interval & threshold by adding a client.
clientsEngine._store.create({id: "foo", cleartext: "bar"});
scheduler.updateClientMode();
do_check_eq(scheduler.syncThreshold, MULTI_DEVICE_THRESHOLD);
do_check_eq(scheduler.syncInterval, scheduler.activeInterval);
do_check_true(scheduler.numClients > 1);
do_check_false(scheduler.idle);
// Resets the number of clients to 0.
clientsEngine.resetClient();
scheduler.updateClientMode();
// Goes back to single user if # clients is 1.
do_check_eq(scheduler.numClients, 1);
do_check_eq(scheduler.syncThreshold, SINGLE_USER_THRESHOLD);
do_check_eq(scheduler.syncInterval, scheduler.singleDeviceInterval);
do_check_false(scheduler.numClients > 1);
do_check_false(scheduler.idle);
cleanUpAndGo();
});
add_test(function test_masterpassword_locked_retry_interval() {
_("Test Status.login = MASTER_PASSWORD_LOCKED results in reschedule at MASTER_PASSWORD interval");
let loginFailed = false;
Svc.Obs.add("weave:service:login:error", function onLoginError() {
Svc.Obs.remove("weave:service:login:error", onLoginError);
loginFailed = true;
});
let rescheduleInterval = false;
let oldScheduleAtInterval = SyncScheduler.prototype.scheduleAtInterval;
SyncScheduler.prototype.scheduleAtInterval = function (interval) {
rescheduleInterval = true;
do_check_eq(interval, MASTER_PASSWORD_LOCKED_RETRY_INTERVAL);
};
let oldVerifyLogin = Service.verifyLogin;
Service.verifyLogin = function () {
Status.login = MASTER_PASSWORD_LOCKED;
return false;
};
let server = sync_httpd_setup();
setUp();
Service.sync();
do_check_true(loginFailed);
do_check_eq(Status.login, MASTER_PASSWORD_LOCKED);
do_check_true(rescheduleInterval);
Service.verifyLogin = oldVerifyLogin;
SyncScheduler.prototype.scheduleAtInterval = oldScheduleAtInterval;
cleanUpAndGo(server);
});
add_test(function test_calculateBackoff() {
do_check_eq(Status.backoffInterval, 0);
// Test no interval larger than the maximum backoff is used if
// Status.backoffInterval is smaller.
Status.backoffInterval = 5;
let backoffInterval = Utils.calculateBackoff(50, MAXIMUM_BACKOFF_INTERVAL,
Status.backoffInterval);
do_check_eq(backoffInterval, MAXIMUM_BACKOFF_INTERVAL);
// Test Status.backoffInterval is used if it is
// larger than MAXIMUM_BACKOFF_INTERVAL.
Status.backoffInterval = MAXIMUM_BACKOFF_INTERVAL + 10;
backoffInterval = Utils.calculateBackoff(50, MAXIMUM_BACKOFF_INTERVAL,
Status.backoffInterval);
do_check_eq(backoffInterval, MAXIMUM_BACKOFF_INTERVAL + 10);
cleanUpAndGo();
});
add_test(function test_scheduleNextSync_nowOrPast() {
Svc.Obs.add("weave:service:sync:finish", function onSyncFinish() {
Svc.Obs.remove("weave:service:sync:finish", onSyncFinish);
cleanUpAndGo(server);
});
let server = sync_httpd_setup();
setUp();
// We're late for a sync...
scheduler.scheduleNextSync(-1);
});
add_test(function test_scheduleNextSync_future_noBackoff() {
_("scheduleNextSync() uses the current syncInterval if no interval is provided.");
// Test backoffInterval is 0 as expected.
do_check_eq(Status.backoffInterval, 0);
_("Test setting sync interval when nextSync == 0");
scheduler.nextSync = 0;
scheduler.scheduleNextSync();
// nextSync - Date.now() might be smaller than expectedInterval
// since some time has passed since we called scheduleNextSync().
do_check_true(scheduler.nextSync - Date.now()
<= scheduler.syncInterval);
do_check_eq(scheduler.syncTimer.delay, scheduler.syncInterval);
_("Test setting sync interval when nextSync != 0");
scheduler.nextSync = Date.now() + scheduler.singleDeviceInterval;
scheduler.scheduleNextSync();
// nextSync - Date.now() might be smaller than expectedInterval
// since some time has passed since we called scheduleNextSync().
do_check_true(scheduler.nextSync - Date.now()
<= scheduler.syncInterval);
do_check_true(scheduler.syncTimer.delay <= scheduler.syncInterval);
_("Scheduling requests for intervals larger than the current one will be ignored.");
// Request a sync at a longer interval. The sync that's already scheduled
// for sooner takes precedence.
let nextSync = scheduler.nextSync;
let timerDelay = scheduler.syncTimer.delay;
let requestedInterval = scheduler.syncInterval * 10;
scheduler.scheduleNextSync(requestedInterval);
do_check_eq(scheduler.nextSync, nextSync);
do_check_eq(scheduler.syncTimer.delay, timerDelay);
// We can schedule anything we want if there isn't a sync scheduled.
scheduler.nextSync = 0;
scheduler.scheduleNextSync(requestedInterval);
do_check_true(scheduler.nextSync <= Date.now() + requestedInterval);
do_check_eq(scheduler.syncTimer.delay, requestedInterval);
// Request a sync at the smallest possible interval (0 triggers now).
scheduler.scheduleNextSync(1);
do_check_true(scheduler.nextSync <= Date.now() + 1);
do_check_eq(scheduler.syncTimer.delay, 1);
cleanUpAndGo();
});
add_test(function test_scheduleNextSync_future_backoff() {
_("scheduleNextSync() will honour backoff in all scheduling requests.");
// Let's take a backoff interval that's bigger than the default sync interval.
const BACKOFF = 7337;
Status.backoffInterval = scheduler.syncInterval + BACKOFF;
_("Test setting sync interval when nextSync == 0");
scheduler.nextSync = 0;
scheduler.scheduleNextSync();
// nextSync - Date.now() might be smaller than expectedInterval
// since some time has passed since we called scheduleNextSync().
do_check_true(scheduler.nextSync - Date.now()
<= Status.backoffInterval);
do_check_eq(scheduler.syncTimer.delay, Status.backoffInterval);
_("Test setting sync interval when nextSync != 0");
scheduler.nextSync = Date.now() + scheduler.singleDeviceInterval;
scheduler.scheduleNextSync();
// nextSync - Date.now() might be smaller than expectedInterval
// since some time has passed since we called scheduleNextSync().
do_check_true(scheduler.nextSync - Date.now()
<= Status.backoffInterval);
do_check_true(scheduler.syncTimer.delay <= Status.backoffInterval);
// Request a sync at a longer interval. The sync that's already scheduled
// for sooner takes precedence.
let nextSync = scheduler.nextSync;
let timerDelay = scheduler.syncTimer.delay;
let requestedInterval = scheduler.syncInterval * 10;
do_check_true(requestedInterval > Status.backoffInterval);
scheduler.scheduleNextSync(requestedInterval);
do_check_eq(scheduler.nextSync, nextSync);
do_check_eq(scheduler.syncTimer.delay, timerDelay);
// We can schedule anything we want if there isn't a sync scheduled.
scheduler.nextSync = 0;
scheduler.scheduleNextSync(requestedInterval);
do_check_true(scheduler.nextSync <= Date.now() + requestedInterval);
do_check_eq(scheduler.syncTimer.delay, requestedInterval);
// Request a sync at the smallest possible interval (0 triggers now).
scheduler.scheduleNextSync(1);
do_check_true(scheduler.nextSync <= Date.now() + Status.backoffInterval);
do_check_eq(scheduler.syncTimer.delay, Status.backoffInterval);
cleanUpAndGo();
});
add_test(function test_handleSyncError() {
let server = sync_httpd_setup();
setUp();
// Force sync to fail.
Svc.Prefs.set("firstSync", "notReady");
_("Ensure expected initial environment.");
do_check_eq(scheduler._syncErrors, 0);
do_check_false(Status.enforceBackoff);
do_check_eq(scheduler.syncInterval, scheduler.singleDeviceInterval);
do_check_eq(Status.backoffInterval, 0);
// Trigger sync with an error several times & observe
// functionality of handleSyncError()
_("Test first error calls scheduleNextSync on default interval");
Service.sync();
do_check_true(scheduler.nextSync <= Date.now() + scheduler.singleDeviceInterval);
do_check_eq(scheduler.syncTimer.delay, scheduler.singleDeviceInterval);
do_check_eq(scheduler._syncErrors, 1);
do_check_false(Status.enforceBackoff);
scheduler.syncTimer.clear();
_("Test second error still calls scheduleNextSync on default interval");
Service.sync();
do_check_true(scheduler.nextSync <= Date.now() + scheduler.singleDeviceInterval);
do_check_eq(scheduler.syncTimer.delay, scheduler.singleDeviceInterval);
do_check_eq(scheduler._syncErrors, 2);
do_check_false(Status.enforceBackoff);
scheduler.syncTimer.clear();
_("Test third error sets Status.enforceBackoff and calls scheduleAtInterval");
Service.sync();
let maxInterval = scheduler._syncErrors * (2 * MINIMUM_BACKOFF_INTERVAL);
do_check_eq(Status.backoffInterval, 0);
do_check_true(scheduler.nextSync <= (Date.now() + maxInterval));
do_check_true(scheduler.syncTimer.delay <= maxInterval);
do_check_eq(scheduler._syncErrors, 3);
do_check_true(Status.enforceBackoff);
// Status.enforceBackoff is false but there are still errors.
Status.resetBackoff();
do_check_false(Status.enforceBackoff);
do_check_eq(scheduler._syncErrors, 3);
scheduler.syncTimer.clear();
_("Test fourth error still calls scheduleAtInterval even if enforceBackoff was reset");
Service.sync();
maxInterval = scheduler._syncErrors * (2 * MINIMUM_BACKOFF_INTERVAL);
do_check_true(scheduler.nextSync <= Date.now() + maxInterval);
do_check_true(scheduler.syncTimer.delay <= maxInterval);
do_check_eq(scheduler._syncErrors, 4);
do_check_true(Status.enforceBackoff);
scheduler.syncTimer.clear();
cleanUpAndGo(server);
});
add_test(function test_client_sync_finish_updateClientMode() {
let server = sync_httpd_setup();
setUp();
// Confirm defaults.
do_check_eq(scheduler.syncThreshold, SINGLE_USER_THRESHOLD);
do_check_eq(scheduler.syncInterval, scheduler.singleDeviceInterval);
do_check_false(scheduler.idle);
// Trigger a change in interval & threshold by adding a client.
clientsEngine._store.create({id: "foo", cleartext: "bar"});
do_check_false(scheduler.numClients > 1);
scheduler.updateClientMode();
Service.sync();
do_check_eq(scheduler.syncThreshold, MULTI_DEVICE_THRESHOLD);
do_check_eq(scheduler.syncInterval, scheduler.activeInterval);
do_check_true(scheduler.numClients > 1);
do_check_false(scheduler.idle);
// Resets the number of clients to 0.
clientsEngine.resetClient();
Service.sync();
// Goes back to single user if # clients is 1.
do_check_eq(scheduler.numClients, 1);
do_check_eq(scheduler.syncThreshold, SINGLE_USER_THRESHOLD);
do_check_eq(scheduler.syncInterval, scheduler.singleDeviceInterval);
do_check_false(scheduler.numClients > 1);
do_check_false(scheduler.idle);
cleanUpAndGo(server);
});
add_test(function test_autoconnect_nextSync_past() {
// nextSync will be 0 by default, so it's way in the past.
Svc.Obs.add("weave:service:sync:finish", function onSyncFinish() {
Svc.Obs.remove("weave:service:sync:finish", onSyncFinish);
cleanUpAndGo(server);
});
let server = sync_httpd_setup();
setUp();
scheduler.delayedAutoConnect(0);
});
add_test(function test_autoconnect_nextSync_future() {
let previousSync = Date.now() + scheduler.syncInterval / 2;
scheduler.nextSync = previousSync;
// nextSync rounds to the nearest second.
let expectedSync = scheduler.nextSync;
let expectedInterval = expectedSync - Date.now() - 1000;
// Ensure we don't actually try to sync (or log in for that matter).
function onLoginStart() {
do_throw("Should not get here!");
}
Svc.Obs.add("weave:service:login:start", onLoginStart);
waitForZeroTimer(function () {
do_check_eq(scheduler.nextSync, expectedSync);
do_check_true(scheduler.syncTimer.delay >= expectedInterval);
Svc.Obs.remove("weave:service:login:start", onLoginStart);
cleanUpAndGo();
});
setBasicCredentials("johndoe", "ilovejane", "abcdeabcdeabcdeabcdeabcdea");
scheduler.delayedAutoConnect(0);
});
add_test(function test_autoconnect_mp_locked() {
let server = sync_httpd_setup();
setUp();
// Pretend user did not unlock master password.
let origLocked = Utils.mpLocked;
Utils.mpLocked = function() true;
let origGetter = Identity.__lookupGetter__("syncKey");
let origSetter = Identity.__lookupSetter__("syncKey");
delete Identity.syncKey;
Identity.__defineGetter__("syncKey", function() {
_("Faking Master Password entry cancelation.");
throw "User canceled Master Password entry";
});
// A locked master password will still trigger a sync, but then we'll hit
// MASTER_PASSWORD_LOCKED and hence MASTER_PASSWORD_LOCKED_RETRY_INTERVAL.
Svc.Obs.add("weave:service:login:error", function onLoginError() {
Svc.Obs.remove("weave:service:login:error", onLoginError);
Utils.nextTick(function aLittleBitAfterLoginError() {
do_check_eq(Status.login, MASTER_PASSWORD_LOCKED);
Utils.mpLocked = origLocked;
delete Identity.syncKey;
Identity.__defineGetter__("syncKey", origGetter);
Identity.__defineSetter__("syncKey", origSetter);
cleanUpAndGo(server);
});
});
scheduler.delayedAutoConnect(0);
});
add_test(function test_no_autoconnect_during_wizard() {
let server = sync_httpd_setup();
setUp();
// Simulate the Sync setup wizard.
Svc.Prefs.set("firstSync", "notReady");
// Ensure we don't actually try to sync (or log in for that matter).
function onLoginStart() {
do_throw("Should not get here!");
}
Svc.Obs.add("weave:service:login:start", onLoginStart);
waitForZeroTimer(function () {
Svc.Obs.remove("weave:service:login:start", onLoginStart);
cleanUpAndGo(server);
});
scheduler.delayedAutoConnect(0);
});
add_test(function test_no_autoconnect_status_not_ok() {
let server = sync_httpd_setup();
// Ensure we don't actually try to sync (or log in for that matter).
function onLoginStart() {
do_throw("Should not get here!");
}
Svc.Obs.add("weave:service:login:start", onLoginStart);
waitForZeroTimer(function () {
Svc.Obs.remove("weave:service:login:start", onLoginStart);
do_check_eq(Status.service, CLIENT_NOT_CONFIGURED);
do_check_eq(Status.login, LOGIN_FAILED_NO_USERNAME);
cleanUpAndGo(server);
});
scheduler.delayedAutoConnect(0);
});
add_test(function test_autoconnectDelay_pref() {
Svc.Obs.add("weave:service:sync:finish", function onSyncFinish() {
Svc.Obs.remove("weave:service:sync:finish", onSyncFinish);
cleanUpAndGo(server);
});
Svc.Prefs.set("autoconnectDelay", 1);
let server = sync_httpd_setup();
setUp();
Svc.Obs.notify("weave:service:ready");
// autoconnectDelay pref is multiplied by 1000.
do_check_eq(scheduler._autoTimer.delay, 1000);
do_check_eq(Status.service, STATUS_OK);
});
add_test(function test_idle_adjustSyncInterval() {
// Confirm defaults.
do_check_eq(scheduler.idle, false);
// Single device: nothing changes.
scheduler.observe(null, "idle", Svc.Prefs.get("scheduler.idleTime"));
do_check_eq(scheduler.idle, true);
do_check_eq(scheduler.syncInterval, scheduler.singleDeviceInterval);
// Multiple devices: switch to idle interval.
scheduler.idle = false;
clientsEngine._store.create({id: "foo", cleartext: "bar"});
scheduler.updateClientMode();
scheduler.observe(null, "idle", Svc.Prefs.get("scheduler.idleTime"));
do_check_eq(scheduler.idle, true);
do_check_eq(scheduler.syncInterval, scheduler.idleInterval);
cleanUpAndGo();
});
add_test(function test_back_triggersSync() {
// Confirm defaults.
do_check_false(scheduler.idle);
do_check_eq(Status.backoffInterval, 0);
// Set up: Define 2 clients and put the system in idle.
scheduler.numClients = 2;
scheduler.observe(null, "idle", Svc.Prefs.get("scheduler.idleTime"));
do_check_true(scheduler.idle);
// We don't actually expect the sync (or the login, for that matter) to
// succeed. We just want to ensure that it was attempted.
Svc.Obs.add("weave:service:login:error", function onLoginError() {
Svc.Obs.remove("weave:service:login:error", onLoginError);
cleanUpAndGo();
});
// Send a 'back' event to trigger sync soonish.
scheduler.observe(null, "back", Svc.Prefs.get("scheduler.idleTime"));
});
add_test(function test_back_triggersSync_observesBackoff() {
// Confirm defaults.
do_check_false(scheduler.idle);
// Set up: Set backoff, define 2 clients and put the system in idle.
const BACKOFF = 7337;
Status.backoffInterval = scheduler.idleInterval + BACKOFF;
scheduler.numClients = 2;
scheduler.observe(null, "idle", Svc.Prefs.get("scheduler.idleTime"));
do_check_eq(scheduler.idle, true);
function onLoginStart() {
do_throw("Shouldn't have kicked off a sync!");
}
Svc.Obs.add("weave:service:login:start", onLoginStart);
timer = Utils.namedTimer(function () {
Svc.Obs.remove("weave:service:login:start", onLoginStart);
do_check_true(scheduler.nextSync <= Date.now() + Status.backoffInterval);
do_check_eq(scheduler.syncTimer.delay, Status.backoffInterval);
cleanUpAndGo();
}, IDLE_OBSERVER_BACK_DELAY * 1.5, {}, "timer");
// Send a 'back' event to try to trigger sync soonish.
scheduler.observe(null, "back", Svc.Prefs.get("scheduler.idleTime"));
});
add_test(function test_back_debouncing() {
_("Ensure spurious back-then-idle events, as observed on OS X, don't trigger a sync.");
// Confirm defaults.
do_check_eq(scheduler.idle, false);
// Set up: Define 2 clients and put the system in idle.
scheduler.numClients = 2;
scheduler.observe(null, "idle", Svc.Prefs.get("scheduler.idleTime"));
do_check_eq(scheduler.idle, true);
function onLoginStart() {
do_throw("Shouldn't have kicked off a sync!");
}
Svc.Obs.add("weave:service:login:start", onLoginStart);
// Create spurious back-then-idle events as observed on OS X:
scheduler.observe(null, "back", Svc.Prefs.get("scheduler.idleTime"));
scheduler.observe(null, "idle", Svc.Prefs.get("scheduler.idleTime"));
timer = Utils.namedTimer(function () {
Svc.Obs.remove("weave:service:login:start", onLoginStart);
cleanUpAndGo();
}, IDLE_OBSERVER_BACK_DELAY * 1.5, {}, "timer");
});
add_test(function test_no_sync_node() {
// Test when Status.sync == NO_SYNC_NODE_FOUND
// it is not overwritten on sync:finish
let server = sync_httpd_setup();
setUp();
Service.serverURL = TEST_SERVER_URL;
Service.sync();
do_check_eq(Status.sync, NO_SYNC_NODE_FOUND);
do_check_eq(scheduler.syncTimer.delay, NO_SYNC_NODE_INTERVAL);
cleanUpAndGo(server);
});
add_test(function test_sync_failed_partial_500s() {
_("Test a 5xx status calls handleSyncError.");
scheduler._syncErrors = MAX_ERROR_COUNT_BEFORE_BACKOFF;
let server = sync_httpd_setup();
let engine = Service.engineManager.get("catapult");
engine.enabled = true;
engine.exception = {status: 500};
do_check_eq(Status.sync, SYNC_SUCCEEDED);
do_check_true(setUp());
Service.sync();
do_check_eq(Status.service, SYNC_FAILED_PARTIAL);
let maxInterval = scheduler._syncErrors * (2 * MINIMUM_BACKOFF_INTERVAL);
do_check_eq(Status.backoffInterval, 0);
do_check_true(Status.enforceBackoff);
do_check_eq(scheduler._syncErrors, 4);
do_check_true(scheduler.nextSync <= (Date.now() + maxInterval));
do_check_true(scheduler.syncTimer.delay <= maxInterval);
cleanUpAndGo(server);
});
add_test(function test_sync_failed_partial_400s() {
_("Test a non-5xx status doesn't call handleSyncError.");
scheduler._syncErrors = MAX_ERROR_COUNT_BEFORE_BACKOFF;
let server = sync_httpd_setup();
let engine = Service.engineManager.get("catapult");
engine.enabled = true;
engine.exception = {status: 400};
// Have multiple devices for an active interval.
clientsEngine._store.create({id: "foo", cleartext: "bar"});
do_check_eq(Status.sync, SYNC_SUCCEEDED);
do_check_true(setUp());
Service.sync();
do_check_eq(Status.service, SYNC_FAILED_PARTIAL);
do_check_eq(scheduler.syncInterval, scheduler.activeInterval);
do_check_eq(Status.backoffInterval, 0);
do_check_false(Status.enforceBackoff);
do_check_eq(scheduler._syncErrors, 0);
do_check_true(scheduler.nextSync <= (Date.now() + scheduler.activeInterval));
do_check_true(scheduler.syncTimer.delay <= scheduler.activeInterval);
cleanUpAndGo(server);
});
add_test(function test_sync_X_Weave_Backoff() {
let server = sync_httpd_setup();
setUp();
// Use an odd value on purpose so that it doesn't happen to coincide with one
// of the sync intervals.
const BACKOFF = 7337;
// Extend info/collections so that we can put it into server maintenance mode.
const INFO_COLLECTIONS = "/1.1/johndoe/info/collections";
let infoColl = server._handler._overridePaths[INFO_COLLECTIONS];
let serverBackoff = false;
function infoCollWithBackoff(request, response) {
if (serverBackoff) {
response.setHeader("X-Weave-Backoff", "" + BACKOFF);
}
infoColl(request, response);
}
server.registerPathHandler(INFO_COLLECTIONS, infoCollWithBackoff);
// Pretend we have two clients so that the regular sync interval is
// sufficiently low.
clientsEngine._store.create({id: "foo", cleartext: "bar"});
let rec = clientsEngine._store.createRecord("foo", "clients");
rec.encrypt();
rec.upload(Service.resource(clientsEngine.engineURL + rec.id));
// Sync once to log in and get everything set up. Let's verify our initial
// values.
Service.sync();
do_check_eq(Status.backoffInterval, 0);
do_check_eq(Status.minimumNextSync, 0);
do_check_eq(scheduler.syncInterval, scheduler.activeInterval);
do_check_true(scheduler.nextSync <=
Date.now() + scheduler.syncInterval);
// Sanity check that we picked the right value for BACKOFF:
do_check_true(scheduler.syncInterval < BACKOFF * 1000);
// Turn on server maintenance and sync again.
serverBackoff = true;
Service.sync();
do_check_true(Status.backoffInterval >= BACKOFF * 1000);
// Allowing 1 second worth of of leeway between when Status.minimumNextSync
// was set and when this line gets executed.
let minimumExpectedDelay = (BACKOFF - 1) * 1000;
do_check_true(Status.minimumNextSync >= Date.now() + minimumExpectedDelay);
// Verify that the next sync is actually going to wait that long.
do_check_true(scheduler.nextSync >= Date.now() + minimumExpectedDelay);
do_check_true(scheduler.syncTimer.delay >= minimumExpectedDelay);
cleanUpAndGo(server);
});
add_test(function test_sync_503_Retry_After() {
let server = sync_httpd_setup();
setUp();
// Use an odd value on purpose so that it doesn't happen to coincide with one
// of the sync intervals.
const BACKOFF = 7337;
// Extend info/collections so that we can put it into server maintenance mode.
const INFO_COLLECTIONS = "/1.1/johndoe/info/collections";
let infoColl = server._handler._overridePaths[INFO_COLLECTIONS];
let serverMaintenance = false;
function infoCollWithMaintenance(request, response) {
if (!serverMaintenance) {
infoColl(request, response);
return;
}
response.setHeader("Retry-After", "" + BACKOFF);
response.setStatusLine(request.httpVersion, 503, "Service Unavailable");
}
server.registerPathHandler(INFO_COLLECTIONS, infoCollWithMaintenance);
// Pretend we have two clients so that the regular sync interval is
// sufficiently low.
clientsEngine._store.create({id: "foo", cleartext: "bar"});
let rec = clientsEngine._store.createRecord("foo", "clients");
rec.encrypt();
rec.upload(Service.resource(clientsEngine.engineURL + rec.id));
// Sync once to log in and get everything set up. Let's verify our initial
// values.
Service.sync();
do_check_false(Status.enforceBackoff);
do_check_eq(Status.backoffInterval, 0);
do_check_eq(Status.minimumNextSync, 0);
do_check_eq(scheduler.syncInterval, scheduler.activeInterval);
do_check_true(scheduler.nextSync <=
Date.now() + scheduler.syncInterval);
// Sanity check that we picked the right value for BACKOFF:
do_check_true(scheduler.syncInterval < BACKOFF * 1000);
// Turn on server maintenance and sync again.
serverMaintenance = true;
Service.sync();
do_check_true(Status.enforceBackoff);
do_check_true(Status.backoffInterval >= BACKOFF * 1000);
// Allowing 1 second worth of of leeway between when Status.minimumNextSync
// was set and when this line gets executed.
let minimumExpectedDelay = (BACKOFF - 1) * 1000;
do_check_true(Status.minimumNextSync >= Date.now() + minimumExpectedDelay);
// Verify that the next sync is actually going to wait that long.
do_check_true(scheduler.nextSync >= Date.now() + minimumExpectedDelay);
do_check_true(scheduler.syncTimer.delay >= minimumExpectedDelay);
cleanUpAndGo(server);
});
add_test(function test_loginError_recoverable_reschedules() {
_("Verify that a recoverable login error schedules a new sync.");
setBasicCredentials("johndoe", "ilovejane", "abcdeabcdeabcdeabcdeabcdea");
Service.serverURL = TEST_SERVER_URL;
Service.clusterURL = TEST_CLUSTER_URL;
Service.persistLogin();
Status.resetSync(); // reset Status.login
Svc.Obs.add("weave:service:login:error", function onLoginError() {
Svc.Obs.remove("weave:service:login:error", onLoginError);
Utils.nextTick(function aLittleBitAfterLoginError() {
do_check_eq(Status.login, LOGIN_FAILED_NETWORK_ERROR);
let expectedNextSync = Date.now() + scheduler.syncInterval;
do_check_true(scheduler.nextSync > Date.now());
do_check_true(scheduler.nextSync <= expectedNextSync);
do_check_true(scheduler.syncTimer.delay > 0);
do_check_true(scheduler.syncTimer.delay <= scheduler.syncInterval);
Svc.Obs.remove("weave:service:sync:start", onSyncStart);
cleanUpAndGo();
});
});
// Let's set it up so that a sync is overdue, both in terms of previously
// scheduled syncs and the global score. We still do not expect an immediate
// sync because we just tried (duh).
scheduler.nextSync = Date.now() - 100000;
scheduler.globalScore = SINGLE_USER_THRESHOLD + 1;
function onSyncStart() {
do_throw("Shouldn't have started a sync!");
}
Svc.Obs.add("weave:service:sync:start", onSyncStart);
// Sanity check.
do_check_eq(scheduler.syncTimer, null);
do_check_eq(Status.checkSetup(), STATUS_OK);
do_check_eq(Status.login, LOGIN_SUCCEEDED);
scheduler.scheduleNextSync(0);
});
add_test(function test_loginError_fatal_clearsTriggers() {
_("Verify that a fatal login error clears sync triggers.");
setBasicCredentials("johndoe", "ilovejane", "abcdeabcdeabcdeabcdeabcdea");
Service.serverURL = TEST_SERVER_URL;
Service.clusterURL = TEST_CLUSTER_URL;
Service.persistLogin();
Status.resetSync(); // reset Status.login
let server = httpd_setup({
"/1.1/johndoe/info/collections": httpd_handler(401, "Unauthorized")
});
Svc.Obs.add("weave:service:login:error", function onLoginError() {
Svc.Obs.remove("weave:service:login:error", onLoginError);
Utils.nextTick(function aLittleBitAfterLoginError() {
do_check_eq(Status.login, LOGIN_FAILED_LOGIN_REJECTED);
do_check_eq(scheduler.nextSync, 0);
do_check_eq(scheduler.syncTimer, null);
cleanUpAndGo(server);
});
});
// Sanity check.
do_check_eq(scheduler.nextSync, 0);
do_check_eq(scheduler.syncTimer, null);
do_check_eq(Status.checkSetup(), STATUS_OK);
do_check_eq(Status.login, LOGIN_SUCCEEDED);
scheduler.scheduleNextSync(0);
});
add_test(function test_proper_interval_on_only_failing() {
_("Ensure proper behavior when only failed records are applied.");
// If an engine reports that no records succeeded, we shouldn't decrease the
// sync interval.
do_check_false(scheduler.hasIncomingItems);
const INTERVAL = 10000000;
scheduler.syncInterval = INTERVAL;
Svc.Obs.notify("weave:service:sync:applied", {
applied: 2,
succeeded: 0,
failed: 2,
newFailed: 2,
reconciled: 0
});
Utils.nextTick(function() {
scheduler.adjustSyncInterval();
do_check_false(scheduler.hasIncomingItems);
do_check_eq(scheduler.syncInterval, scheduler.singleDeviceInterval);
run_next_test();
});
});