diff options
Diffstat (limited to 'services/sync/tests/unit/test_service_sync_remoteSetup.js')
-rw-r--r-- | services/sync/tests/unit/test_service_sync_remoteSetup.js | 237 |
1 files changed, 237 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..f69f70dcce --- /dev/null +++ b/services/sync/tests/unit/test_service_sync_remoteSetup.js @@ -0,0 +1,237 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +const { Service } = ChromeUtils.import("resource://services-sync/service.js"); + +// 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); + } +}); |