summaryrefslogtreecommitdiffstats
path: root/services/sync/tests/unit/test_service_sync_remoteSetup.js
diff options
context:
space:
mode:
Diffstat (limited to 'services/sync/tests/unit/test_service_sync_remoteSetup.js')
-rw-r--r--services/sync/tests/unit/test_service_sync_remoteSetup.js239
1 files changed, 239 insertions, 0 deletions
diff --git a/services/sync/tests/unit/test_service_sync_remoteSetup.js b/services/sync/tests/unit/test_service_sync_remoteSetup.js
new file mode 100644
index 0000000000..ec9fcce20c
--- /dev/null
+++ b/services/sync/tests/unit/test_service_sync_remoteSetup.js
@@ -0,0 +1,239 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const { Service } = ChromeUtils.importESModule(
+ "resource://services-sync/service.sys.mjs"
+);
+
+// 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 {
+ Svc.Prefs.resetBranch("");
+ server.stop(do_test_finished);
+ }
+});