forked from mirrors/gecko-dev
Theoretically we only need to change this where the strings might be non-ascii but it seems safer in the long run to just avoid the "char" versions entirely. Differential Revision: https://phabricator.services.mozilla.com/D200342
1195 lines
38 KiB
JavaScript
1195 lines
38 KiB
JavaScript
/* Any copyright is dedicated to the Public Domain.
|
|
http://creativecommons.org/publicdomain/zero/1.0/ */
|
|
|
|
const { FxAccounts } = ChromeUtils.importESModule(
|
|
"resource://gre/modules/FxAccounts.sys.mjs"
|
|
);
|
|
const { SyncAuthManager } = ChromeUtils.importESModule(
|
|
"resource://services-sync/sync_auth.sys.mjs"
|
|
);
|
|
const { SyncScheduler } = ChromeUtils.importESModule(
|
|
"resource://services-sync/policies.sys.mjs"
|
|
);
|
|
const { Service } = ChromeUtils.importESModule(
|
|
"resource://services-sync/service.sys.mjs"
|
|
);
|
|
const { Status } = ChromeUtils.importESModule(
|
|
"resource://services-sync/status.sys.mjs"
|
|
);
|
|
|
|
function CatapultEngine() {
|
|
SyncEngine.call(this, "Catapult", Service);
|
|
}
|
|
CatapultEngine.prototype = {
|
|
exception: null, // tests fill this in
|
|
async _sync() {
|
|
throw this.exception;
|
|
},
|
|
};
|
|
Object.setPrototypeOf(CatapultEngine.prototype, SyncEngine.prototype);
|
|
|
|
var scheduler = new SyncScheduler(Service);
|
|
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@mozilla.com/storage/meta/global": upd(
|
|
"meta",
|
|
global.handler()
|
|
),
|
|
"/1.1/johndoe@mozilla.com/info/collections": collectionsHelper.handler,
|
|
"/1.1/johndoe@mozilla.com/storage/crypto/keys": upd(
|
|
"crypto",
|
|
new ServerWBO("keys").handler()
|
|
),
|
|
"/1.1/johndoe@mozilla.com/storage/clients": upd(
|
|
"clients",
|
|
clientsColl.handler()
|
|
),
|
|
});
|
|
}
|
|
|
|
async function setUp(server) {
|
|
await configureIdentity({ username: "johndoe@mozilla.com" }, server);
|
|
|
|
await generateNewKeys(Service.collectionKeys);
|
|
let serverKeys = Service.collectionKeys.asWBO("crypto", "keys");
|
|
await serverKeys.encrypt(Service.identity.syncKeyBundle);
|
|
let result = (
|
|
await serverKeys.upload(Service.resource(Service.cryptoKeysURL))
|
|
).success;
|
|
return result;
|
|
}
|
|
|
|
async function cleanUpAndGo(server) {
|
|
await Async.promiseYield();
|
|
await clientsEngine._store.wipe();
|
|
await Service.startOver();
|
|
// Re-enable logging, which we just disabled.
|
|
syncTestLogging();
|
|
if (server) {
|
|
await promiseStopServer(server);
|
|
}
|
|
}
|
|
|
|
add_task(async function setup() {
|
|
await Service.promiseInitialized;
|
|
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 id => {};
|
|
await Service.engineManager.clear();
|
|
|
|
validate_all_future_pings();
|
|
|
|
scheduler.setDefaults();
|
|
|
|
await Service.engineManager.register(CatapultEngine);
|
|
});
|
|
|
|
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."
|
|
);
|
|
Assert.equal(scheduler.nextSync, 0);
|
|
scheduler.nextSync = TIMESTAMP1;
|
|
Assert.equal(scheduler.nextSync, Math.floor(TIMESTAMP1 / 1000) * 1000);
|
|
|
|
_("'syncInterval' defaults to singleDeviceInterval.");
|
|
Assert.equal(
|
|
Svc.PrefBranch.getPrefType("syncInterval"),
|
|
Ci.nsIPrefBranch.PREF_INVALID
|
|
);
|
|
Assert.equal(scheduler.syncInterval, scheduler.singleDeviceInterval);
|
|
|
|
_("'syncInterval' corresponds to a preference setting.");
|
|
scheduler.syncInterval = INTERVAL;
|
|
Assert.equal(scheduler.syncInterval, INTERVAL);
|
|
Assert.equal(Svc.PrefBranch.getIntPref("syncInterval"), INTERVAL);
|
|
|
|
_(
|
|
"'syncThreshold' corresponds to preference, defaults to SINGLE_USER_THRESHOLD"
|
|
);
|
|
Assert.equal(
|
|
Svc.PrefBranch.getPrefType("syncThreshold"),
|
|
Ci.nsIPrefBranch.PREF_INVALID
|
|
);
|
|
Assert.equal(scheduler.syncThreshold, SINGLE_USER_THRESHOLD);
|
|
scheduler.syncThreshold = THRESHOLD;
|
|
Assert.equal(scheduler.syncThreshold, THRESHOLD);
|
|
|
|
_("'globalScore' corresponds to preference, defaults to zero.");
|
|
Assert.equal(Svc.PrefBranch.getIntPref("globalScore"), 0);
|
|
Assert.equal(scheduler.globalScore, 0);
|
|
scheduler.globalScore = SCORE;
|
|
Assert.equal(scheduler.globalScore, SCORE);
|
|
Assert.equal(Svc.PrefBranch.getIntPref("globalScore"), SCORE);
|
|
|
|
_("Intervals correspond to default preferences.");
|
|
Assert.equal(
|
|
scheduler.singleDeviceInterval,
|
|
Svc.PrefBranch.getIntPref("scheduler.fxa.singleDeviceInterval") * 1000
|
|
);
|
|
Assert.equal(
|
|
scheduler.idleInterval,
|
|
Svc.PrefBranch.getIntPref("scheduler.idleInterval") * 1000
|
|
);
|
|
Assert.equal(
|
|
scheduler.activeInterval,
|
|
Svc.PrefBranch.getIntPref("scheduler.activeInterval") * 1000
|
|
);
|
|
Assert.equal(
|
|
scheduler.immediateInterval,
|
|
Svc.PrefBranch.getIntPref("scheduler.immediateInterval") * 1000
|
|
);
|
|
|
|
_("Custom values for prefs will take effect after a restart.");
|
|
Svc.PrefBranch.setIntPref("scheduler.fxa.singleDeviceInterval", 420);
|
|
Svc.PrefBranch.setIntPref("scheduler.idleInterval", 230);
|
|
Svc.PrefBranch.setIntPref("scheduler.activeInterval", 180);
|
|
Svc.PrefBranch.setIntPref("scheduler.immediateInterval", 31415);
|
|
scheduler.setDefaults();
|
|
Assert.equal(scheduler.idleInterval, 230000);
|
|
Assert.equal(scheduler.singleDeviceInterval, 420000);
|
|
Assert.equal(scheduler.activeInterval, 180000);
|
|
Assert.equal(scheduler.immediateInterval, 31415000);
|
|
|
|
_("Custom values for interval prefs can't be less than 60 seconds.");
|
|
Svc.PrefBranch.setIntPref("scheduler.fxa.singleDeviceInterval", 42);
|
|
Svc.PrefBranch.setIntPref("scheduler.idleInterval", 50);
|
|
Svc.PrefBranch.setIntPref("scheduler.activeInterval", 50);
|
|
Svc.PrefBranch.setIntPref("scheduler.immediateInterval", 10);
|
|
scheduler.setDefaults();
|
|
Assert.equal(scheduler.idleInterval, 60000);
|
|
Assert.equal(scheduler.singleDeviceInterval, 60000);
|
|
Assert.equal(scheduler.activeInterval, 60000);
|
|
Assert.equal(scheduler.immediateInterval, 60000);
|
|
|
|
for (const pref of Svc.PrefBranch.getChildList("")) {
|
|
Svc.PrefBranch.clearUserPref(pref);
|
|
}
|
|
scheduler.setDefaults();
|
|
run_next_test();
|
|
});
|
|
|
|
add_task(async function test_sync_skipped_low_score_no_resync() {
|
|
enableValidationPrefs();
|
|
let server = await sync_httpd_setup();
|
|
|
|
function SkipEngine() {
|
|
SyncEngine.call(this, "Skip", Service);
|
|
this.syncs = 0;
|
|
}
|
|
|
|
SkipEngine.prototype = {
|
|
_sync() {
|
|
do_throw("Should have been skipped");
|
|
},
|
|
shouldSkipSync() {
|
|
return true;
|
|
},
|
|
};
|
|
Object.setPrototypeOf(SkipEngine.prototype, SyncEngine.prototype);
|
|
await Service.engineManager.register(SkipEngine);
|
|
|
|
let engine = Service.engineManager.get("skip");
|
|
engine.enabled = true;
|
|
engine._tracker._score = 30;
|
|
|
|
Assert.equal(Status.sync, SYNC_SUCCEEDED);
|
|
|
|
Assert.ok(await setUp(server));
|
|
|
|
let resyncDoneObserver = promiseOneObserver("weave:service:resyncs-finished");
|
|
|
|
let synced = false;
|
|
function onSyncStarted() {
|
|
Assert.ok(!synced, "Only should sync once");
|
|
synced = true;
|
|
}
|
|
|
|
await Service.sync();
|
|
|
|
Assert.equal(Status.sync, SYNC_SUCCEEDED);
|
|
|
|
Svc.Obs.add("weave:service:sync:start", onSyncStarted);
|
|
await resyncDoneObserver;
|
|
|
|
Svc.Obs.remove("weave:service:sync:start", onSyncStarted);
|
|
engine._tracker._store = 0;
|
|
await cleanUpAndGo(server);
|
|
});
|
|
|
|
add_task(async function test_updateClientMode() {
|
|
_(
|
|
"Test updateClientMode adjusts scheduling attributes based on # of clients appropriately"
|
|
);
|
|
Assert.equal(scheduler.syncThreshold, SINGLE_USER_THRESHOLD);
|
|
Assert.equal(scheduler.syncInterval, scheduler.singleDeviceInterval);
|
|
Assert.equal(false, scheduler.numClients > 1);
|
|
Assert.ok(!scheduler.idle);
|
|
|
|
// Trigger a change in interval & threshold by noting there are multiple clients.
|
|
Svc.PrefBranch.setIntPref("clients.devices.desktop", 1);
|
|
Svc.PrefBranch.setIntPref("clients.devices.mobile", 1);
|
|
scheduler.updateClientMode();
|
|
|
|
Assert.equal(scheduler.syncThreshold, MULTI_DEVICE_THRESHOLD);
|
|
Assert.equal(scheduler.syncInterval, scheduler.activeInterval);
|
|
Assert.ok(scheduler.numClients > 1);
|
|
Assert.ok(!scheduler.idle);
|
|
|
|
// Resets the number of clients to 0.
|
|
await clientsEngine.resetClient();
|
|
Svc.PrefBranch.clearUserPref("clients.devices.mobile");
|
|
scheduler.updateClientMode();
|
|
|
|
// Goes back to single user if # clients is 1.
|
|
Assert.equal(scheduler.numClients, 1);
|
|
Assert.equal(scheduler.syncThreshold, SINGLE_USER_THRESHOLD);
|
|
Assert.equal(scheduler.syncInterval, scheduler.singleDeviceInterval);
|
|
Assert.equal(false, scheduler.numClients > 1);
|
|
Assert.ok(!scheduler.idle);
|
|
|
|
await cleanUpAndGo();
|
|
});
|
|
|
|
add_task(async function test_masterpassword_locked_retry_interval() {
|
|
enableValidationPrefs();
|
|
|
|
_(
|
|
"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;
|
|
Assert.equal(interval, MASTER_PASSWORD_LOCKED_RETRY_INTERVAL);
|
|
};
|
|
|
|
let oldVerifyLogin = Service.verifyLogin;
|
|
Service.verifyLogin = async function () {
|
|
Status.login = MASTER_PASSWORD_LOCKED;
|
|
return false;
|
|
};
|
|
|
|
let server = await sync_httpd_setup();
|
|
await setUp(server);
|
|
|
|
await Service.sync();
|
|
|
|
Assert.ok(loginFailed);
|
|
Assert.equal(Status.login, MASTER_PASSWORD_LOCKED);
|
|
Assert.ok(rescheduleInterval);
|
|
|
|
Service.verifyLogin = oldVerifyLogin;
|
|
SyncScheduler.prototype.scheduleAtInterval = oldScheduleAtInterval;
|
|
|
|
await cleanUpAndGo(server);
|
|
});
|
|
|
|
add_task(async function test_calculateBackoff() {
|
|
Assert.equal(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
|
|
);
|
|
|
|
Assert.equal(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
|
|
);
|
|
|
|
Assert.equal(backoffInterval, MAXIMUM_BACKOFF_INTERVAL + 10);
|
|
|
|
await cleanUpAndGo();
|
|
});
|
|
|
|
add_task(async function test_scheduleNextSync_nowOrPast() {
|
|
enableValidationPrefs();
|
|
|
|
let promiseObserved = promiseOneObserver("weave:service:sync:finish");
|
|
|
|
let server = await sync_httpd_setup();
|
|
await setUp(server);
|
|
|
|
// We're late for a sync...
|
|
scheduler.scheduleNextSync(-1);
|
|
await promiseObserved;
|
|
await cleanUpAndGo(server);
|
|
});
|
|
|
|
add_task(async function test_scheduleNextSync_future_noBackoff() {
|
|
enableValidationPrefs();
|
|
|
|
_(
|
|
"scheduleNextSync() uses the current syncInterval if no interval is provided."
|
|
);
|
|
// Test backoffInterval is 0 as expected.
|
|
Assert.equal(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().
|
|
Assert.ok(scheduler.nextSync - Date.now() <= scheduler.syncInterval);
|
|
Assert.equal(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().
|
|
Assert.ok(scheduler.nextSync - Date.now() <= scheduler.syncInterval);
|
|
Assert.ok(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);
|
|
Assert.equal(scheduler.nextSync, nextSync);
|
|
Assert.equal(scheduler.syncTimer.delay, timerDelay);
|
|
|
|
// We can schedule anything we want if there isn't a sync scheduled.
|
|
scheduler.nextSync = 0;
|
|
scheduler.scheduleNextSync(requestedInterval);
|
|
Assert.ok(scheduler.nextSync <= Date.now() + requestedInterval);
|
|
Assert.equal(scheduler.syncTimer.delay, requestedInterval);
|
|
|
|
// Request a sync at the smallest possible interval (0 triggers now).
|
|
scheduler.scheduleNextSync(1);
|
|
Assert.ok(scheduler.nextSync <= Date.now() + 1);
|
|
Assert.equal(scheduler.syncTimer.delay, 1);
|
|
|
|
await cleanUpAndGo();
|
|
});
|
|
|
|
add_task(async function test_scheduleNextSync_future_backoff() {
|
|
enableValidationPrefs();
|
|
|
|
_("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().
|
|
Assert.ok(scheduler.nextSync - Date.now() <= Status.backoffInterval);
|
|
Assert.equal(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().
|
|
Assert.ok(scheduler.nextSync - Date.now() <= Status.backoffInterval);
|
|
Assert.ok(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;
|
|
Assert.ok(requestedInterval > Status.backoffInterval);
|
|
scheduler.scheduleNextSync(requestedInterval);
|
|
Assert.equal(scheduler.nextSync, nextSync);
|
|
Assert.equal(scheduler.syncTimer.delay, timerDelay);
|
|
|
|
// We can schedule anything we want if there isn't a sync scheduled.
|
|
scheduler.nextSync = 0;
|
|
scheduler.scheduleNextSync(requestedInterval);
|
|
Assert.ok(scheduler.nextSync <= Date.now() + requestedInterval);
|
|
Assert.equal(scheduler.syncTimer.delay, requestedInterval);
|
|
|
|
// Request a sync at the smallest possible interval (0 triggers now).
|
|
scheduler.scheduleNextSync(1);
|
|
Assert.ok(scheduler.nextSync <= Date.now() + Status.backoffInterval);
|
|
Assert.equal(scheduler.syncTimer.delay, Status.backoffInterval);
|
|
|
|
await cleanUpAndGo();
|
|
});
|
|
|
|
add_task(async function test_handleSyncError() {
|
|
enableValidationPrefs();
|
|
|
|
let server = await sync_httpd_setup();
|
|
await setUp(server);
|
|
|
|
// Force sync to fail.
|
|
Svc.PrefBranch.setStringPref("firstSync", "notReady");
|
|
|
|
_("Ensure expected initial environment.");
|
|
Assert.equal(scheduler._syncErrors, 0);
|
|
Assert.ok(!Status.enforceBackoff);
|
|
Assert.equal(scheduler.syncInterval, scheduler.singleDeviceInterval);
|
|
Assert.equal(Status.backoffInterval, 0);
|
|
|
|
// Trigger sync with an error several times & observe
|
|
// functionality of handleSyncError()
|
|
_("Test first error calls scheduleNextSync on default interval");
|
|
await Service.sync();
|
|
Assert.ok(scheduler.nextSync <= Date.now() + scheduler.singleDeviceInterval);
|
|
Assert.equal(scheduler.syncTimer.delay, scheduler.singleDeviceInterval);
|
|
Assert.equal(scheduler._syncErrors, 1);
|
|
Assert.ok(!Status.enforceBackoff);
|
|
scheduler.syncTimer.clear();
|
|
|
|
_("Test second error still calls scheduleNextSync on default interval");
|
|
await Service.sync();
|
|
Assert.ok(scheduler.nextSync <= Date.now() + scheduler.singleDeviceInterval);
|
|
Assert.equal(scheduler.syncTimer.delay, scheduler.singleDeviceInterval);
|
|
Assert.equal(scheduler._syncErrors, 2);
|
|
Assert.ok(!Status.enforceBackoff);
|
|
scheduler.syncTimer.clear();
|
|
|
|
_("Test third error sets Status.enforceBackoff and calls scheduleAtInterval");
|
|
await Service.sync();
|
|
let maxInterval = scheduler._syncErrors * (2 * MINIMUM_BACKOFF_INTERVAL);
|
|
Assert.equal(Status.backoffInterval, 0);
|
|
Assert.ok(scheduler.nextSync <= Date.now() + maxInterval);
|
|
Assert.ok(scheduler.syncTimer.delay <= maxInterval);
|
|
Assert.equal(scheduler._syncErrors, 3);
|
|
Assert.ok(Status.enforceBackoff);
|
|
|
|
// Status.enforceBackoff is false but there are still errors.
|
|
Status.resetBackoff();
|
|
Assert.ok(!Status.enforceBackoff);
|
|
Assert.equal(scheduler._syncErrors, 3);
|
|
scheduler.syncTimer.clear();
|
|
|
|
_(
|
|
"Test fourth error still calls scheduleAtInterval even if enforceBackoff was reset"
|
|
);
|
|
await Service.sync();
|
|
maxInterval = scheduler._syncErrors * (2 * MINIMUM_BACKOFF_INTERVAL);
|
|
Assert.ok(scheduler.nextSync <= Date.now() + maxInterval);
|
|
Assert.ok(scheduler.syncTimer.delay <= maxInterval);
|
|
Assert.equal(scheduler._syncErrors, 4);
|
|
Assert.ok(Status.enforceBackoff);
|
|
scheduler.syncTimer.clear();
|
|
|
|
_("Arrange for a successful sync to reset the scheduler error count");
|
|
let promiseObserved = promiseOneObserver("weave:service:sync:finish");
|
|
Svc.PrefBranch.setStringPref("firstSync", "wipeRemote");
|
|
scheduler.scheduleNextSync(-1);
|
|
await promiseObserved;
|
|
await cleanUpAndGo(server);
|
|
});
|
|
|
|
add_task(async function test_client_sync_finish_updateClientMode() {
|
|
enableValidationPrefs();
|
|
|
|
let server = await sync_httpd_setup();
|
|
await setUp(server);
|
|
|
|
// Confirm defaults.
|
|
Assert.equal(scheduler.syncThreshold, SINGLE_USER_THRESHOLD);
|
|
Assert.equal(scheduler.syncInterval, scheduler.singleDeviceInterval);
|
|
Assert.ok(!scheduler.idle);
|
|
|
|
// Trigger a change in interval & threshold by adding a client.
|
|
await clientsEngine._store.create({
|
|
id: "foo",
|
|
cleartext: { os: "mobile", version: "0.01", type: "desktop" },
|
|
});
|
|
Assert.equal(false, scheduler.numClients > 1);
|
|
scheduler.updateClientMode();
|
|
await Service.sync();
|
|
|
|
Assert.equal(scheduler.syncThreshold, MULTI_DEVICE_THRESHOLD);
|
|
Assert.equal(scheduler.syncInterval, scheduler.activeInterval);
|
|
Assert.ok(scheduler.numClients > 1);
|
|
Assert.ok(!scheduler.idle);
|
|
|
|
// Resets the number of clients to 0.
|
|
await clientsEngine.resetClient();
|
|
// Also re-init the server, or we suck our "foo" client back down.
|
|
await setUp(server);
|
|
|
|
await Service.sync();
|
|
|
|
// Goes back to single user if # clients is 1.
|
|
Assert.equal(scheduler.numClients, 1);
|
|
Assert.equal(scheduler.syncThreshold, SINGLE_USER_THRESHOLD);
|
|
Assert.equal(scheduler.syncInterval, scheduler.singleDeviceInterval);
|
|
Assert.equal(false, scheduler.numClients > 1);
|
|
Assert.ok(!scheduler.idle);
|
|
|
|
await cleanUpAndGo(server);
|
|
});
|
|
|
|
add_task(async function test_autoconnect_nextSync_past() {
|
|
enableValidationPrefs();
|
|
|
|
let promiseObserved = promiseOneObserver("weave:service:sync:finish");
|
|
// nextSync will be 0 by default, so it's way in the past.
|
|
|
|
let server = await sync_httpd_setup();
|
|
await setUp(server);
|
|
|
|
scheduler.autoConnect();
|
|
await promiseObserved;
|
|
await cleanUpAndGo(server);
|
|
});
|
|
|
|
add_task(async function test_autoconnect_nextSync_future() {
|
|
enableValidationPrefs();
|
|
|
|
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);
|
|
|
|
await configureIdentity({ username: "johndoe@mozilla.com" });
|
|
scheduler.autoConnect();
|
|
await promiseZeroTimer();
|
|
|
|
Assert.equal(scheduler.nextSync, expectedSync);
|
|
Assert.ok(scheduler.syncTimer.delay >= expectedInterval);
|
|
|
|
Svc.Obs.remove("weave:service:login:start", onLoginStart);
|
|
await cleanUpAndGo();
|
|
});
|
|
|
|
add_task(async function test_autoconnect_mp_locked() {
|
|
let server = await sync_httpd_setup();
|
|
await setUp(server);
|
|
|
|
// Pretend user did not unlock master password.
|
|
let origLocked = Utils.mpLocked;
|
|
Utils.mpLocked = () => true;
|
|
|
|
let origEnsureMPUnlocked = Utils.ensureMPUnlocked;
|
|
Utils.ensureMPUnlocked = () => {
|
|
_("Faking Master Password entry cancelation.");
|
|
return false;
|
|
};
|
|
let origFxA = Service.identity._fxaService;
|
|
Service.identity._fxaService = new FxAccounts({
|
|
currentAccountState: {
|
|
getUserAccountData(...args) {
|
|
return origFxA._internal.currentAccountState.getUserAccountData(
|
|
...args
|
|
);
|
|
},
|
|
},
|
|
keys: {
|
|
canGetKeyForScope() {
|
|
return false;
|
|
},
|
|
},
|
|
});
|
|
// A locked master password will still trigger a sync, but then we'll hit
|
|
// MASTER_PASSWORD_LOCKED and hence MASTER_PASSWORD_LOCKED_RETRY_INTERVAL.
|
|
let promiseObserved = promiseOneObserver("weave:service:login:error");
|
|
|
|
scheduler.autoConnect();
|
|
await promiseObserved;
|
|
|
|
await Async.promiseYield();
|
|
|
|
Assert.equal(Status.login, MASTER_PASSWORD_LOCKED);
|
|
|
|
Utils.mpLocked = origLocked;
|
|
Utils.ensureMPUnlocked = origEnsureMPUnlocked;
|
|
Service.identity._fxaService = origFxA;
|
|
|
|
await cleanUpAndGo(server);
|
|
});
|
|
|
|
add_task(async function test_no_autoconnect_during_wizard() {
|
|
let server = await sync_httpd_setup();
|
|
await setUp(server);
|
|
|
|
// Simulate the Sync setup wizard.
|
|
Svc.PrefBranch.setStringPref("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);
|
|
|
|
scheduler.autoConnect(0);
|
|
await promiseZeroTimer();
|
|
Svc.Obs.remove("weave:service:login:start", onLoginStart);
|
|
await cleanUpAndGo(server);
|
|
});
|
|
|
|
add_task(async function test_no_autoconnect_status_not_ok() {
|
|
let server = await sync_httpd_setup();
|
|
Status.__authManager = Service.identity = new SyncAuthManager();
|
|
|
|
// 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);
|
|
|
|
scheduler.autoConnect();
|
|
await promiseZeroTimer();
|
|
Svc.Obs.remove("weave:service:login:start", onLoginStart);
|
|
|
|
Assert.equal(Status.service, CLIENT_NOT_CONFIGURED);
|
|
Assert.equal(Status.login, LOGIN_FAILED_NO_USERNAME);
|
|
|
|
await cleanUpAndGo(server);
|
|
});
|
|
|
|
add_task(async function test_idle_adjustSyncInterval() {
|
|
// Confirm defaults.
|
|
Assert.equal(scheduler.idle, false);
|
|
|
|
// Single device: nothing changes.
|
|
scheduler.observe(
|
|
null,
|
|
"idle",
|
|
Svc.PrefBranch.getIntPref("scheduler.idleTime")
|
|
);
|
|
Assert.equal(scheduler.idle, true);
|
|
Assert.equal(scheduler.syncInterval, scheduler.singleDeviceInterval);
|
|
|
|
// Multiple devices: switch to idle interval.
|
|
scheduler.idle = false;
|
|
Svc.PrefBranch.setIntPref("clients.devices.desktop", 1);
|
|
Svc.PrefBranch.setIntPref("clients.devices.mobile", 1);
|
|
scheduler.updateClientMode();
|
|
scheduler.observe(
|
|
null,
|
|
"idle",
|
|
Svc.PrefBranch.getIntPref("scheduler.idleTime")
|
|
);
|
|
Assert.equal(scheduler.idle, true);
|
|
Assert.equal(scheduler.syncInterval, scheduler.idleInterval);
|
|
|
|
await cleanUpAndGo();
|
|
});
|
|
|
|
add_task(async function test_back_triggersSync() {
|
|
// Confirm defaults.
|
|
Assert.ok(!scheduler.idle);
|
|
Assert.equal(Status.backoffInterval, 0);
|
|
|
|
// Set up: Define 2 clients and put the system in idle.
|
|
Svc.PrefBranch.setIntPref("clients.devices.desktop", 1);
|
|
Svc.PrefBranch.setIntPref("clients.devices.mobile", 1);
|
|
scheduler.observe(
|
|
null,
|
|
"idle",
|
|
Svc.PrefBranch.getIntPref("scheduler.idleTime")
|
|
);
|
|
Assert.ok(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.
|
|
let promiseObserved = promiseOneObserver("weave:service:login:error");
|
|
|
|
// Send an 'active' event to trigger sync soonish.
|
|
scheduler.observe(
|
|
null,
|
|
"active",
|
|
Svc.PrefBranch.getIntPref("scheduler.idleTime")
|
|
);
|
|
await promiseObserved;
|
|
await cleanUpAndGo();
|
|
});
|
|
|
|
add_task(async function test_active_triggersSync_observesBackoff() {
|
|
// Confirm defaults.
|
|
Assert.ok(!scheduler.idle);
|
|
|
|
// Set up: Set backoff, define 2 clients and put the system in idle.
|
|
const BACKOFF = 7337;
|
|
Status.backoffInterval = scheduler.idleInterval + BACKOFF;
|
|
Svc.PrefBranch.setIntPref("clients.devices.desktop", 1);
|
|
Svc.PrefBranch.setIntPref("clients.devices.mobile", 1);
|
|
scheduler.observe(
|
|
null,
|
|
"idle",
|
|
Svc.PrefBranch.getIntPref("scheduler.idleTime")
|
|
);
|
|
Assert.equal(scheduler.idle, true);
|
|
|
|
function onLoginStart() {
|
|
do_throw("Shouldn't have kicked off a sync!");
|
|
}
|
|
Svc.Obs.add("weave:service:login:start", onLoginStart);
|
|
|
|
let promiseTimer = promiseNamedTimer(
|
|
IDLE_OBSERVER_BACK_DELAY * 1.5,
|
|
{},
|
|
"timer"
|
|
);
|
|
|
|
// Send an 'active' event to try to trigger sync soonish.
|
|
scheduler.observe(
|
|
null,
|
|
"active",
|
|
Svc.PrefBranch.getIntPref("scheduler.idleTime")
|
|
);
|
|
await promiseTimer;
|
|
Svc.Obs.remove("weave:service:login:start", onLoginStart);
|
|
|
|
Assert.ok(scheduler.nextSync <= Date.now() + Status.backoffInterval);
|
|
Assert.equal(scheduler.syncTimer.delay, Status.backoffInterval);
|
|
|
|
await cleanUpAndGo();
|
|
});
|
|
|
|
add_task(async function test_back_debouncing() {
|
|
_(
|
|
"Ensure spurious back-then-idle events, as observed on OS X, don't trigger a sync."
|
|
);
|
|
|
|
// Confirm defaults.
|
|
Assert.equal(scheduler.idle, false);
|
|
|
|
// Set up: Define 2 clients and put the system in idle.
|
|
Svc.PrefBranch.setIntPref("clients.devices.desktop", 1);
|
|
Svc.PrefBranch.setIntPref("clients.devices.mobile", 1);
|
|
scheduler.observe(
|
|
null,
|
|
"idle",
|
|
Svc.PrefBranch.getIntPref("scheduler.idleTime")
|
|
);
|
|
Assert.equal(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,
|
|
"active",
|
|
Svc.PrefBranch.getIntPref("scheduler.idleTime")
|
|
);
|
|
scheduler.observe(
|
|
null,
|
|
"idle",
|
|
Svc.PrefBranch.getIntPref("scheduler.idleTime")
|
|
);
|
|
|
|
await promiseNamedTimer(IDLE_OBSERVER_BACK_DELAY * 1.5, {}, "timer");
|
|
Svc.Obs.remove("weave:service:login:start", onLoginStart);
|
|
await cleanUpAndGo();
|
|
});
|
|
|
|
add_task(async function test_no_sync_node() {
|
|
enableValidationPrefs();
|
|
|
|
// Test when Status.sync == NO_SYNC_NODE_FOUND
|
|
// it is not overwritten on sync:finish
|
|
let server = await sync_httpd_setup();
|
|
await setUp(server);
|
|
|
|
let oldfc = Service.identity._findCluster;
|
|
Service.identity._findCluster = () => null;
|
|
Service.clusterURL = "";
|
|
try {
|
|
await Service.sync();
|
|
Assert.equal(Status.sync, NO_SYNC_NODE_FOUND);
|
|
Assert.equal(scheduler.syncTimer.delay, NO_SYNC_NODE_INTERVAL);
|
|
|
|
await cleanUpAndGo(server);
|
|
} finally {
|
|
Service.identity._findCluster = oldfc;
|
|
}
|
|
});
|
|
|
|
add_task(async function test_sync_failed_partial_500s() {
|
|
enableValidationPrefs();
|
|
|
|
_("Test a 5xx status calls handleSyncError.");
|
|
scheduler._syncErrors = MAX_ERROR_COUNT_BEFORE_BACKOFF;
|
|
let server = await sync_httpd_setup();
|
|
|
|
let engine = Service.engineManager.get("catapult");
|
|
engine.enabled = true;
|
|
engine.exception = { status: 500 };
|
|
|
|
Assert.equal(Status.sync, SYNC_SUCCEEDED);
|
|
|
|
Assert.ok(await setUp(server));
|
|
|
|
await Service.sync();
|
|
|
|
Assert.equal(Status.service, SYNC_FAILED_PARTIAL);
|
|
|
|
let maxInterval = scheduler._syncErrors * (2 * MINIMUM_BACKOFF_INTERVAL);
|
|
Assert.equal(Status.backoffInterval, 0);
|
|
Assert.ok(Status.enforceBackoff);
|
|
Assert.equal(scheduler._syncErrors, 4);
|
|
Assert.ok(scheduler.nextSync <= Date.now() + maxInterval);
|
|
Assert.ok(scheduler.syncTimer.delay <= maxInterval);
|
|
|
|
await cleanUpAndGo(server);
|
|
});
|
|
|
|
add_task(async function test_sync_failed_partial_noresync() {
|
|
enableValidationPrefs();
|
|
let server = await sync_httpd_setup();
|
|
|
|
let engine = Service.engineManager.get("catapult");
|
|
engine.enabled = true;
|
|
engine.exception = "Bad news";
|
|
engine._tracker._score = MULTI_DEVICE_THRESHOLD + 1;
|
|
|
|
Assert.equal(Status.sync, SYNC_SUCCEEDED);
|
|
|
|
Assert.ok(await setUp(server));
|
|
|
|
let resyncDoneObserver = promiseOneObserver("weave:service:resyncs-finished");
|
|
|
|
await Service.sync();
|
|
|
|
Assert.equal(Status.service, SYNC_FAILED_PARTIAL);
|
|
|
|
function onSyncStarted() {
|
|
do_throw("Should not start resync when previous sync failed");
|
|
}
|
|
|
|
Svc.Obs.add("weave:service:sync:start", onSyncStarted);
|
|
await resyncDoneObserver;
|
|
|
|
Svc.Obs.remove("weave:service:sync:start", onSyncStarted);
|
|
engine._tracker._store = 0;
|
|
await cleanUpAndGo(server);
|
|
});
|
|
|
|
add_task(async function test_sync_failed_partial_400s() {
|
|
enableValidationPrefs();
|
|
|
|
_("Test a non-5xx status doesn't call handleSyncError.");
|
|
scheduler._syncErrors = MAX_ERROR_COUNT_BEFORE_BACKOFF;
|
|
let server = await sync_httpd_setup();
|
|
|
|
let engine = Service.engineManager.get("catapult");
|
|
engine.enabled = true;
|
|
engine.exception = { status: 400 };
|
|
|
|
// Have multiple devices for an active interval.
|
|
await clientsEngine._store.create({
|
|
id: "foo",
|
|
cleartext: { os: "mobile", version: "0.01", type: "desktop" },
|
|
});
|
|
|
|
Assert.equal(Status.sync, SYNC_SUCCEEDED);
|
|
|
|
Assert.ok(await setUp(server));
|
|
|
|
await Service.sync();
|
|
|
|
Assert.equal(Status.service, SYNC_FAILED_PARTIAL);
|
|
Assert.equal(scheduler.syncInterval, scheduler.activeInterval);
|
|
|
|
Assert.equal(Status.backoffInterval, 0);
|
|
Assert.ok(!Status.enforceBackoff);
|
|
Assert.equal(scheduler._syncErrors, 0);
|
|
Assert.ok(scheduler.nextSync <= Date.now() + scheduler.activeInterval);
|
|
Assert.ok(scheduler.syncTimer.delay <= scheduler.activeInterval);
|
|
|
|
await cleanUpAndGo(server);
|
|
});
|
|
|
|
add_task(async function test_sync_X_Weave_Backoff() {
|
|
enableValidationPrefs();
|
|
|
|
let server = await sync_httpd_setup();
|
|
await setUp(server);
|
|
|
|
// 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@mozilla.com/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.
|
|
await clientsEngine._store.create({
|
|
id: "foo",
|
|
cleartext: { os: "mobile", version: "0.01", type: "desktop" },
|
|
});
|
|
let rec = await clientsEngine._store.createRecord("foo", "clients");
|
|
await rec.encrypt(Service.collectionKeys.keyForCollection("clients"));
|
|
await rec.upload(Service.resource(clientsEngine.engineURL + rec.id));
|
|
|
|
// Sync once to log in and get everything set up. Let's verify our initial
|
|
// values.
|
|
await Service.sync();
|
|
Assert.equal(Status.backoffInterval, 0);
|
|
Assert.equal(Status.minimumNextSync, 0);
|
|
Assert.equal(scheduler.syncInterval, scheduler.activeInterval);
|
|
Assert.ok(scheduler.nextSync <= Date.now() + scheduler.syncInterval);
|
|
// Sanity check that we picked the right value for BACKOFF:
|
|
Assert.ok(scheduler.syncInterval < BACKOFF * 1000);
|
|
|
|
// Turn on server maintenance and sync again.
|
|
serverBackoff = true;
|
|
await Service.sync();
|
|
|
|
Assert.ok(Status.backoffInterval >= BACKOFF * 1000);
|
|
// Allowing 20 seconds worth of of leeway between when Status.minimumNextSync
|
|
// was set and when this line gets executed.
|
|
let minimumExpectedDelay = (BACKOFF - 20) * 1000;
|
|
Assert.ok(Status.minimumNextSync >= Date.now() + minimumExpectedDelay);
|
|
|
|
// Verify that the next sync is actually going to wait that long.
|
|
Assert.ok(scheduler.nextSync >= Date.now() + minimumExpectedDelay);
|
|
Assert.ok(scheduler.syncTimer.delay >= minimumExpectedDelay);
|
|
|
|
await cleanUpAndGo(server);
|
|
});
|
|
|
|
add_task(async function test_sync_503_Retry_After() {
|
|
enableValidationPrefs();
|
|
|
|
let server = await sync_httpd_setup();
|
|
await setUp(server);
|
|
|
|
// 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@mozilla.com/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.
|
|
await clientsEngine._store.create({
|
|
id: "foo",
|
|
cleartext: { os: "mobile", version: "0.01", type: "desktop" },
|
|
});
|
|
let rec = await clientsEngine._store.createRecord("foo", "clients");
|
|
await rec.encrypt(Service.collectionKeys.keyForCollection("clients"));
|
|
await rec.upload(Service.resource(clientsEngine.engineURL + rec.id));
|
|
|
|
// Sync once to log in and get everything set up. Let's verify our initial
|
|
// values.
|
|
await Service.sync();
|
|
Assert.ok(!Status.enforceBackoff);
|
|
Assert.equal(Status.backoffInterval, 0);
|
|
Assert.equal(Status.minimumNextSync, 0);
|
|
Assert.equal(scheduler.syncInterval, scheduler.activeInterval);
|
|
Assert.ok(scheduler.nextSync <= Date.now() + scheduler.syncInterval);
|
|
// Sanity check that we picked the right value for BACKOFF:
|
|
Assert.ok(scheduler.syncInterval < BACKOFF * 1000);
|
|
|
|
// Turn on server maintenance and sync again.
|
|
serverMaintenance = true;
|
|
await Service.sync();
|
|
|
|
Assert.ok(Status.enforceBackoff);
|
|
Assert.ok(Status.backoffInterval >= BACKOFF * 1000);
|
|
// Allowing 3 seconds worth of of leeway between when Status.minimumNextSync
|
|
// was set and when this line gets executed.
|
|
let minimumExpectedDelay = (BACKOFF - 3) * 1000;
|
|
Assert.ok(Status.minimumNextSync >= Date.now() + minimumExpectedDelay);
|
|
|
|
// Verify that the next sync is actually going to wait that long.
|
|
Assert.ok(scheduler.nextSync >= Date.now() + minimumExpectedDelay);
|
|
Assert.ok(scheduler.syncTimer.delay >= minimumExpectedDelay);
|
|
|
|
await cleanUpAndGo(server);
|
|
});
|
|
|
|
add_task(async function test_loginError_recoverable_reschedules() {
|
|
_("Verify that a recoverable login error schedules a new sync.");
|
|
await configureIdentity({ username: "johndoe@mozilla.com" });
|
|
Service.clusterURL = "http://localhost:1234/";
|
|
Status.resetSync(); // reset Status.login
|
|
|
|
let promiseObserved = promiseOneObserver("weave:service:login:error");
|
|
|
|
// 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.
|
|
Assert.equal(scheduler.syncTimer, null);
|
|
Assert.equal(Status.checkSetup(), STATUS_OK);
|
|
Assert.equal(Status.login, LOGIN_SUCCEEDED);
|
|
|
|
scheduler.scheduleNextSync(0);
|
|
await promiseObserved;
|
|
await Async.promiseYield();
|
|
|
|
Assert.equal(Status.login, LOGIN_FAILED_NETWORK_ERROR);
|
|
|
|
let expectedNextSync = Date.now() + scheduler.syncInterval;
|
|
Assert.ok(scheduler.nextSync > Date.now());
|
|
Assert.ok(scheduler.nextSync <= expectedNextSync);
|
|
Assert.ok(scheduler.syncTimer.delay > 0);
|
|
Assert.ok(scheduler.syncTimer.delay <= scheduler.syncInterval);
|
|
|
|
Svc.Obs.remove("weave:service:sync:start", onSyncStart);
|
|
await cleanUpAndGo();
|
|
});
|
|
|
|
add_task(async function test_loginError_fatal_clearsTriggers() {
|
|
_("Verify that a fatal login error clears sync triggers.");
|
|
await configureIdentity({ username: "johndoe@mozilla.com" });
|
|
|
|
let server = httpd_setup({
|
|
"/1.1/johndoe@mozilla.com/info/collections": httpd_handler(
|
|
401,
|
|
"Unauthorized"
|
|
),
|
|
});
|
|
|
|
Service.clusterURL = server.baseURI + "/";
|
|
Status.resetSync(); // reset Status.login
|
|
|
|
let promiseObserved = promiseOneObserver("weave:service:login:error");
|
|
|
|
// Sanity check.
|
|
Assert.equal(scheduler.nextSync, 0);
|
|
Assert.equal(scheduler.syncTimer, null);
|
|
Assert.equal(Status.checkSetup(), STATUS_OK);
|
|
Assert.equal(Status.login, LOGIN_SUCCEEDED);
|
|
|
|
scheduler.scheduleNextSync(0);
|
|
await promiseObserved;
|
|
await Async.promiseYield();
|
|
|
|
// For the FxA identity, a 401 on info/collections means a transient
|
|
// error, probably due to an inability to fetch a token.
|
|
Assert.equal(Status.login, LOGIN_FAILED_NETWORK_ERROR);
|
|
// syncs should still be scheduled.
|
|
Assert.ok(scheduler.nextSync > Date.now());
|
|
Assert.ok(scheduler.syncTimer.delay > 0);
|
|
|
|
await cleanUpAndGo(server);
|
|
});
|
|
|
|
add_task(async 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.
|
|
Assert.ok(!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,
|
|
});
|
|
|
|
await Async.promiseYield();
|
|
scheduler.adjustSyncInterval();
|
|
Assert.ok(!scheduler.hasIncomingItems);
|
|
Assert.equal(scheduler.syncInterval, scheduler.singleDeviceInterval);
|
|
});
|
|
|
|
add_task(async function test_link_status_change() {
|
|
_("Check that we only attempt to sync when link status is up");
|
|
try {
|
|
sinon.spy(scheduler, "scheduleNextSync");
|
|
|
|
Svc.Obs.notify("network:link-status-changed", null, "down");
|
|
equal(scheduler.scheduleNextSync.callCount, 0);
|
|
|
|
Svc.Obs.notify("network:link-status-changed", null, "change");
|
|
equal(scheduler.scheduleNextSync.callCount, 0);
|
|
|
|
Svc.Obs.notify("network:link-status-changed", null, "up");
|
|
equal(scheduler.scheduleNextSync.callCount, 1);
|
|
|
|
Svc.Obs.notify("network:link-status-changed", null, "change");
|
|
equal(scheduler.scheduleNextSync.callCount, 1);
|
|
} finally {
|
|
scheduler.scheduleNextSync.restore();
|
|
}
|
|
});
|