Source code
Revision control
Copy as Markdown
Other Tools
Test Info:
- Manifest: services/sync/tests/unit/xpcshell.toml
/* Any copyright is dedicated to the Public Domain.
const { Service } = ChromeUtils.importESModule(
);
// This sucks, but this test fails if this engine is enabled, due to dumb
// things that aren't related to this engine. In short:
// * Because the addon manager isn't initialized, the addons engine fails to
// initialize. So we end up writing a meta/global with `extension-storage`
// but not addons.
// * After we sync, we discover 'addons' is locally enabled, but because it's
// not in m/g, we decide it's been remotely declined (and it decides this
// without even considering `declined`). So we disable 'addons'.
// * Disabling 'addons' means 'extension-storage' is disabled - but because
// that *is* in meta/global we re-update meta/global to remove it.
// * This test fails due to the extra, unexpected update of m/g.
//
// Another option would be to ensure the addons manager is initialized, but
// that's a larger patch and still isn't strictly relevant to what's being
// tested here, so...
Services.prefs.setBoolPref(
"services.sync.engine.extension-storage.force",
false
);
add_task(async function run_test() {
enableValidationPrefs();
validate_all_future_pings();
Log.repository.rootLogger.addAppender(new Log.DumpAppender());
let clients = new ServerCollection();
let meta_global = new ServerWBO("global");
let collectionsHelper = track_collections_helper();
let upd = collectionsHelper.with_updated_collection;
let collections = collectionsHelper.collections;
function wasCalledHandler(wbo) {
let handler = wbo.handler();
return function () {
wbo.wasCalled = true;
handler.apply(this, arguments);
};
}
let keysWBO = new ServerWBO("keys");
let cryptoColl = new ServerCollection({ keys: keysWBO });
let metaColl = new ServerCollection({ global: meta_global });
do_test_pending();
/**
* Handle the bulk DELETE request sent by wipeServer.
*/
function storageHandler(request, response) {
Assert.equal("DELETE", request.method);
Assert.ok(request.hasHeader("X-Confirm-Delete"));
_("Wiping out all collections.");
cryptoColl.delete({});
clients.delete({});
metaColl.delete({});
let ts = new_timestamp();
collectionsHelper.update_collection("crypto", ts);
collectionsHelper.update_collection("clients", ts);
collectionsHelper.update_collection("meta", ts);
return_timestamp(request, response, ts);
}
const GLOBAL_PATH = "/1.1/johndoe/storage/meta/global";
let handlers = {
"/1.1/johndoe/storage": storageHandler,
"/1.1/johndoe/storage/crypto/keys": upd("crypto", keysWBO.handler()),
"/1.1/johndoe/storage/crypto": upd("crypto", cryptoColl.handler()),
"/1.1/johndoe/storage/clients": upd("clients", clients.handler()),
"/1.1/johndoe/storage/meta": upd("meta", wasCalledHandler(metaColl)),
"/1.1/johndoe/storage/meta/global": upd(
"meta",
wasCalledHandler(meta_global)
),
"/1.1/johndoe/info/collections": collectionsHelper.handler,
};
function mockHandler(path, mock) {
server.registerPathHandler(path, mock(handlers[path]));
return {
restore() {
server.registerPathHandler(path, handlers[path]);
},
};
}
let server = httpd_setup(handlers);
try {
_("Checking Status.sync with no credentials.");
await Service.verifyAndFetchSymmetricKeys();
Assert.equal(Service.status.sync, CREDENTIALS_CHANGED);
Assert.equal(Service.status.login, LOGIN_FAILED_NO_PASSPHRASE);
await configureIdentity({ username: "johndoe" }, server);
await Service.login();
_("Checking that remoteSetup returns true when credentials have changed.");
(await Service.recordManager.get(Service.metaURL)).payload.syncID =
"foobar";
Assert.ok(await Service._remoteSetup());
let returnStatusCode = (method, code) => oldMethod => (req, res) => {
if (req.method === method) {
res.setStatusLine(req.httpVersion, code, "");
} else {
oldMethod(req, res);
}
};
let mock = mockHandler(GLOBAL_PATH, returnStatusCode("GET", 401));
Service.recordManager.del(Service.metaURL);
_(
"Checking that remoteSetup returns false on 401 on first get /meta/global."
);
Assert.equal(false, await Service._remoteSetup());
mock.restore();
await Service.login();
mock = mockHandler(GLOBAL_PATH, returnStatusCode("GET", 503));
Service.recordManager.del(Service.metaURL);
_(
"Checking that remoteSetup returns false on 503 on first get /meta/global."
);
Assert.equal(false, await Service._remoteSetup());
Assert.equal(Service.status.sync, METARECORD_DOWNLOAD_FAIL);
mock.restore();
await Service.login();
mock = mockHandler(GLOBAL_PATH, returnStatusCode("GET", 404));
Service.recordManager.del(Service.metaURL);
_("Checking that remoteSetup recovers on 404 on first get /meta/global.");
Assert.ok(await Service._remoteSetup());
mock.restore();
let makeOutdatedMeta = async () => {
Service.metaModified = 0;
let infoResponse = await Service._fetchInfo();
return {
status: infoResponse.status,
obj: {
crypto: infoResponse.obj.crypto,
clients: infoResponse.obj.clients,
meta: 1,
},
};
};
_(
"Checking that remoteSetup recovers on 404 on get /meta/global after clear cached one."
);
mock = mockHandler(GLOBAL_PATH, returnStatusCode("GET", 404));
Service.recordManager.set(Service.metaURL, { isNew: false });
Assert.ok(await Service._remoteSetup(await makeOutdatedMeta()));
mock.restore();
_(
"Checking that remoteSetup returns false on 503 on get /meta/global after clear cached one."
);
mock = mockHandler(GLOBAL_PATH, returnStatusCode("GET", 503));
Service.status.sync = "";
Service.recordManager.set(Service.metaURL, { isNew: false });
Assert.equal(false, await Service._remoteSetup(await makeOutdatedMeta()));
Assert.equal(Service.status.sync, "");
mock.restore();
metaColl.delete({});
_("Do an initial sync.");
await Service.sync();
_("Checking that remoteSetup returns true.");
Assert.ok(await Service._remoteSetup());
_("Verify that the meta record was uploaded.");
Assert.equal(meta_global.data.syncID, Service.syncID);
Assert.equal(meta_global.data.storageVersion, STORAGE_VERSION);
Assert.equal(
meta_global.data.engines.clients.version,
Service.clientsEngine.version
);
Assert.equal(
meta_global.data.engines.clients.syncID,
await Service.clientsEngine.getSyncID()
);
_(
"Set the collection info hash so that sync() will remember the modified times for future runs."
);
let lastSync = await Service.clientsEngine.getLastSync();
collections.meta = lastSync;
collections.clients = lastSync;
await Service.sync();
_("Sync again and verify that meta/global wasn't downloaded again");
meta_global.wasCalled = false;
await Service.sync();
Assert.ok(!meta_global.wasCalled);
_(
"Fake modified records. This will cause a redownload, but not reupload since it hasn't changed."
);
collections.meta += 42;
meta_global.wasCalled = false;
let metaModified = meta_global.modified;
await Service.sync();
Assert.ok(meta_global.wasCalled);
Assert.equal(metaModified, meta_global.modified);
// Try to screw up HMAC calculation.
// Re-encrypt keys with a new random keybundle, and upload them to the
// server, just as might happen with a second client.
_("Attempting to screw up HMAC by re-encrypting keys.");
let keys = Service.collectionKeys.asWBO();
let b = new BulkKeyBundle("hmacerror");
await b.generateRandom();
collections.crypto = keys.modified = 100 + Date.now() / 1000; // Future modification time.
await keys.encrypt(b);
await keys.upload(Service.resource(Service.cryptoKeysURL));
Assert.equal(false, await Service.verifyAndFetchSymmetricKeys());
Assert.equal(Service.status.login, LOGIN_FAILED_INVALID_PASSPHRASE);
} finally {
for (const pref of Svc.PrefBranch.getChildList("")) {
Svc.PrefBranch.clearUserPref(pref);
}
server.stop(do_test_finished);
}
});