From 0c47c83e1b3b7d95681a43fbb0de0e17b2cd5b25 Mon Sep 17 00:00:00 2001 From: wolfbeast Date: Sat, 6 Oct 2018 06:57:51 +0200 Subject: Import Tycho weave client --- services/sync/tests/unit/fake_login_manager.js | 2 +- services/sync/tests/unit/head_appinfo.js | 65 +- .../sync/tests/unit/head_errorhandler_common.js | 112 -- services/sync/tests/unit/head_helpers.js | 242 +-- services/sync/tests/unit/head_http_server.js | 112 +- services/sync/tests/unit/prefs_test_prefs_store.js | 25 - services/sync/tests/unit/sync_ping_schema.json | 198 -- services/sync/tests/unit/systemaddon-search.xml | 27 - services/sync/tests/unit/test_addon_utils.js | 53 +- services/sync/tests/unit/test_addons_engine.js | 17 +- services/sync/tests/unit/test_addons_reconciler.js | 2 +- services/sync/tests/unit/test_addons_store.js | 127 +- services/sync/tests/unit/test_addons_tracker.js | 9 +- services/sync/tests/unit/test_block_sync.js | 37 + services/sync/tests/unit/test_bookmark_duping.js | 644 ------- services/sync/tests/unit/test_bookmark_engine.js | 321 +--- services/sync/tests/unit/test_bookmark_invalid.js | 63 - .../test_bookmark_legacy_microsummaries_support.js | 4 +- .../sync/tests/unit/test_bookmark_livemarks.js | 19 +- services/sync/tests/unit/test_bookmark_order.js | 519 +----- .../unit/test_bookmark_places_query_rewriting.js | 55 +- .../tests/unit/test_bookmark_smart_bookmarks.js | 12 +- services/sync/tests/unit/test_bookmark_store.js | 128 +- services/sync/tests/unit/test_bookmark_tracker.js | 1499 +--------------- .../sync/tests/unit/test_bookmark_validator.js | 347 ---- .../sync/tests/unit/test_browserid_identity.js | 260 +-- services/sync/tests/unit/test_clients_engine.js | 1027 +---------- .../sync/tests/unit/test_collection_getBatched.js | 195 -- .../sync/tests/unit/test_collections_recovery.js | 13 +- services/sync/tests/unit/test_corrupt_keys.js | 16 +- services/sync/tests/unit/test_engine.js | 24 +- services/sync/tests/unit/test_errorhandler.js | 1893 ++++++++++++++++++++ services/sync/tests/unit/test_errorhandler_1.js | 913 ---------- services/sync/tests/unit/test_errorhandler_2.js | 1012 ----------- services/sync/tests/unit/test_errorhandler_eol.js | 8 +- .../sync/tests/unit/test_errorhandler_filelog.js | 83 +- .../test_errorhandler_sync_checkServerError.js | 21 +- .../tests/unit/test_extension_storage_crypto.js | 93 - .../tests/unit/test_extension_storage_engine.js | 62 - .../tests/unit/test_extension_storage_tracker.js | 38 - services/sync/tests/unit/test_forms_tracker.js | 14 - services/sync/tests/unit/test_fxa_migration.js | 279 +++ .../sync/tests/unit/test_fxa_migration_sentinel.js | 150 ++ .../sync/tests/unit/test_fxa_node_reassignment.js | 689 ++++--- .../sync/tests/unit/test_fxa_service_cluster.js | 136 +- services/sync/tests/unit/test_fxa_startOver.js | 126 +- services/sync/tests/unit/test_healthreport.js | 194 ++ .../sync/tests/unit/test_healthreport_migration.js | 155 ++ services/sync/tests/unit/test_history_store.js | 25 +- services/sync/tests/unit/test_history_tracker.js | 6 +- services/sync/tests/unit/test_hmac_error.js | 11 +- services/sync/tests/unit/test_identity_manager.js | 2 +- services/sync/tests/unit/test_interval_triggers.js | 21 +- services/sync/tests/unit/test_jpakeclient.js | 12 +- services/sync/tests/unit/test_keys.js | 2 +- services/sync/tests/unit/test_load_modules.js | 2 +- services/sync/tests/unit/test_node_reassignment.js | 23 +- services/sync/tests/unit/test_notifications.js | 32 + services/sync/tests/unit/test_password_store.js | 148 +- services/sync/tests/unit/test_password_tracker.js | 6 +- .../sync/tests/unit/test_password_validator.js | 158 -- services/sync/tests/unit/test_postqueue.js | 455 ----- services/sync/tests/unit/test_prefs_store.js | 85 +- services/sync/tests/unit/test_records_crypto.js | 28 +- services/sync/tests/unit/test_resource.js | 13 +- services/sync/tests/unit/test_resource_async.js | 14 +- services/sync/tests/unit/test_resource_header.js | 6 +- services/sync/tests/unit/test_resource_ua.js | 18 +- services/sync/tests/unit/test_score_triggers.js | 8 +- .../sync/tests/unit/test_service_attributes.js | 13 +- .../sync/tests/unit/test_service_detect_upgrade.js | 4 +- .../sync/tests/unit/test_service_getStorageInfo.js | 6 +- services/sync/tests/unit/test_service_login.js | 2 +- .../sync/tests/unit/test_service_passwordUTF8.js | 4 +- services/sync/tests/unit/test_service_startOver.js | 2 +- services/sync/tests/unit/test_service_startup.js | 11 +- .../sync/tests/unit/test_service_sync_locked.js | 13 +- .../tests/unit/test_service_sync_remoteSetup.js | 77 +- .../sync/tests/unit/test_service_sync_specified.js | 160 -- .../unit/test_service_sync_updateEnabledEngines.js | 9 +- .../sync/tests/unit/test_service_wipeServer.js | 14 +- services/sync/tests/unit/test_status.js | 6 +- services/sync/tests/unit/test_syncedtabs.js | 221 --- services/sync/tests/unit/test_syncengine.js | 10 +- services/sync/tests/unit/test_syncengine_sync.js | 100 +- services/sync/tests/unit/test_syncscheduler.js | 113 +- .../sync/tests/unit/test_syncstoragerequest.js | 5 - services/sync/tests/unit/test_tab_engine.js | 12 +- services/sync/tests/unit/test_tab_store.js | 50 +- services/sync/tests/unit/test_tab_tracker.js | 43 +- services/sync/tests/unit/test_telemetry.js | 564 ------ services/sync/tests/unit/test_utils_catch.js | 54 +- services/sync/tests/unit/test_utils_deferGetSet.js | 8 +- services/sync/tests/unit/test_utils_deriveKey.js | 2 +- services/sync/tests/unit/test_utils_lock.js | 28 +- services/sync/tests/unit/test_utils_notify.js | 24 +- .../tests/unit/test_warn_on_truncated_response.js | 4 +- services/sync/tests/unit/xpcshell.ini | 42 +- 98 files changed, 4148 insertions(+), 10568 deletions(-) delete mode 100644 services/sync/tests/unit/head_errorhandler_common.js delete mode 100644 services/sync/tests/unit/prefs_test_prefs_store.js delete mode 100644 services/sync/tests/unit/sync_ping_schema.json delete mode 100644 services/sync/tests/unit/systemaddon-search.xml create mode 100644 services/sync/tests/unit/test_block_sync.js delete mode 100644 services/sync/tests/unit/test_bookmark_duping.js delete mode 100644 services/sync/tests/unit/test_bookmark_invalid.js delete mode 100644 services/sync/tests/unit/test_bookmark_validator.js delete mode 100644 services/sync/tests/unit/test_collection_getBatched.js create mode 100644 services/sync/tests/unit/test_errorhandler.js delete mode 100644 services/sync/tests/unit/test_errorhandler_1.js delete mode 100644 services/sync/tests/unit/test_errorhandler_2.js delete mode 100644 services/sync/tests/unit/test_extension_storage_crypto.js delete mode 100644 services/sync/tests/unit/test_extension_storage_engine.js delete mode 100644 services/sync/tests/unit/test_extension_storage_tracker.js create mode 100644 services/sync/tests/unit/test_fxa_migration.js create mode 100644 services/sync/tests/unit/test_fxa_migration_sentinel.js create mode 100644 services/sync/tests/unit/test_healthreport.js create mode 100644 services/sync/tests/unit/test_healthreport_migration.js create mode 100644 services/sync/tests/unit/test_notifications.js delete mode 100644 services/sync/tests/unit/test_password_validator.js delete mode 100644 services/sync/tests/unit/test_postqueue.js delete mode 100644 services/sync/tests/unit/test_service_sync_specified.js delete mode 100644 services/sync/tests/unit/test_syncedtabs.js delete mode 100644 services/sync/tests/unit/test_telemetry.js (limited to 'services/sync/tests/unit') diff --git a/services/sync/tests/unit/fake_login_manager.js b/services/sync/tests/unit/fake_login_manager.js index 6f3148c45..32adcbcb5 100644 --- a/services/sync/tests/unit/fake_login_manager.js +++ b/services/sync/tests/unit/fake_login_manager.js @@ -4,7 +4,7 @@ Cu.import("resource://services-sync/util.js"); // Fake Sample Data // ---------------------------------------- -var fakeSampleLogins = [ +let fakeSampleLogins = [ // Fake nsILoginInfo object. {hostname: "www.boogle.com", formSubmitURL: "http://www.boogle.com/search", diff --git a/services/sync/tests/unit/head_appinfo.js b/services/sync/tests/unit/head_appinfo.js index d2a680df5..eea47905f 100644 --- a/services/sync/tests/unit/head_appinfo.js +++ b/services/sync/tests/unit/head_appinfo.js @@ -1,54 +1,65 @@ /* Any copyright is dedicated to the Public Domain. http://creativecommons.org/publicdomain/zero/1.0/ */ -var {classes: Cc, interfaces: Ci, results: Cr, utils: Cu} = Components; -Cu.import("resource://gre/modules/Services.jsm"); -Cu.import("resource://gre/modules/XPCOMUtils.jsm"); +const {classes: Cc, interfaces: Ci, results: Cr, utils: Cu} = Components; -var gSyncProfile; +let gSyncProfile; gSyncProfile = do_get_profile(); // Init FormHistoryStartup and pretend we opened a profile. -var fhs = Cc["@mozilla.org/satchel/form-history-startup;1"] +let fhs = Cc["@mozilla.org/satchel/form-history-startup;1"] .getService(Ci.nsIObserver); fhs.observe(null, "profile-after-change", null); -// An app is going to have some prefs set which xpcshell tests don't. -Services.prefs.setCharPref("identity.sync.tokenserver.uri", "http://token-server"); -// Set the validation prefs to attempt validation every time to avoid non-determinism. -Services.prefs.setIntPref("services.sync.validation.interval", 0); -Services.prefs.setIntPref("services.sync.validation.percentageChance", 100); -Services.prefs.setIntPref("services.sync.validation.maxRecords", -1); -Services.prefs.setBoolPref("services.sync.validation.enabled", true); +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); // Make sure to provide the right OS so crypto loads the right binaries -function getOS() { - switch (mozinfo.os) { - case "win": - return "WINNT"; - case "mac": - return "Darwin"; - default: - return "Linux"; - } -} +let OS = "XPCShell"; +if ("@mozilla.org/windows-registry-key;1" in Cc) + OS = "WINNT"; +else if ("nsILocalFileMac" in Ci) + OS = "Darwin"; +else + OS = "Linux"; -Cu.import("resource://testing-common/AppInfo.jsm", this); -updateAppInfo({ +let XULAppInfo = { + vendor: "Mozilla", name: "XPCShell", ID: "xpcshell@tests.mozilla.org", version: "1", + appBuildID: "20100621", platformVersion: "", - OS: getOS(), -}); + platformBuildID: "20100621", + inSafeMode: false, + logConsoleErrors: true, + OS: OS, + XPCOMABI: "noarch-spidermonkey", + QueryInterface: XPCOMUtils.generateQI([Ci.nsIXULAppInfo, Ci.nsIXULRuntime]), + invalidateCachesOnRestart: function invalidateCachesOnRestart() { } +}; + +let XULAppInfoFactory = { + createInstance: function (outer, iid) { + if (outer != null) + throw Cr.NS_ERROR_NO_AGGREGATION; + return XULAppInfo.QueryInterface(iid); + } +}; + +let registrar = Components.manager.QueryInterface(Ci.nsIComponentRegistrar); +registrar.registerFactory(Components.ID("{fbfae60b-64a4-44ef-a911-08ceb70b9f31}"), + "XULAppInfo", "@mozilla.org/xre/app-info;1", + XULAppInfoFactory); + // Register resource aliases. Normally done in SyncComponents.manifest. function addResourceAlias() { + Cu.import("resource://gre/modules/Services.jsm"); const resProt = Services.io.getProtocolHandler("resource") .QueryInterface(Ci.nsIResProtocolHandler); - for (let s of ["common", "sync", "crypto"]) { + for each (let s in ["common", "sync", "crypto"]) { let uri = Services.io.newURI("resource://gre/modules/services-" + s + "/", null, null); resProt.setSubstitution("services-" + s, uri); diff --git a/services/sync/tests/unit/head_errorhandler_common.js b/services/sync/tests/unit/head_errorhandler_common.js deleted file mode 100644 index f4af60d9d..000000000 --- a/services/sync/tests/unit/head_errorhandler_common.js +++ /dev/null @@ -1,112 +0,0 @@ -/* Any copyright is dedicated to the Public Domain. - http://creativecommons.org/publicdomain/zero/1.0/ */ - -Cu.import("resource://services-sync/engines.js"); - -// Common code for test_errorhandler_{1,2}.js -- pulled out to make it less -// monolithic and take less time to execute. -const EHTestsCommon = { - - service_unavailable(request, response) { - let body = "Service Unavailable"; - response.setStatusLine(request.httpVersion, 503, "Service Unavailable"); - response.setHeader("Retry-After", "42"); - response.bodyOutputStream.write(body, body.length); - }, - - sync_httpd_setup() { - let global = new ServerWBO("global", { - syncID: Service.syncID, - storageVersion: STORAGE_VERSION, - engines: {clients: {version: Service.clientsEngine.version, - syncID: Service.clientsEngine.syncID}, - catapult: {version: Service.engineManager.get("catapult").version, - syncID: Service.engineManager.get("catapult").syncID}} - }); - let clientsColl = new ServerCollection({}, true); - - // Tracking info/collections. - let collectionsHelper = track_collections_helper(); - let upd = collectionsHelper.with_updated_collection; - - let handler_401 = httpd_handler(401, "Unauthorized"); - return httpd_setup({ - // Normal server behaviour. - "/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()), - - // Credentials are wrong or node reallocated. - "/1.1/janedoe/storage/meta/global": handler_401, - "/1.1/janedoe/info/collections": handler_401, - - // Maintenance or overloaded (503 + Retry-After) at info/collections. - "/maintenance/1.1/broken.info/info/collections": EHTestsCommon.service_unavailable, - - // Maintenance or overloaded (503 + Retry-After) at meta/global. - "/maintenance/1.1/broken.meta/storage/meta/global": EHTestsCommon.service_unavailable, - "/maintenance/1.1/broken.meta/info/collections": collectionsHelper.handler, - - // Maintenance or overloaded (503 + Retry-After) at crypto/keys. - "/maintenance/1.1/broken.keys/storage/meta/global": upd("meta", global.handler()), - "/maintenance/1.1/broken.keys/info/collections": collectionsHelper.handler, - "/maintenance/1.1/broken.keys/storage/crypto/keys": EHTestsCommon.service_unavailable, - - // Maintenance or overloaded (503 + Retry-After) at wiping collection. - "/maintenance/1.1/broken.wipe/info/collections": collectionsHelper.handler, - "/maintenance/1.1/broken.wipe/storage/meta/global": upd("meta", global.handler()), - "/maintenance/1.1/broken.wipe/storage/crypto/keys": - upd("crypto", (new ServerWBO("keys")).handler()), - "/maintenance/1.1/broken.wipe/storage": EHTestsCommon.service_unavailable, - "/maintenance/1.1/broken.wipe/storage/clients": upd("clients", clientsColl.handler()), - "/maintenance/1.1/broken.wipe/storage/catapult": EHTestsCommon.service_unavailable - }); - }, - - CatapultEngine: (function() { - function CatapultEngine() { - SyncEngine.call(this, "Catapult", Service); - } - CatapultEngine.prototype = { - __proto__: SyncEngine.prototype, - exception: null, // tests fill this in - _sync: function _sync() { - if (this.exception) { - throw this.exception; - } - } - }; - - return CatapultEngine; - }()), - - - generateCredentialsChangedFailure() { - // Make sync fail due to changed credentials. We simply re-encrypt - // the keys with a different Sync Key, without changing the local one. - let newSyncKeyBundle = new SyncKeyBundle("johndoe", "23456234562345623456234562"); - let keys = Service.collectionKeys.asWBO(); - keys.encrypt(newSyncKeyBundle); - keys.upload(Service.resource(Service.cryptoKeysURL)); - }, - - setUp(server) { - return configureIdentity({ username: "johndoe" }).then( - () => { - Service.serverURL = server.baseURI + "/"; - Service.clusterURL = server.baseURI + "/"; - } - ).then( - () => EHTestsCommon.generateAndUploadKeys() - ); - }, - - generateAndUploadKeys() { - generateNewKeys(Service.collectionKeys); - let serverKeys = Service.collectionKeys.asWBO("crypto", "keys"); - serverKeys.encrypt(Service.identity.syncKeyBundle); - return serverKeys.upload(Service.resource(Service.cryptoKeysURL)).success; - } -}; diff --git a/services/sync/tests/unit/head_helpers.js b/services/sync/tests/unit/head_helpers.js index 3c59e1de5..04534dc8e 100644 --- a/services/sync/tests/unit/head_helpers.js +++ b/services/sync/tests/unit/head_helpers.js @@ -4,39 +4,8 @@ Cu.import("resource://services-common/async.js"); Cu.import("resource://testing-common/services/common/utils.js"); Cu.import("resource://testing-common/PlacesTestUtils.jsm"); -Cu.import("resource://services-sync/util.js"); -Cu.import("resource://gre/modules/XPCOMUtils.jsm"); -XPCOMUtils.defineLazyGetter(this, 'SyncPingSchema', function() { - let ns = {}; - Cu.import("resource://gre/modules/FileUtils.jsm", ns); - let stream = Cc["@mozilla.org/network/file-input-stream;1"] - .createInstance(Ci.nsIFileInputStream); - let jsonReader = Cc["@mozilla.org/dom/json;1"] - .createInstance(Components.interfaces.nsIJSON); - let schema; - try { - let schemaFile = do_get_file("sync_ping_schema.json"); - stream.init(schemaFile, ns.FileUtils.MODE_RDONLY, ns.FileUtils.PERMS_FILE, 0); - schema = jsonReader.decodeFromStream(stream, stream.available()); - } finally { - stream.close(); - } - - // Allow tests to make whatever engines they want, this shouldn't cause - // validation failure. - schema.definitions.engine.properties.name = { type: "string" }; - return schema; -}); - -XPCOMUtils.defineLazyGetter(this, 'SyncPingValidator', function() { - let ns = {}; - Cu.import("resource://testing-common/ajv-4.1.1.js", ns); - let ajv = new ns.Ajv({ async: "co*" }); - return ajv.compile(SyncPingSchema); -}); - -var provider = { +let provider = { getFile: function(prop, persistent) { persistent.value = true; switch (prop) { @@ -51,7 +20,7 @@ var provider = { Services.dirsvc.QueryInterface(Ci.nsIDirectoryService).registerProvider(provider); // This is needed for loadAddonTestFunctions(). -var gGlobalScope = this; +let gGlobalScope = this; function ExtensionsTestPath(path) { if (path[0] != "/") { @@ -76,24 +45,6 @@ function loadAddonTestFunctions() { createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9.2"); } -function webExtensionsTestPath(path) { - if (path[0] != "/") { - throw Error("Path must begin with '/': " + path); - } - - return "../../../../toolkit/components/extensions/test/xpcshell" + path; -} - -/** - * Loads the WebExtension test functions by importing its test file. - */ -function loadWebExtensionTestFunctions() { - const path = webExtensionsTestPath("/head_sync.js"); - let file = do_get_file(path); - let uri = Services.io.newFileURI(file); - Services.scriptloader.loadSubScript(uri.spec, gGlobalScope); -} - function getAddonInstall(name) { let f = do_get_file(ExtensionsTestPath("/addons/" + name + ".xpi")); let cb = Async.makeSyncCallback(); @@ -255,192 +206,3 @@ function do_check_array_eq(a1, a2) { do_check_eq(a1[i], a2[i]); } } - -// Helper function to get the sync telemetry and add the typically used test -// engine names to its list of allowed engines. -function get_sync_test_telemetry() { - let ns = {}; - Cu.import("resource://services-sync/telemetry.js", ns); - let testEngines = ["rotary", "steam", "sterling", "catapult"]; - for (let engineName of testEngines) { - ns.SyncTelemetry.allowedEngines.add(engineName); - } - ns.SyncTelemetry.submissionInterval = -1; - return ns.SyncTelemetry; -} - -function assert_valid_ping(record) { - // This is called as the test harness tears down due to shutdown. This - // will typically have no recorded syncs, and the validator complains about - // it. So ignore such records (but only ignore when *both* shutdown and - // no Syncs - either of them not being true might be an actual problem) - if (record && (record.why != "shutdown" || record.syncs.length != 0)) { - if (!SyncPingValidator(record)) { - deepEqual([], SyncPingValidator.errors, "Sync telemetry ping validation failed"); - } - equal(record.version, 1); - record.syncs.forEach(p => { - lessOrEqual(p.when, Date.now()); - if (p.devices) { - ok(!p.devices.some(device => device.id == p.deviceID)); - equal(new Set(p.devices.map(device => device.id)).size, - p.devices.length, "Duplicate device ids in ping devices list"); - } - }); - } -} - -// Asserts that `ping` is a ping that doesn't contain any failure information -function assert_success_ping(ping) { - ok(!!ping); - assert_valid_ping(ping); - ping.syncs.forEach(record => { - ok(!record.failureReason); - equal(undefined, record.status); - greater(record.engines.length, 0); - for (let e of record.engines) { - ok(!e.failureReason); - equal(undefined, e.status); - if (e.validation) { - equal(undefined, e.validation.problems); - equal(undefined, e.validation.failureReason); - } - if (e.outgoing) { - for (let o of e.outgoing) { - equal(undefined, o.failed); - notEqual(undefined, o.sent); - } - } - if (e.incoming) { - equal(undefined, e.incoming.failed); - equal(undefined, e.incoming.newFailed); - notEqual(undefined, e.incoming.applied || e.incoming.reconciled); - } - } - }); -} - -// Hooks into telemetry to validate all pings after calling. -function validate_all_future_pings() { - let telem = get_sync_test_telemetry(); - telem.submit = assert_valid_ping; -} - -function wait_for_ping(callback, allowErrorPings, getFullPing = false) { - return new Promise(resolve => { - let telem = get_sync_test_telemetry(); - let oldSubmit = telem.submit; - telem.submit = function(record) { - telem.submit = oldSubmit; - if (allowErrorPings) { - assert_valid_ping(record); - } else { - assert_success_ping(record); - } - if (getFullPing) { - resolve(record); - } else { - equal(record.syncs.length, 1); - resolve(record.syncs[0]); - } - }; - callback(); - }); -} - -// Short helper for wait_for_ping -function sync_and_validate_telem(allowErrorPings, getFullPing = false) { - return wait_for_ping(() => Service.sync(), allowErrorPings, getFullPing); -} - -// Used for the (many) cases where we do a 'partial' sync, where only a single -// engine is actually synced, but we still want to ensure we're generating a -// valid ping. Returns a promise that resolves to the ping, or rejects with the -// thrown error after calling an optional callback. -function sync_engine_and_validate_telem(engine, allowErrorPings, onError) { - return new Promise((resolve, reject) => { - let telem = get_sync_test_telemetry(); - let caughtError = null; - // Clear out status, so failures from previous syncs won't show up in the - // telemetry ping. - let ns = {}; - Cu.import("resource://services-sync/status.js", ns); - ns.Status._engines = {}; - ns.Status.partial = false; - // Ideally we'd clear these out like we do with engines, (probably via - // Status.resetSync()), but this causes *numerous* tests to fail, so we just - // assume that if no failureReason or engine failures are set, and the - // status properties are the same as they were initially, that it's just - // a leftover. - // This is only an issue since we're triggering the sync of just one engine, - // without doing any other parts of the sync. - let initialServiceStatus = ns.Status._service; - let initialSyncStatus = ns.Status._sync; - - let oldSubmit = telem.submit; - telem.submit = function(ping) { - telem.submit = oldSubmit; - ping.syncs.forEach(record => { - if (record && record.status) { - // did we see anything to lead us to believe that something bad actually happened - let realProblem = record.failureReason || record.engines.some(e => { - if (e.failureReason || e.status) { - return true; - } - if (e.outgoing && e.outgoing.some(o => o.failed > 0)) { - return true; - } - return e.incoming && e.incoming.failed; - }); - if (!realProblem) { - // no, so if the status is the same as it was initially, just assume - // that its leftover and that we can ignore it. - if (record.status.sync && record.status.sync == initialSyncStatus) { - delete record.status.sync; - } - if (record.status.service && record.status.service == initialServiceStatus) { - delete record.status.service; - } - if (!record.status.sync && !record.status.service) { - delete record.status; - } - } - } - }); - if (allowErrorPings) { - assert_valid_ping(ping); - } else { - assert_success_ping(ping); - } - equal(ping.syncs.length, 1); - if (caughtError) { - if (onError) { - onError(ping.syncs[0]); - } - reject(caughtError); - } else { - resolve(ping.syncs[0]); - } - } - Svc.Obs.notify("weave:service:sync:start"); - try { - engine.sync(); - } catch (e) { - caughtError = e; - } - if (caughtError) { - Svc.Obs.notify("weave:service:sync:error", caughtError); - } else { - Svc.Obs.notify("weave:service:sync:finish"); - } - }); -} - -// Avoid an issue where `client.name2` containing unicode characters causes -// a number of tests to fail, due to them assuming that we do not need to utf-8 -// encode or decode data sent through the mocked server (see bug 1268912). -Utils.getDefaultDeviceName = function() { - return "Test device name"; -}; - - diff --git a/services/sync/tests/unit/head_http_server.js b/services/sync/tests/unit/head_http_server.js index 26f62310c..c917c4988 100644 --- a/services/sync/tests/unit/head_http_server.js +++ b/services/sync/tests/unit/head_http_server.js @@ -1,4 +1,4 @@ -var Cm = Components.manager; +const Cm = Components.manager; // Shared logging for all HTTP server functions. Cu.import("resource://gre/modules/Log.jsm"); @@ -178,13 +178,9 @@ ServerCollection.prototype = { * @return an array of IDs. */ keys: function keys(filter) { - let ids = []; - for (let [id, wbo] of Object.entries(this._wbos)) { - if (wbo.payload && (!filter || filter(id, wbo))) { - ids.push(id); - } - } - return ids; + return [id for ([id, wbo] in Iterator(this._wbos)) + if (wbo.payload && + (!filter || filter(id, wbo)))]; }, /** @@ -198,13 +194,8 @@ ServerCollection.prototype = { * @return an array of ServerWBOs. */ wbos: function wbos(filter) { - let os = []; - for (let [id, wbo] of Object.entries(this._wbos)) { - if (wbo.payload) { - os.push(wbo); - } - } - + let os = [wbo for ([id, wbo] in Iterator(this._wbos)) + if (wbo.payload)]; if (filter) { return os.filter(filter); } @@ -276,7 +267,7 @@ ServerCollection.prototype = { count: function(options) { options = options || {}; let c = 0; - for (let [id, wbo] of Object.entries(this._wbos)) { + for (let [id, wbo] in Iterator(this._wbos)) { if (wbo.modified && this._inResultSet(wbo, options)) { c++; } @@ -287,23 +278,12 @@ ServerCollection.prototype = { get: function(options) { let result; if (options.full) { - let data = []; - for (let [id, wbo] of Object.entries(this._wbos)) { - // Drop deleted. - if (wbo.modified && this._inResultSet(wbo, options)) { - data.push(wbo.get()); - } - } - let start = options.offset || 0; + let data = [wbo.get() for ([id, wbo] in Iterator(this._wbos)) + // Drop deleted. + if (wbo.modified && + this._inResultSet(wbo, options))]; if (options.limit) { - let numItemsPastOffset = data.length - start; - data = data.slice(start, start + options.limit); - // use options as a backchannel to set x-weave-next-offset - if (numItemsPastOffset > options.limit) { - options.nextOffset = start + options.limit; - } - } else if (start) { - data = data.slice(start); + data = data.slice(0, options.limit); } // Our implementation of application/newlines. result = data.join("\n") + "\n"; @@ -311,18 +291,10 @@ ServerCollection.prototype = { // Use options as a backchannel to report count. options.recordCount = data.length; } else { - let data = []; - for (let [id, wbo] of Object.entries(this._wbos)) { - if (this._inResultSet(wbo, options)) { - data.push(id); - } - } - let start = options.offset || 0; + let data = [id for ([id, wbo] in Iterator(this._wbos)) + if (this._inResultSet(wbo, options))]; if (options.limit) { - data = data.slice(start, start + options.limit); - options.nextOffset = start + options.limit; - } else if (start) { - data = data.slice(start); + data = data.slice(0, options.limit); } result = JSON.stringify(data); options.recordCount = data.length; @@ -337,8 +309,7 @@ ServerCollection.prototype = { // This will count records where we have an existing ServerWBO // registered with us as successful and all other records as failed. - for (let key in input) { - let record = input[key]; + for each (let record in input) { let wbo = this.wbo(record.id); if (!wbo && this.acceptNew) { this._log.debug("Creating WBO " + JSON.stringify(record.id) + @@ -361,7 +332,7 @@ ServerCollection.prototype = { delete: function(options) { let deleted = []; - for (let [id, wbo] of Object.entries(this._wbos)) { + for (let [id, wbo] in Iterator(this._wbos)) { if (this._inResultSet(wbo, options)) { this._log.debug("Deleting " + JSON.stringify(wbo)); deleted.push(wbo.id); @@ -383,7 +354,7 @@ ServerCollection.prototype = { // Parse queryString let options = {}; - for (let chunk of request.queryString.split("&")) { + for each (let chunk in request.queryString.split("&")) { if (!chunk) { continue; } @@ -403,36 +374,29 @@ ServerCollection.prototype = { if (options.limit) { options.limit = parseInt(options.limit, 10); } - if (options.offset) { - options.offset = parseInt(options.offset, 10); - } switch(request.method) { case "GET": - body = self.get(options, request); - // see http://moz-services-docs.readthedocs.io/en/latest/storage/apis-1.5.html - // for description of these headers. - let { recordCount: records, nextOffset } = options; - - self._log.info("Records: " + records + ", nextOffset: " + nextOffset); + body = self.get(options); + // "If supported by the db, this header will return the number of + // records total in the request body of any multiple-record GET + // request." + let records = options.recordCount; + self._log.info("Records: " + records); if (records != null) { response.setHeader("X-Weave-Records", "" + records); } - if (nextOffset) { - response.setHeader("X-Weave-Next-Offset", "" + nextOffset); - } - response.setHeader("X-Last-Modified", "" + this.timestamp); break; case "POST": - let res = self.post(readBytesFromInputStream(request.bodyInputStream), request); + let res = self.post(readBytesFromInputStream(request.bodyInputStream)); body = JSON.stringify(res); response.newModified = res.modified; break; case "DELETE": self._log.debug("Invoking ServerCollection.DELETE."); - let deleted = self.delete(options, request); + let deleted = self.delete(options); let ts = new_timestamp(); body = JSON.stringify(ts); response.newModified = ts; @@ -541,7 +505,7 @@ function track_collections_helper() { * find out what it needs without monkeypatching. Use this object as your * prototype, and override as appropriate. */ -var SyncServerCallback = { +let SyncServerCallback = { onCollectionDeleted: function onCollectionDeleted(user, collection) {}, onItemDeleted: function onItemDeleted(user, collection, wboID) {}, @@ -581,13 +545,13 @@ SyncServer.prototype = { * Start the SyncServer's underlying HTTP server. * * @param port - * The numeric port on which to start. -1 implies the default, a - * randomly chosen port. + * The numeric port on which to start. A falsy value implies the + * default, a randomly chosen port. * @param cb * A callback function (of no arguments) which is invoked after * startup. */ - start: function start(port = -1, cb) { + start: function start(port, cb) { if (this.started) { this._log.warn("Warning: server already started on " + this.port); return; @@ -605,7 +569,7 @@ SyncServer.prototype = { } catch (ex) { _("=========================================="); _("Got exception starting Sync HTTP server."); - _("Error: " + Log.exceptionStr(ex)); + _("Error: " + Utils.exceptionStr(ex)); _("Is there a process already listening on port " + port + "?"); _("=========================================="); do_throw(ex); @@ -703,10 +667,10 @@ SyncServer.prototype = { throw new Error("Unknown user."); } let userCollections = this.users[username].collections; - for (let [id, contents] of Object.entries(collections)) { + for (let [id, contents] in Iterator(collections)) { let coll = userCollections[id] || this._insertCollection(userCollections, id); - for (let [wboID, payload] of Object.entries(contents)) { + for (let [wboID, payload] in Iterator(contents)) { coll.insert(wboID, payload); } } @@ -740,8 +704,7 @@ SyncServer.prototype = { throw new Error("Unknown user."); } let userCollections = this.users[username].collections; - for (let name in userCollections) { - let coll = userCollections[name]; + for each (let [name, coll] in Iterator(userCollections)) { this._log.trace("Bulk deleting " + name + " for " + username + "..."); coll.delete({}); } @@ -805,10 +768,7 @@ SyncServer.prototype = { */ respond: function respond(req, resp, code, status, body, headers) { resp.setStatusLine(req.httpVersion, code, status); - if (!headers) - headers = this.defaultHeaders; - for (let header in headers) { - let value = headers[header]; + for each (let [header, value] in Iterator(headers || this.defaultHeaders)) { resp.setHeader(header, value); } resp.setHeader("X-Weave-Timestamp", "" + this.timestamp(), false); @@ -1035,7 +995,7 @@ SyncServer.prototype = { */ function serverForUsers(users, contents, callback) { let server = new SyncServer(callback); - for (let [user, pass] of Object.entries(users)) { + for (let [user, pass] in Iterator(users)) { server.registerUser(user, pass); server.createContents(user, contents); } diff --git a/services/sync/tests/unit/prefs_test_prefs_store.js b/services/sync/tests/unit/prefs_test_prefs_store.js deleted file mode 100644 index 109757a35..000000000 --- a/services/sync/tests/unit/prefs_test_prefs_store.js +++ /dev/null @@ -1,25 +0,0 @@ -// This is a "preferences" file used by test_prefs_store.js - -// The prefs that control what should be synced. -// Most of these are "default" prefs, so the value itself will not sync. -pref("services.sync.prefs.sync.testing.int", true); -pref("services.sync.prefs.sync.testing.string", true); -pref("services.sync.prefs.sync.testing.bool", true); -pref("services.sync.prefs.sync.testing.dont.change", true); -// this one is a user pref, so it *will* sync. -user_pref("services.sync.prefs.sync.testing.turned.off", false); -pref("services.sync.prefs.sync.testing.nonexistent", true); -pref("services.sync.prefs.sync.testing.default", true); - -// The preference values - these are all user_prefs, otherwise their value -// will not be synced. -user_pref("testing.int", 123); -user_pref("testing.string", "ohai"); -user_pref("testing.bool", true); -user_pref("testing.dont.change", "Please don't change me."); -user_pref("testing.turned.off", "I won't get synced."); -user_pref("testing.not.turned.on", "I won't get synced either!"); - -// A pref that exists but still has the default value - will be synced with -// null as the value. -pref("testing.default", "I'm the default value"); diff --git a/services/sync/tests/unit/sync_ping_schema.json b/services/sync/tests/unit/sync_ping_schema.json deleted file mode 100644 index 56114fb93..000000000 --- a/services/sync/tests/unit/sync_ping_schema.json +++ /dev/null @@ -1,198 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-04/schema#", - "description": "schema for Sync pings, documentation avaliable in toolkit/components/telemetry/docs/sync-ping.rst", - "type": "object", - "additionalProperties": false, - "required": ["version", "syncs", "why"], - "properties": { - "version": { "type": "integer", "minimum": 0 }, - "discarded": { "type": "integer", "minimum": 1 }, - "why": { "enum": ["shutdown", "schedule"] }, - "syncs": { - "type": "array", - "minItems": 1, - "items": { "$ref": "#/definitions/payload" } - } - }, - "definitions": { - "payload": { - "type": "object", - "additionalProperties": false, - "required": ["when", "uid", "took"], - "properties": { - "didLogin": { "type": "boolean" }, - "when": { "type": "integer" }, - "uid": { - "type": "string", - "pattern": "^[0-9a-f]{32}$" - }, - "devices": { - "type": "array", - "items": { "$ref": "#/definitions/device" } - }, - "deviceID": { - "type": "string", - "pattern": "^[0-9a-f]{64}$" - }, - "status": { - "type": "object", - "anyOf": [ - { "required": ["sync"] }, - { "required": ["service"] } - ], - "additionalProperties": false, - "properties": { - "sync": { "type": "string" }, - "service": { "type": "string" } - } - }, - "why": { "enum": ["startup", "schedule", "score", "user", "tabs"] }, - "took": { "type": "integer", "minimum": -1 }, - "failureReason": { "$ref": "#/definitions/error" }, - "engines": { - "type": "array", - "minItems": 1, - "items": { "$ref": "#/definitions/engine" } - } - } - }, - "device": { - "required": ["os", "id", "version"], - "additionalProperties": false, - "type": "object", - "properties": { - "id": { "type": "string", "pattern": "^[0-9a-f]{64}$" }, - "os": { "type": "string" }, - "version": { "type": "string" } - } - }, - "engine": { - "required": ["name"], - "additionalProperties": false, - "properties": { - "failureReason": { "$ref": "#/definitions/error" }, - "name": { "enum": ["addons", "bookmarks", "clients", "forms", "history", "passwords", "prefs", "tabs"] }, - "took": { "type": "integer", "minimum": 1 }, - "status": { "type": "string" }, - "incoming": { - "type": "object", - "additionalProperties": false, - "anyOf": [ - {"required": ["applied"]}, - {"required": ["failed"]}, - {"required": ["newFailed"]}, - {"required": ["reconciled"]} - ], - "properties": { - "applied": { "type": "integer", "minimum": 1 }, - "failed": { "type": "integer", "minimum": 1 }, - "newFailed": { "type": "integer", "minimum": 1 }, - "reconciled": { "type": "integer", "minimum": 1 } - } - }, - "outgoing": { - "type": "array", - "minItems": 1, - "items": { "$ref": "#/definitions/outgoingBatch" } - }, - "validation": { - "type": "object", - "additionalProperties": false, - "anyOf": [ - { "required": ["checked"] }, - { "required": ["failureReason"] } - ], - "properties": { - "checked": { "type": "integer", "minimum": 0 }, - "failureReason": { "$ref": "#/definitions/error" }, - "took": { "type": "integer" }, - "version": { "type": "integer" }, - "problems": { - "type": "array", - "minItems": 1, - "$ref": "#/definitions/validationProblem" - } - } - } - } - }, - "outgoingBatch": { - "type": "object", - "additionalProperties": false, - "anyOf": [ - {"required": ["sent"]}, - {"required": ["failed"]} - ], - "properties": { - "sent": { "type": "integer", "minimum": 1 }, - "failed": { "type": "integer", "minimum": 1 } - } - }, - "error": { - "oneOf": [ - { "$ref": "#/definitions/httpError" }, - { "$ref": "#/definitions/nsError" }, - { "$ref": "#/definitions/shutdownError" }, - { "$ref": "#/definitions/authError" }, - { "$ref": "#/definitions/otherError" }, - { "$ref": "#/definitions/unexpectedError" }, - { "$ref": "#/definitions/sqlError" } - ] - }, - "httpError": { - "required": ["name", "code"], - "properties": { - "name": { "enum": ["httperror"] }, - "code": { "type": "integer" } - } - }, - "nsError": { - "required": ["name", "code"], - "properties": { - "name": { "enum": ["nserror"] }, - "code": { "type": "integer" } - } - }, - "shutdownError": { - "required": ["name"], - "properties": { - "name": { "enum": ["shutdownerror"] } - } - }, - "authError": { - "required": ["name"], - "properties": { - "name": { "enum": ["autherror"] }, - "from": { "enum": ["tokenserver", "fxaccounts", "hawkclient"] } - } - }, - "otherError": { - "required": ["name"], - "properties": { - "name": { "enum": ["othererror"] }, - "error": { "type": "string" } - } - }, - "unexpectedError": { - "required": ["name"], - "properties": { - "name": { "enum": ["unexpectederror"] }, - "error": { "type": "string" } - } - }, - "sqlError": { - "required": ["name"], - "properties": { - "name": { "enum": ["sqlerror"] }, - "code": { "type": "integer" } - } - }, - "validationProblem": { - "required": ["name", "count"], - "properties": { - "name": { "type": "string" }, - "count": { "type": "integer" } - } - } - } -} \ No newline at end of file diff --git a/services/sync/tests/unit/systemaddon-search.xml b/services/sync/tests/unit/systemaddon-search.xml deleted file mode 100644 index d34e3937c..000000000 --- a/services/sync/tests/unit/systemaddon-search.xml +++ /dev/null @@ -1,27 +0,0 @@ - - - - System Add-on Test - Extension - system1@tests.mozilla.org - addon11 - 1.0 - - - Firefox - 1 - 3.6 - * - xpcshell@tests.mozilla.org - - ALL - - http://127.0.0.1:8888/system.xpi - - 2009-09-14T04:47:42Z - - - 2011-09-05T20:42:09Z - - - diff --git a/services/sync/tests/unit/test_addon_utils.js b/services/sync/tests/unit/test_addon_utils.js index bbbd81d0d..49824cd4c 100644 --- a/services/sync/tests/unit/test_addon_utils.js +++ b/services/sync/tests/unit/test_addon_utils.js @@ -3,7 +3,6 @@ "use strict"; -Cu.import("resource://gre/modules/Log.jsm"); Cu.import("resource://gre/modules/Preferences.jsm"); Cu.import("resource://services-sync/addonutils.js"); Cu.import("resource://services-sync/util.js"); @@ -11,7 +10,7 @@ Cu.import("resource://services-sync/util.js"); const HTTP_PORT = 8888; const SERVER_ADDRESS = "http://127.0.0.1:8888"; -var prefs = new Preferences(); +let prefs = new Preferences(); prefs.set("extensions.getAddons.get.url", SERVER_ADDRESS + "/search/guid:%IDS%"); @@ -36,7 +35,7 @@ function createAndStartHTTPServer(port=HTTP_PORT) { return server; } catch (ex) { _("Got exception starting HTTP server on port " + port); - _("Error: " + Log.exceptionStr(ex)); + _("Error: " + Utils.exceptionStr(ex)); do_throw(ex); } } @@ -61,9 +60,6 @@ add_test(function test_handle_empty_source_uri() { do_check_true("installedIDs" in result); do_check_eq(0, result.installedIDs.length); - do_check_true("skipped" in result); - do_check_true(result.skipped.includes(ID)); - server.stop(run_next_test); }); @@ -83,18 +79,44 @@ add_test(function test_ignore_untrusted_source_uris() { let sourceURI = ioService.newURI(s, null, null); let addon = {sourceURI: sourceURI, name: "bad", id: "bad"}; - let canInstall = AddonUtils.canInstallAddon(addon); - do_check_false(canInstall, "Correctly rejected a bad URL"); + try { + let cb = Async.makeSpinningCallback(); + AddonUtils.getInstallFromSearchResult(addon, cb, true); + cb.wait(); + } catch (ex) { + do_check_neq(null, ex); + do_check_eq(0, ex.message.indexOf("Insecure source URI")); + continue; + } + + // We should never get here if an exception is thrown. + do_check_true(false); } + let count = 0; for (let s of good) { let sourceURI = ioService.newURI(s, null, null); let addon = {sourceURI: sourceURI, name: "good", id: "good"}; - let canInstall = AddonUtils.canInstallAddon(addon); - do_check_true(canInstall, "Correctly accepted a good URL"); + // Despite what you might think, we don't get an error in the callback. + // The install won't work because the underlying Addon instance wasn't + // proper. But, that just results in an AddonInstall that is missing + // certain values. We really just care that the callback is being invoked + // anyway. + let callback = function onInstall(error, install) { + do_check_null(error); + do_check_neq(null, install); + do_check_eq(sourceURI.spec, install.sourceURI.spec); + + count += 1; + + if (count >= good.length) { + run_next_test(); + } + }; + + AddonUtils.getInstallFromSearchResult(addon, callback, true); } - run_next_test(); }); add_test(function test_source_uri_rewrite() { @@ -103,6 +125,8 @@ add_test(function test_source_uri_rewrite() { // This tests for conformance with bug 708134 so server-side metrics aren't // skewed. + Svc.Prefs.set("addons.ignoreRepositoryChecking", true); + // We resort to monkeypatching because of the API design. let oldFunction = AddonUtils.__proto__.installAddonFromSearchResult; @@ -127,15 +151,12 @@ add_test(function test_source_uri_rewrite() { let server = createAndStartHTTPServer(); let installCallback = Async.makeSpinningCallback(); - let installOptions = { - id: "rewrite@tests.mozilla.org", - requireSecureURI: false, - } - AddonUtils.installAddons([installOptions], installCallback); + AddonUtils.installAddons([{id: "rewrite@tests.mozilla.org"}], installCallback); installCallback.wait(); do_check_true(installCalled); AddonUtils.__proto__.installAddonFromSearchResult = oldFunction; + Svc.Prefs.reset("addons.ignoreRepositoryChecking"); server.stop(run_next_test); }); diff --git a/services/sync/tests/unit/test_addons_engine.js b/services/sync/tests/unit/test_addons_engine.js index 64e4e32e8..ca2e4bd96 100644 --- a/services/sync/tests/unit/test_addons_engine.js +++ b/services/sync/tests/unit/test_addons_engine.js @@ -13,20 +13,19 @@ Cu.import("resource://services-sync/service.js"); Cu.import("resource://services-sync/util.js"); Cu.import("resource://testing-common/services/sync/utils.js"); -var prefs = new Preferences(); +let prefs = new Preferences(); prefs.set("extensions.getAddons.get.url", "http://localhost:8888/search/guid:%IDS%"); -prefs.set("extensions.install.requireSecureOrigin", false); loadAddonTestFunctions(); startupManager(); -var engineManager = Service.engineManager; +let engineManager = Service.engineManager; engineManager.register(AddonsEngine); -var engine = engineManager.get("addons"); -var reconciler = engine._reconciler; -var tracker = engine._tracker; +let engine = engineManager.get("addons"); +let reconciler = engine._reconciler; +let tracker = engine._tracker; function advance_test() { reconciler._addons = {}; @@ -36,6 +35,8 @@ function advance_test() { reconciler.saveState(null, cb); cb.wait(); + Svc.Prefs.reset("addons.ignoreRepositoryChecking"); + run_next_test(); } @@ -103,6 +104,7 @@ add_test(function test_get_changed_ids() { tracker.clearChangedIDs(); _("Ensure reconciler changes are populated."); + Svc.Prefs.set("addons.ignoreRepositoryChecking", true); let addon = installAddon("test_bootstrap1_1"); tracker.clearChangedIDs(); // Just in case. changes = engine.getChangedIDs(); @@ -149,6 +151,9 @@ add_test(function test_disabled_install_semantics() { // This is essentially a test for bug 712542, which snuck into the original // add-on sync drop. It ensures that when an add-on is installed that the // disabled state and incoming syncGUID is preserved, even on the next sync. + + Svc.Prefs.set("addons.ignoreRepositoryChecking", true); + const USER = "foo"; const PASSWORD = "password"; const PASSPHRASE = "abcdeabcdeabcdeabcdeabcdea"; diff --git a/services/sync/tests/unit/test_addons_reconciler.js b/services/sync/tests/unit/test_addons_reconciler.js index d93bdfc03..8cfa37d78 100644 --- a/services/sync/tests/unit/test_addons_reconciler.js +++ b/services/sync/tests/unit/test_addons_reconciler.js @@ -71,7 +71,7 @@ add_test(function test_install_detection() { const KEYS = ["id", "guid", "enabled", "installed", "modified", "type", "scope", "foreignInstall"]; - for (let key of KEYS) { + for each (let key in KEYS) { do_check_true(key in record); do_check_neq(null, record[key]); } diff --git a/services/sync/tests/unit/test_addons_store.js b/services/sync/tests/unit/test_addons_store.js index b52cfab31..b21f6afe1 100644 --- a/services/sync/tests/unit/test_addons_store.js +++ b/services/sync/tests/unit/test_addons_store.js @@ -3,47 +3,25 @@ "use strict"; -Cu.import("resource://gre/modules/Log.jsm"); Cu.import("resource://gre/modules/Preferences.jsm"); Cu.import("resource://services-sync/addonutils.js"); Cu.import("resource://services-sync/engines/addons.js"); Cu.import("resource://services-sync/service.js"); Cu.import("resource://services-sync/util.js"); -Cu.import("resource://testing-common/services/sync/utils.js"); -Cu.import("resource://gre/modules/FileUtils.jsm"); const HTTP_PORT = 8888; -var prefs = new Preferences(); +let prefs = new Preferences(); prefs.set("extensions.getAddons.get.url", "http://localhost:8888/search/guid:%IDS%"); -prefs.set("extensions.install.requireSecureOrigin", false); - -const SYSTEM_ADDON_ID = "system1@tests.mozilla.org"; -let systemAddonFile; - -// The system add-on must be installed before AddonManager is started. -function loadSystemAddon() { - let addonFilename = SYSTEM_ADDON_ID + ".xpi"; - const distroDir = FileUtils.getDir("ProfD", ["sysfeatures", "app0"], true); - do_get_file(ExtensionsTestPath("/data/system_addons/system1_1.xpi")).copyTo(distroDir, addonFilename); - systemAddonFile = FileUtils.File(distroDir.path); - systemAddonFile.append(addonFilename); - systemAddonFile.lastModifiedTime = Date.now(); - // As we're not running in application, we need to setup the features directory - // used by system add-ons. - registerDirectory("XREAppFeat", distroDir); -} - loadAddonTestFunctions(); -loadSystemAddon(); startupManager(); Service.engineManager.register(AddonsEngine); -var engine = Service.engineManager.get("addons"); -var tracker = engine._tracker; -var store = engine._store; -var reconciler = engine._reconciler; +let engine = Service.engineManager.get("addons"); +let tracker = engine._tracker; +let store = engine._store; +let reconciler = engine._reconciler; /** * Create a AddonsRec for this application with the fields specified. @@ -77,16 +55,12 @@ function createAndStartHTTPServer(port) { server.registerFile("/search/guid:missing-xpi%40tests.mozilla.org", do_get_file("missing-xpi-search.xml")); - server.registerFile("/search/guid:system1%40tests.mozilla.org", - do_get_file("systemaddon-search.xml")); - server.registerFile("/system.xpi", systemAddonFile); - server.start(port); return server; } catch (ex) { _("Got exception starting HTTP server on port " + port); - _("Error: " + Log.exceptionStr(ex)); + _("Error: " + Utils.exceptionStr(ex)); do_throw(ex); } } @@ -94,7 +68,6 @@ function createAndStartHTTPServer(port) { function run_test() { initTestLogging("Trace"); Log.repository.getLogger("Sync.Engine.Addons").level = Log.Level.Trace; - Log.repository.getLogger("Sync.Tracker.Addons").level = Log.Level.Trace; Log.repository.getLogger("Sync.AddonsRepository").level = Log.Level.Trace; @@ -219,6 +192,7 @@ add_test(function test_apply_uninstall() { add_test(function test_addon_syncability() { _("Ensure isAddonSyncable functions properly."); + Svc.Prefs.set("addons.ignoreRepositoryChecking", true); Svc.Prefs.set("addons.trustedSourceHostnames", "addons.mozilla.org,other.example.com"); @@ -228,8 +202,8 @@ add_test(function test_addon_syncability() { do_check_true(store.isAddonSyncable(addon)); let dummy = {}; - const KEYS = ["id", "syncGUID", "type", "scope", "foreignInstall", "isSyncable"]; - for (let k of KEYS) { + const KEYS = ["id", "syncGUID", "type", "scope", "foreignInstall"]; + for each (let k in KEYS) { dummy[k] = addon[k]; } @@ -243,10 +217,6 @@ add_test(function test_addon_syncability() { do_check_false(store.isAddonSyncable(dummy)); dummy.scope = addon.scope; - dummy.isSyncable = false; - do_check_false(store.isAddonSyncable(dummy)); - dummy.isSyncable = addon.isSyncable; - dummy.foreignInstall = true; do_check_false(store.isAddonSyncable(dummy)); dummy.foreignInstall = false; @@ -272,16 +242,16 @@ add_test(function test_addon_syncability() { "https://untrusted.example.com/foo", // non-trusted hostname` ]; - for (let uri of trusted) { + for each (let uri in trusted) { do_check_true(store.isSourceURITrusted(createURI(uri))); } - for (let uri of untrusted) { + for each (let uri in untrusted) { do_check_false(store.isSourceURITrusted(createURI(uri))); } Svc.Prefs.set("addons.trustedSourceHostnames", ""); - for (let uri of trusted) { + for each (let uri in trusted) { do_check_false(store.isSourceURITrusted(createURI(uri))); } @@ -296,6 +266,8 @@ add_test(function test_addon_syncability() { add_test(function test_ignore_hotfixes() { _("Ensure that hotfix extensions are ignored."); + Svc.Prefs.set("addons.ignoreRepositoryChecking", true); + // A hotfix extension is one that has the id the same as the // extensions.hotfix.id pref. let prefs = new Preferences("extensions."); @@ -304,8 +276,8 @@ add_test(function test_ignore_hotfixes() { do_check_true(store.isAddonSyncable(addon)); let dummy = {}; - const KEYS = ["id", "syncGUID", "type", "scope", "foreignInstall", "isSyncable"]; - for (let k of KEYS) { + const KEYS = ["id", "syncGUID", "type", "scope", "foreignInstall"]; + for each (let k in KEYS) { dummy[k] = addon[k]; } @@ -327,6 +299,7 @@ add_test(function test_ignore_hotfixes() { uninstallAddon(addon); + Svc.Prefs.reset("addons.ignoreRepositoryChecking"); prefs.reset("hotfix.id"); run_next_test(); @@ -336,6 +309,8 @@ add_test(function test_ignore_hotfixes() { add_test(function test_get_all_ids() { _("Ensures that getAllIDs() returns an appropriate set."); + Svc.Prefs.set("addons.ignoreRepositoryChecking", true); + _("Installing two addons."); let addon1 = installAddon("test_install1"); let addon2 = installAddon("test_bootstrap1_1"); @@ -354,6 +329,7 @@ add_test(function test_get_all_ids() { addon1.install.cancel(); uninstallAddon(addon2); + Svc.Prefs.reset("addons.ignoreRepositoryChecking"); run_next_test(); }); @@ -379,6 +355,9 @@ add_test(function test_change_item_id() { add_test(function test_create() { _("Ensure creating/installing an add-on from a record works."); + // Set this so that getInstallFromSearchResult doesn't end up + // failing the install due to an insecure source URI scheme. + Svc.Prefs.set("addons.ignoreRepositoryChecking", true); let server = createAndStartHTTPServer(HTTP_PORT); let addon = installAddon("test_bootstrap1_1"); @@ -398,6 +377,7 @@ add_test(function test_create() { uninstallAddon(newAddon); + Svc.Prefs.reset("addons.ignoreRepositoryChecking"); server.stop(run_next_test); }); @@ -432,18 +412,8 @@ add_test(function test_create_bad_install() { let record = createRecordForThisApp(guid, id, true, false); let failed = store.applyIncomingBatch([record]); - // This addon had no source URI so was skipped - but it's not treated as - // failure. - // XXX - this test isn't testing what we thought it was. Previously the addon - // was not being installed due to requireSecureURL checking *before* we'd - // attempted to get the XPI. - // With requireSecureURL disabled we do see a download failure, but the addon - // *does* get added to |failed|. - // FTR: onDownloadFailed() is called with ERROR_NETWORK_FAILURE, so it's going - // to be tricky to distinguish a 404 from other transient network errors - // where we do want the addon to end up in |failed|. - // This is being tracked in bug 1284778. - //do_check_eq(0, failed.length); + do_check_eq(1, failed.length); + do_check_eq(guid, failed[0]); let addon = getAddonFromAddonManagerByID(id); do_check_eq(null, addon); @@ -451,56 +421,19 @@ add_test(function test_create_bad_install() { server.stop(run_next_test); }); -add_test(function test_ignore_system() { - _("Ensure we ignore system addons"); - // Our system addon should not appear in getAllIDs - engine._refreshReconcilerState(); - let num = 0; - for (let guid in store.getAllIDs()) { - num += 1; - let addon = reconciler.getAddonStateFromSyncGUID(guid); - do_check_neq(addon.id, SYSTEM_ADDON_ID); - } - do_check_true(num > 1, "should have seen at least one.") - run_next_test(); -}); - -add_test(function test_incoming_system() { - _("Ensure we handle incoming records that refer to a system addon"); - // eg, loop initially had a normal addon but it was then "promoted" to be a - // system addon but wanted to keep the same ID. The server record exists due - // to this. - - // before we start, ensure the system addon isn't disabled. - do_check_false(getAddonFromAddonManagerByID(SYSTEM_ADDON_ID).userDisabled); - - // Now simulate an incoming record with the same ID as the system addon, - // but flagged as disabled - it should not be applied. - let server = createAndStartHTTPServer(HTTP_PORT); - // We make the incoming record flag the system addon as disabled - it should - // be ignored. - let guid = Utils.makeGUID(); - let record = createRecordForThisApp(guid, SYSTEM_ADDON_ID, false, false); - - let failed = store.applyIncomingBatch([record]); - do_check_eq(0, failed.length); - - // The system addon should still not be userDisabled. - do_check_false(getAddonFromAddonManagerByID(SYSTEM_ADDON_ID).userDisabled); - - server.stop(run_next_test); -}); - add_test(function test_wipe() { _("Ensures that wiping causes add-ons to be uninstalled."); let addon1 = installAddon("test_bootstrap1_1"); + Svc.Prefs.set("addons.ignoreRepositoryChecking", true); store.wipe(); let addon = getAddonFromAddonManagerByID(addon1.id); do_check_eq(null, addon); + Svc.Prefs.reset("addons.ignoreRepositoryChecking"); + run_next_test(); }); @@ -515,6 +448,7 @@ add_test(function test_wipe_and_install() { let record = createRecordForThisApp(installed.syncGUID, installed.id, true, false); + Svc.Prefs.set("addons.ignoreRepositoryChecking", true); store.wipe(); let deleted = getAddonFromAddonManagerByID(installed.id); @@ -528,6 +462,7 @@ add_test(function test_wipe_and_install() { let fetched = getAddonFromAddonManagerByID(record.addonID); do_check_true(!!fetched); + Svc.Prefs.reset("addons.ignoreRepositoryChecking"); server.stop(run_next_test); }); diff --git a/services/sync/tests/unit/test_addons_tracker.js b/services/sync/tests/unit/test_addons_tracker.js index 01bf37ab9..690a57d03 100644 --- a/services/sync/tests/unit/test_addons_tracker.js +++ b/services/sync/tests/unit/test_addons_tracker.js @@ -11,13 +11,14 @@ Cu.import("resource://services-sync/util.js"); loadAddonTestFunctions(); startupManager(); +Svc.Prefs.set("addons.ignoreRepositoryChecking", true); Svc.Prefs.set("engine.addons", true); Service.engineManager.register(AddonsEngine); -var engine = Service.engineManager.get("addons"); -var reconciler = engine._reconciler; -var store = engine._store; -var tracker = engine._tracker; +let engine = Service.engineManager.get("addons"); +let reconciler = engine._reconciler; +let store = engine._store; +let tracker = engine._tracker; // Don't write out by default. tracker.persistChangedIDs = false; diff --git a/services/sync/tests/unit/test_block_sync.js b/services/sync/tests/unit/test_block_sync.js new file mode 100644 index 000000000..f83b7b740 --- /dev/null +++ b/services/sync/tests/unit/test_block_sync.js @@ -0,0 +1,37 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ +Cu.import("resource://services-sync/main.js"); +Cu.import("resource://services-sync/util.js"); + +// Simple test for block/unblock. +add_task(function *() { + Assert.ok(!Weave.Service.scheduler.isBlocked, "sync is not blocked.") + Assert.ok(!Svc.Prefs.has("scheduler.blocked-until"), "have no blocked pref"); + Weave.Service.scheduler.blockSync(); + + Assert.ok(Weave.Service.scheduler.isBlocked, "sync is blocked.") + Assert.ok(Svc.Prefs.has("scheduler.blocked-until"), "have the blocked pref"); + + Weave.Service.scheduler.unblockSync(); + Assert.ok(!Weave.Service.scheduler.isBlocked, "sync is not blocked.") + Assert.ok(!Svc.Prefs.has("scheduler.blocked-until"), "have no blocked pref"); + + // now check the "until" functionality. + let until = Date.now() + 1000; + Weave.Service.scheduler.blockSync(until); + Assert.ok(Weave.Service.scheduler.isBlocked, "sync is blocked.") + Assert.ok(Svc.Prefs.has("scheduler.blocked-until"), "have the blocked pref"); + + // wait for 'until' to pass. + yield new Promise((resolve, reject) => { + CommonUtils.namedTimer(resolve, 1000, {}, "timer"); + }); + + // should have automagically unblocked and removed the pref. + Assert.ok(!Weave.Service.scheduler.isBlocked, "sync is not blocked.") + Assert.ok(!Svc.Prefs.has("scheduler.blocked-until"), "have no blocked pref"); +}); + +function run_test() { + run_next_test(); +} diff --git a/services/sync/tests/unit/test_bookmark_duping.js b/services/sync/tests/unit/test_bookmark_duping.js deleted file mode 100644 index 1e6c6ed2e..000000000 --- a/services/sync/tests/unit/test_bookmark_duping.js +++ /dev/null @@ -1,644 +0,0 @@ -/* Any copyright is dedicated to the Public Domain. - http://creativecommons.org/publicdomain/zero/1.0/ */ - -Cu.import("resource://gre/modules/PlacesUtils.jsm"); -Cu.import("resource://services-common/async.js"); -Cu.import("resource://gre/modules/Log.jsm"); -Cu.import("resource://services-sync/engines.js"); -Cu.import("resource://services-sync/engines/bookmarks.js"); -Cu.import("resource://services-sync/service.js"); -Cu.import("resource://services-sync/util.js"); -Cu.import("resource://testing-common/services/sync/utils.js"); -Cu.import("resource://services-sync/bookmark_validator.js"); - - -initTestLogging("Trace"); - -const bms = PlacesUtils.bookmarks; - -Service.engineManager.register(BookmarksEngine); - -const engine = new BookmarksEngine(Service); -const store = engine._store; -store._log.level = Log.Level.Trace; -engine._log.level = Log.Level.Trace; - -function promiseOneObserver(topic) { - return new Promise((resolve, reject) => { - let observer = function(subject, topic, data) { - Services.obs.removeObserver(observer, topic); - resolve({ subject: subject, data: data }); - } - Services.obs.addObserver(observer, topic, false); - }); -} - -function setup() { - let server = serverForUsers({"foo": "password"}, { - meta: {global: {engines: {bookmarks: {version: engine.version, - syncID: engine.syncID}}}}, - bookmarks: {}, - }); - - generateNewKeys(Service.collectionKeys); - - new SyncTestingInfrastructure(server.server); - - let collection = server.user("foo").collection("bookmarks"); - - Svc.Obs.notify("weave:engine:start-tracking"); // We skip usual startup... - - return { server, collection }; -} - -function* cleanup(server) { - Svc.Obs.notify("weave:engine:stop-tracking"); - Services.prefs.setBoolPref("services.sync-testing.startOverKeepIdentity", true); - let promiseStartOver = promiseOneObserver("weave:service:start-over:finish"); - Service.startOver(); - yield promiseStartOver; - yield new Promise(resolve => server.stop(resolve)); - yield bms.eraseEverything(); -} - -function getFolderChildrenIDs(folderId) { - let index = 0; - let result = []; - while (true) { - let childId = bms.getIdForItemAt(folderId, index); - if (childId == -1) { - break; - } - result.push(childId); - index++; - } - return result; -} - -function createFolder(parentId, title) { - let id = bms.createFolder(parentId, title, 0); - let guid = store.GUIDForId(id); - return { id, guid }; -} - -function createBookmark(parentId, url, title, index = bms.DEFAULT_INDEX) { - let uri = Utils.makeURI(url); - let id = bms.insertBookmark(parentId, uri, index, title) - let guid = store.GUIDForId(id); - return { id, guid }; -} - -function getServerRecord(collection, id) { - let wbo = collection.get({ full: true, ids: [id] }); - // Whew - lots of json strings inside strings. - return JSON.parse(JSON.parse(JSON.parse(wbo).payload).ciphertext); -} - -function* promiseNoLocalItem(guid) { - // Check there's no item with the specified guid. - let got = yield bms.fetch({ guid }); - ok(!got, `No record remains with GUID ${guid}`); - // and while we are here ensure the places cache doesn't still have it. - yield Assert.rejects(PlacesUtils.promiseItemId(guid)); -} - -function* validate(collection, expectedFailures = []) { - let validator = new BookmarkValidator(); - let records = collection.payloads(); - - let problems = validator.inspectServerRecords(records).problemData; - // all non-zero problems. - let summary = problems.getSummary().filter(prob => prob.count != 0); - - // split into 2 arrays - expected and unexpected. - let isInExpectedFailures = elt => { - for (let i = 0; i < expectedFailures.length; i++) { - if (elt.name == expectedFailures[i].name && elt.count == expectedFailures[i].count) { - return true; - } - } - return false; - } - let expected = []; - let unexpected = []; - for (let elt of summary) { - (isInExpectedFailures(elt) ? expected : unexpected).push(elt); - } - if (unexpected.length || expected.length != expectedFailures.length) { - do_print("Validation failed:"); - do_print(JSON.stringify(summary)); - // print the entire validator output as it has IDs etc. - do_print(JSON.stringify(problems, undefined, 2)); - // All server records and the entire bookmark tree. - do_print("Server records:\n" + JSON.stringify(collection.payloads(), undefined, 2)); - let tree = yield PlacesUtils.promiseBookmarksTree("", { includeItemIds: true }); - do_print("Local bookmark tree:\n" + JSON.stringify(tree, undefined, 2)); - ok(false); - } -} - -add_task(function* test_dupe_bookmark() { - _("Ensure that a bookmark we consider a dupe is handled correctly."); - - let { server, collection } = this.setup(); - - try { - // The parent folder and one bookmark in it. - let {id: folder1_id, guid: folder1_guid } = createFolder(bms.toolbarFolder, "Folder 1"); - let {id: bmk1_id, guid: bmk1_guid} = createBookmark(folder1_id, "http://getfirefox.com/", "Get Firefox!"); - - engine.sync(); - - // We've added the bookmark, its parent (folder1) plus "menu", "toolbar", "unfiled", and "mobile". - equal(collection.count(), 6); - equal(getFolderChildrenIDs(folder1_id).length, 1); - - // Now create a new incoming record that looks alot like a dupe. - let newGUID = Utils.makeGUID(); - let to_apply = { - id: newGUID, - bmkUri: "http://getfirefox.com/", - type: "bookmark", - title: "Get Firefox!", - parentName: "Folder 1", - parentid: folder1_guid, - }; - - collection.insert(newGUID, encryptPayload(to_apply), Date.now() / 1000 + 10); - _("Syncing so new dupe record is processed"); - engine.lastSync = engine.lastSync - 0.01; - engine.sync(); - - // We should have logically deleted the dupe record. - equal(collection.count(), 7); - ok(getServerRecord(collection, bmk1_guid).deleted); - // and physically removed from the local store. - yield promiseNoLocalItem(bmk1_guid); - // Parent should still only have 1 item. - equal(getFolderChildrenIDs(folder1_id).length, 1); - // The parent record on the server should now reference the new GUID and not the old. - let serverRecord = getServerRecord(collection, folder1_guid); - ok(!serverRecord.children.includes(bmk1_guid)); - ok(serverRecord.children.includes(newGUID)); - - // and a final sanity check - use the validator - yield validate(collection); - } finally { - yield cleanup(server); - } -}); - -add_task(function* test_dupe_reparented_bookmark() { - _("Ensure that a bookmark we consider a dupe from a different parent is handled correctly"); - - let { server, collection } = this.setup(); - - try { - // The parent folder and one bookmark in it. - let {id: folder1_id, guid: folder1_guid } = createFolder(bms.toolbarFolder, "Folder 1"); - let {id: bmk1_id, guid: bmk1_guid} = createBookmark(folder1_id, "http://getfirefox.com/", "Get Firefox!"); - // Another parent folder *with the same name* - let {id: folder2_id, guid: folder2_guid } = createFolder(bms.toolbarFolder, "Folder 1"); - - do_print(`folder1_guid=${folder1_guid}, folder2_guid=${folder2_guid}, bmk1_guid=${bmk1_guid}`); - - engine.sync(); - - // We've added the bookmark, 2 folders plus "menu", "toolbar", "unfiled", and "mobile". - equal(collection.count(), 7); - equal(getFolderChildrenIDs(folder1_id).length, 1); - equal(getFolderChildrenIDs(folder2_id).length, 0); - - // Now create a new incoming record that looks alot like a dupe of the - // item in folder1_guid, but with a record that points to folder2_guid. - let newGUID = Utils.makeGUID(); - let to_apply = { - id: newGUID, - bmkUri: "http://getfirefox.com/", - type: "bookmark", - title: "Get Firefox!", - parentName: "Folder 1", - parentid: folder2_guid, - }; - - collection.insert(newGUID, encryptPayload(to_apply), Date.now() / 1000 + 10); - - _("Syncing so new dupe record is processed"); - engine.lastSync = engine.lastSync - 0.01; - engine.sync(); - - // We should have logically deleted the dupe record. - equal(collection.count(), 8); - ok(getServerRecord(collection, bmk1_guid).deleted); - // and physically removed from the local store. - yield promiseNoLocalItem(bmk1_guid); - // The original folder no longer has the item - equal(getFolderChildrenIDs(folder1_id).length, 0); - // But the second dupe folder does. - equal(getFolderChildrenIDs(folder2_id).length, 1); - - // The record for folder1 on the server should reference neither old or new GUIDs. - let serverRecord1 = getServerRecord(collection, folder1_guid); - ok(!serverRecord1.children.includes(bmk1_guid)); - ok(!serverRecord1.children.includes(newGUID)); - - // The record for folder2 on the server should only reference the new new GUID. - let serverRecord2 = getServerRecord(collection, folder2_guid); - ok(!serverRecord2.children.includes(bmk1_guid)); - ok(serverRecord2.children.includes(newGUID)); - - // and a final sanity check - use the validator - yield validate(collection); - } finally { - yield cleanup(server); - } -}); - -add_task(function* test_dupe_reparented_locally_changed_bookmark() { - _("Ensure that a bookmark with local changes we consider a dupe from a different parent is handled correctly"); - - let { server, collection } = this.setup(); - - try { - // The parent folder and one bookmark in it. - let {id: folder1_id, guid: folder1_guid } = createFolder(bms.toolbarFolder, "Folder 1"); - let {id: bmk1_id, guid: bmk1_guid} = createBookmark(folder1_id, "http://getfirefox.com/", "Get Firefox!"); - // Another parent folder *with the same name* - let {id: folder2_id, guid: folder2_guid } = createFolder(bms.toolbarFolder, "Folder 1"); - - do_print(`folder1_guid=${folder1_guid}, folder2_guid=${folder2_guid}, bmk1_guid=${bmk1_guid}`); - - engine.sync(); - - // We've added the bookmark, 2 folders plus "menu", "toolbar", "unfiled", and "mobile". - equal(collection.count(), 7); - equal(getFolderChildrenIDs(folder1_id).length, 1); - equal(getFolderChildrenIDs(folder2_id).length, 0); - - // Now create a new incoming record that looks alot like a dupe of the - // item in folder1_guid, but with a record that points to folder2_guid. - let newGUID = Utils.makeGUID(); - let to_apply = { - id: newGUID, - bmkUri: "http://getfirefox.com/", - type: "bookmark", - title: "Get Firefox!", - parentName: "Folder 1", - parentid: folder2_guid, - }; - - collection.insert(newGUID, encryptPayload(to_apply), Date.now() / 1000 + 10); - - // Make a change to the bookmark that's a dupe, and set the modification - // time further in the future than the incoming record. This will cause - // us to issue the infamous "DATA LOSS" warning in the logs but cause us - // to *not* apply the incoming record. - engine._tracker.addChangedID(bmk1_guid, Date.now() / 1000 + 60); - - _("Syncing so new dupe record is processed"); - engine.lastSync = engine.lastSync - 0.01; - engine.sync(); - - // We should have logically deleted the dupe record. - equal(collection.count(), 8); - ok(getServerRecord(collection, bmk1_guid).deleted); - // and physically removed from the local store. - yield promiseNoLocalItem(bmk1_guid); - // The original folder still longer has the item - equal(getFolderChildrenIDs(folder1_id).length, 1); - // The second folder does not. - equal(getFolderChildrenIDs(folder2_id).length, 0); - - // The record for folder1 on the server should reference only the GUID. - let serverRecord1 = getServerRecord(collection, folder1_guid); - ok(!serverRecord1.children.includes(bmk1_guid)); - ok(serverRecord1.children.includes(newGUID)); - - // The record for folder2 on the server should reference nothing. - let serverRecord2 = getServerRecord(collection, folder2_guid); - ok(!serverRecord2.children.includes(bmk1_guid)); - ok(!serverRecord2.children.includes(newGUID)); - - // and a final sanity check - use the validator - yield validate(collection); - } finally { - yield cleanup(server); - } -}); - -add_task(function* test_dupe_reparented_to_earlier_appearing_parent_bookmark() { - _("Ensure that a bookmark we consider a dupe from a different parent that " + - "appears in the same sync before the dupe item"); - - let { server, collection } = this.setup(); - - try { - // The parent folder and one bookmark in it. - let {id: folder1_id, guid: folder1_guid } = createFolder(bms.toolbarFolder, "Folder 1"); - let {id: bmk1_id, guid: bmk1_guid} = createBookmark(folder1_id, "http://getfirefox.com/", "Get Firefox!"); - // One more folder we'll use later. - let {id: folder2_id, guid: folder2_guid} = createFolder(bms.toolbarFolder, "A second folder"); - - do_print(`folder1=${folder1_guid}, bmk1=${bmk1_guid} folder2=${folder2_guid}`); - - engine.sync(); - - // We've added the bookmark, 2 folders plus "menu", "toolbar", "unfiled", and "mobile". - equal(collection.count(), 7); - equal(getFolderChildrenIDs(folder1_id).length, 1); - - let newGUID = Utils.makeGUID(); - let newParentGUID = Utils.makeGUID(); - - // Have the new parent appear before the dupe item. - collection.insert(newParentGUID, encryptPayload({ - id: newParentGUID, - type: "folder", - title: "Folder 1", - parentName: "A second folder", - parentid: folder2_guid, - children: [newGUID], - tags: [], - }), Date.now() / 1000 + 10); - - // And also the update to "folder 2" that references the new parent. - collection.insert(folder2_guid, encryptPayload({ - id: folder2_guid, - type: "folder", - title: "A second folder", - parentName: "Bookmarks Toolbar", - parentid: "toolbar", - children: [newParentGUID], - tags: [], - }), Date.now() / 1000 + 10); - - // Now create a new incoming record that looks alot like a dupe of the - // item in folder1_guid, with a record that points to a parent with the - // same name which appeared earlier in this sync. - collection.insert(newGUID, encryptPayload({ - id: newGUID, - bmkUri: "http://getfirefox.com/", - type: "bookmark", - title: "Get Firefox!", - parentName: "Folder 1", - parentid: newParentGUID, - tags: [], - }), Date.now() / 1000 + 10); - - - _("Syncing so new records are processed."); - engine.lastSync = engine.lastSync - 0.01; - engine.sync(); - - // Everything should be parented correctly. - equal(getFolderChildrenIDs(folder1_id).length, 0); - let newParentID = store.idForGUID(newParentGUID); - let newID = store.idForGUID(newGUID); - deepEqual(getFolderChildrenIDs(newParentID), [newID]); - - // Make sure the validator thinks everything is hunky-dory. - yield validate(collection); - } finally { - yield cleanup(server); - } -}); - -add_task(function* test_dupe_reparented_to_later_appearing_parent_bookmark() { - _("Ensure that a bookmark we consider a dupe from a different parent that " + - "doesn't exist locally as we process the child, but does appear in the same sync"); - - let { server, collection } = this.setup(); - - try { - // The parent folder and one bookmark in it. - let {id: folder1_id, guid: folder1_guid } = createFolder(bms.toolbarFolder, "Folder 1"); - let {id: bmk1_id, guid: bmk1_guid} = createBookmark(folder1_id, "http://getfirefox.com/", "Get Firefox!"); - // One more folder we'll use later. - let {id: folder2_id, guid: folder2_guid} = createFolder(bms.toolbarFolder, "A second folder"); - - do_print(`folder1=${folder1_guid}, bmk1=${bmk1_guid} folder2=${folder2_guid}`); - - engine.sync(); - - // We've added the bookmark, 2 folders plus "menu", "toolbar", "unfiled", and "mobile". - equal(collection.count(), 7); - equal(getFolderChildrenIDs(folder1_id).length, 1); - - // Now create a new incoming record that looks alot like a dupe of the - // item in folder1_guid, but with a record that points to a parent with the - // same name, but a non-existing local ID. - let newGUID = Utils.makeGUID(); - let newParentGUID = Utils.makeGUID(); - - collection.insert(newGUID, encryptPayload({ - id: newGUID, - bmkUri: "http://getfirefox.com/", - type: "bookmark", - title: "Get Firefox!", - parentName: "Folder 1", - parentid: newParentGUID, - tags: [], - }), Date.now() / 1000 + 10); - - // Now have the parent appear after (so when the record above is processed - // this is still unknown.) - collection.insert(newParentGUID, encryptPayload({ - id: newParentGUID, - type: "folder", - title: "Folder 1", - parentName: "A second folder", - parentid: folder2_guid, - children: [newGUID], - tags: [], - }), Date.now() / 1000 + 10); - // And also the update to "folder 2" that references the new parent. - collection.insert(folder2_guid, encryptPayload({ - id: folder2_guid, - type: "folder", - title: "A second folder", - parentName: "Bookmarks Toolbar", - parentid: "toolbar", - children: [newParentGUID], - tags: [], - }), Date.now() / 1000 + 10); - - _("Syncing so out-of-order records are processed."); - engine.lastSync = engine.lastSync - 0.01; - engine.sync(); - - // The intended parent did end up existing, so it should be parented - // correctly after de-duplication. - equal(getFolderChildrenIDs(folder1_id).length, 0); - let newParentID = store.idForGUID(newParentGUID); - let newID = store.idForGUID(newGUID); - deepEqual(getFolderChildrenIDs(newParentID), [newID]); - - // Make sure the validator thinks everything is hunky-dory. - yield validate(collection); - } finally { - yield cleanup(server); - } -}); - -add_task(function* test_dupe_reparented_to_future_arriving_parent_bookmark() { - _("Ensure that a bookmark we consider a dupe from a different parent that " + - "doesn't exist locally and doesn't appear in this Sync is handled correctly"); - - let { server, collection } = this.setup(); - - try { - // The parent folder and one bookmark in it. - let {id: folder1_id, guid: folder1_guid } = createFolder(bms.toolbarFolder, "Folder 1"); - let {id: bmk1_id, guid: bmk1_guid} = createBookmark(folder1_id, "http://getfirefox.com/", "Get Firefox!"); - // One more folder we'll use later. - let {id: folder2_id, guid: folder2_guid} = createFolder(bms.toolbarFolder, "A second folder"); - - do_print(`folder1=${folder1_guid}, bmk1=${bmk1_guid} folder2=${folder2_guid}`); - - engine.sync(); - - // We've added the bookmark, 2 folders plus "menu", "toolbar", "unfiled", and "mobile". - equal(collection.count(), 7); - equal(getFolderChildrenIDs(folder1_id).length, 1); - - // Now create a new incoming record that looks alot like a dupe of the - // item in folder1_guid, but with a record that points to a parent with the - // same name, but a non-existing local ID. - let newGUID = Utils.makeGUID(); - let newParentGUID = Utils.makeGUID(); - - collection.insert(newGUID, encryptPayload({ - id: newGUID, - bmkUri: "http://getfirefox.com/", - type: "bookmark", - title: "Get Firefox!", - parentName: "Folder 1", - parentid: newParentGUID, - tags: [], - }), Date.now() / 1000 + 10); - - _("Syncing so new dupe record is processed"); - engine.lastSync = engine.lastSync - 0.01; - engine.sync(); - - // We should have logically deleted the dupe record. - equal(collection.count(), 8); - ok(getServerRecord(collection, bmk1_guid).deleted); - // and physically removed from the local store. - yield promiseNoLocalItem(bmk1_guid); - // The intended parent doesn't exist, so it remains in the original folder - equal(getFolderChildrenIDs(folder1_id).length, 1); - - // The record for folder1 on the server should reference the new GUID. - let serverRecord1 = getServerRecord(collection, folder1_guid); - ok(!serverRecord1.children.includes(bmk1_guid)); - ok(serverRecord1.children.includes(newGUID)); - - // As the incoming parent is missing the item should have been annotated - // with that missing parent. - equal(PlacesUtils.annotations.getItemAnnotation(store.idForGUID(newGUID), "sync/parent"), - newParentGUID); - - // Check the validator. Sadly, this is known to cause a mismatch between - // the server and client views of the tree. - let expected = [ - // We haven't fixed the incoming record that referenced the missing parent. - { name: "orphans", count: 1 }, - ]; - yield validate(collection, expected); - - // Now have the parent magically appear in a later sync - but - // it appears as being in a different parent from our existing "Folder 1", - // so the folder itself isn't duped. - collection.insert(newParentGUID, encryptPayload({ - id: newParentGUID, - type: "folder", - title: "Folder 1", - parentName: "A second folder", - parentid: folder2_guid, - children: [newGUID], - tags: [], - }), Date.now() / 1000 + 10); - // We also queue an update to "folder 2" that references the new parent. - collection.insert(folder2_guid, encryptPayload({ - id: folder2_guid, - type: "folder", - title: "A second folder", - parentName: "Bookmarks Toolbar", - parentid: "toolbar", - children: [newParentGUID], - tags: [], - }), Date.now() / 1000 + 10); - - _("Syncing so missing parent appears"); - engine.lastSync = engine.lastSync - 0.01; - engine.sync(); - - // The intended parent now does exist, so it should have been reparented. - equal(getFolderChildrenIDs(folder1_id).length, 0); - let newParentID = store.idForGUID(newParentGUID); - let newID = store.idForGUID(newGUID); - deepEqual(getFolderChildrenIDs(newParentID), [newID]); - - // validation now has different errors :( - expected = [ - // The validator reports multipleParents because: - // * The incoming record newParentGUID still (and correctly) references - // newGUID as a child. - // * Our original Folder1 was updated to include newGUID when it - // originally de-deuped and couldn't find the parent. - // * When the parent *did* eventually arrive we used the parent annotation - // to correctly reparent - but that reparenting process does not change - // the server record. - // Hence, newGUID is a child of both those server records :( - { name: "multipleParents", count: 1 }, - ]; - yield validate(collection, expected); - - } finally { - yield cleanup(server); - } -}); - -add_task(function* test_dupe_empty_folder() { - _("Ensure that an empty folder we consider a dupe is handled correctly."); - // Empty folders aren't particularly interesting in practice (as that seems - // an edge-case) but duping folders with items is broken - bug 1293163. - let { server, collection } = this.setup(); - - try { - // The folder we will end up duping away. - let {id: folder1_id, guid: folder1_guid } = createFolder(bms.toolbarFolder, "Folder 1"); - - engine.sync(); - - // We've added 1 folder, "menu", "toolbar", "unfiled", and "mobile". - equal(collection.count(), 5); - - // Now create new incoming records that looks alot like a dupe of "Folder 1". - let newFolderGUID = Utils.makeGUID(); - collection.insert(newFolderGUID, encryptPayload({ - id: newFolderGUID, - type: "folder", - title: "Folder 1", - parentName: "Bookmarks Toolbar", - parentid: "toolbar", - children: [], - }), Date.now() / 1000 + 10); - - _("Syncing so new dupe records are processed"); - engine.lastSync = engine.lastSync - 0.01; - engine.sync(); - - yield validate(collection); - - // Collection now has one additional record - the logically deleted dupe. - equal(collection.count(), 6); - // original folder should be logically deleted. - ok(getServerRecord(collection, folder1_guid).deleted); - yield promiseNoLocalItem(folder1_guid); - } finally { - yield cleanup(server); - } -}); -// XXX - TODO - folders with children. Bug 1293163 diff --git a/services/sync/tests/unit/test_bookmark_engine.js b/services/sync/tests/unit/test_bookmark_engine.js index 9de6c5c0d..bd4c740cb 100644 --- a/services/sync/tests/unit/test_bookmark_engine.js +++ b/services/sync/tests/unit/test_bookmark_engine.js @@ -2,10 +2,9 @@ http://creativecommons.org/publicdomain/zero/1.0/ */ Cu.import("resource://gre/modules/PlacesUtils.jsm"); -Cu.import("resource://gre/modules/PlacesSyncUtils.jsm"); Cu.import("resource://gre/modules/BookmarkJSONUtils.jsm"); +Cu.import("resource://services-common/async.js"); Cu.import("resource://gre/modules/Log.jsm"); -Cu.import("resource://services-sync/constants.js"); Cu.import("resource://services-sync/engines.js"); Cu.import("resource://services-sync/engines/bookmarks.js"); Cu.import("resource://services-sync/service.js"); @@ -13,168 +12,9 @@ Cu.import("resource://services-sync/util.js"); Cu.import("resource://testing-common/services/sync/utils.js"); Cu.import("resource://gre/modules/Promise.jsm"); -initTestLogging("Trace"); - Service.engineManager.register(BookmarksEngine); -function* assertChildGuids(folderGuid, expectedChildGuids, message) { - let tree = yield PlacesUtils.promiseBookmarksTree(folderGuid); - let childGuids = tree.children.map(child => child.guid); - deepEqual(childGuids, expectedChildGuids, message); -} - -add_task(function* test_change_during_sync() { - _("Ensure that we track changes made during a sync."); - - let engine = new BookmarksEngine(Service); - let store = engine._store; - let tracker = engine._tracker; - let server = serverForFoo(engine); - new SyncTestingInfrastructure(server.server); - - let collection = server.user("foo").collection("bookmarks"); - - let bz_id = PlacesUtils.bookmarks.insertBookmark( - PlacesUtils.bookmarksMenuFolderId, Utils.makeURI("https://bugzilla.mozilla.org/"), - PlacesUtils.bookmarks.DEFAULT_INDEX, "Bugzilla"); - let bz_guid = yield PlacesUtils.promiseItemGuid(bz_id); - _(`Bugzilla GUID: ${bz_guid}`); - - Svc.Obs.notify("weave:engine:start-tracking"); - - try { - let folder1_id = PlacesUtils.bookmarks.createFolder( - PlacesUtils.bookmarks.toolbarFolder, "Folder 1", 0); - let folder1_guid = store.GUIDForId(folder1_id); - _(`Folder GUID: ${folder1_guid}`); - - let bmk1_id = PlacesUtils.bookmarks.insertBookmark( - folder1_id, Utils.makeURI("http://getthunderbird.com/"), - PlacesUtils.bookmarks.DEFAULT_INDEX, "Get Thunderbird!"); - let bmk1_guid = store.GUIDForId(bmk1_id); - _(`Thunderbird GUID: ${bmk1_guid}`); - - // Sync is synchronous, so, to simulate a bookmark change made during a - // sync, we create a server record that adds a bookmark as a side effect. - let bmk2_guid = "get-firefox1"; // New child of Folder 1, created remotely. - let bmk3_id = -1; // New child of Folder 1, created locally during sync. - let folder2_guid = "folder2-1111"; // New folder, created remotely. - let tagQuery_guid = "tag-query111"; // New tag query child of Folder 2, created remotely. - let bmk4_guid = "example-org1"; // New tagged child of Folder 2, created remotely. - { - // An existing record changed on the server that should not trigger - // another sync when applied. - let bzBmk = new Bookmark("bookmarks", bz_guid); - bzBmk.bmkUri = "https://bugzilla.mozilla.org/"; - bzBmk.description = "New description"; - bzBmk.title = "Bugzilla"; - bzBmk.tags = ["new", "tags"]; - bzBmk.parentName = "Bookmarks Toolbar"; - bzBmk.parentid = "toolbar"; - collection.insert(bz_guid, encryptPayload(bzBmk.cleartext)); - - let remoteFolder = new BookmarkFolder("bookmarks", folder2_guid); - remoteFolder.title = "Folder 2"; - remoteFolder.children = [bmk4_guid, tagQuery_guid]; - remoteFolder.parentName = "Bookmarks Menu"; - remoteFolder.parentid = "menu"; - collection.insert(folder2_guid, encryptPayload(remoteFolder.cleartext)); - - let localFxBmk = new Bookmark("bookmarks", bmk2_guid); - localFxBmk.bmkUri = "http://getfirefox.com/"; - localFxBmk.description = "Firefox is awesome."; - localFxBmk.title = "Get Firefox!"; - localFxBmk.tags = ["firefox", "awesome", "browser"]; - localFxBmk.keyword = "awesome"; - localFxBmk.loadInSidebar = false; - localFxBmk.parentName = "Folder 1"; - localFxBmk.parentid = folder1_guid; - let remoteFxBmk = collection.insert(bmk2_guid, encryptPayload(localFxBmk.cleartext)); - remoteFxBmk.get = function get() { - _("Inserting bookmark into local store"); - bmk3_id = PlacesUtils.bookmarks.insertBookmark( - folder1_id, Utils.makeURI("https://mozilla.org/"), - PlacesUtils.bookmarks.DEFAULT_INDEX, "Mozilla"); - - return ServerWBO.prototype.get.apply(this, arguments); - }; - - // A tag query referencing a nonexistent tag folder, which we should - // create locally when applying the record. - let localTagQuery = new BookmarkQuery("bookmarks", tagQuery_guid); - localTagQuery.bmkUri = "place:type=7&folder=999"; - localTagQuery.title = "Taggy tags"; - localTagQuery.folderName = "taggy"; - localTagQuery.parentName = "Folder 2"; - localTagQuery.parentid = folder2_guid; - collection.insert(tagQuery_guid, encryptPayload(localTagQuery.cleartext)); - - // A bookmark that should appear in the results for the tag query. - let localTaggedBmk = new Bookmark("bookmarks", bmk4_guid); - localTaggedBmk.bmkUri = "https://example.org"; - localTaggedBmk.title = "Tagged bookmark"; - localTaggedBmk.tags = ["taggy"]; - localTaggedBmk.parentName = "Folder 2"; - localTaggedBmk.parentid = folder2_guid; - collection.insert(bmk4_guid, encryptPayload(localTaggedBmk.cleartext)); - } - - yield* assertChildGuids(folder1_guid, [bmk1_guid], "Folder should have 1 child before first sync"); - - _("Perform first sync"); - { - let changes = engine.pullNewChanges(); - deepEqual(changes.ids().sort(), [folder1_guid, bmk1_guid, "toolbar"].sort(), - "Should track bookmark and folder created before first sync"); - yield sync_engine_and_validate_telem(engine, false); - } - - let bmk2_id = store.idForGUID(bmk2_guid); - let bmk3_guid = store.GUIDForId(bmk3_id); - _(`Mozilla GUID: ${bmk3_guid}`); - { - equal(store.GUIDForId(bmk2_id), bmk2_guid, - "Remote bookmark should be applied during first sync"); - ok(bmk3_id > -1, - "Bookmark created during first sync should exist locally"); - ok(!collection.wbo(bmk3_guid), - "Bookmark created during first sync shouldn't be uploaded yet"); - - yield* assertChildGuids(folder1_guid, [bmk1_guid, bmk3_guid, bmk2_guid], - "Folder 1 should have 3 children after first sync"); - yield* assertChildGuids(folder2_guid, [bmk4_guid, tagQuery_guid], - "Folder 2 should have 2 children after first sync"); - let taggedURIs = PlacesUtils.tagging.getURIsForTag("taggy"); - equal(taggedURIs.length, 1, "Should have 1 tagged URI"); - equal(taggedURIs[0].spec, "https://example.org/", - "Synced tagged bookmark should appear in tagged URI list"); - } - - _("Perform second sync"); - { - let changes = engine.pullNewChanges(); - deepEqual(changes.ids().sort(), [bmk3_guid, folder1_guid].sort(), - "Should track bookmark added during last sync and its parent"); - yield sync_engine_and_validate_telem(engine, false); - - ok(collection.wbo(bmk3_guid), - "Bookmark created during first sync should be uploaded during second sync"); - - yield* assertChildGuids(folder1_guid, [bmk1_guid, bmk3_guid, bmk2_guid], - "Folder 1 should have same children after second sync"); - yield* assertChildGuids(folder2_guid, [bmk4_guid, tagQuery_guid], - "Folder 2 should have same children after second sync"); - } - } finally { - store.wipe(); - Svc.Prefs.resetBranch(""); - Service.recordManager.clearCache(); - yield new Promise(resolve => server.stop(resolve)); - Svc.Obs.notify("weave:engine:stop-tracking"); - } -}); - -add_task(function* bad_record_allIDs() { +add_test(function bad_record_allIDs() { let server = new SyncServer(); server.start(); let syncTesting = new SyncTestingInfrastructure(server.server); @@ -192,6 +32,9 @@ add_task(function* bad_record_allIDs() { _("Record is " + badRecordID); _("Type: " + PlacesUtils.bookmarks.getItemType(badRecordID)); + _("Fetching children."); + store._getChildren("toolbar", {}); + _("Fetching all IDs."); let all = store.getAllIDs(); @@ -201,7 +44,49 @@ add_task(function* bad_record_allIDs() { _("Clean up."); PlacesUtils.bookmarks.removeItem(badRecordID); - yield new Promise(r => server.stop(r)); + server.stop(run_next_test); +}); + +add_test(function test_ID_caching() { + let server = new SyncServer(); + server.start(); + let syncTesting = new SyncTestingInfrastructure(server.server); + + _("Ensure that Places IDs are not cached."); + let engine = new BookmarksEngine(Service); + let store = engine._store; + _("All IDs: " + JSON.stringify(store.getAllIDs())); + + let mobileID = store.idForGUID("mobile"); + _("Change the GUID for that item, and drop the mobile anno."); + store._setGUID(mobileID, "abcdefghijkl"); + PlacesUtils.annotations.removeItemAnnotation(mobileID, "mobile/bookmarksRoot"); + + let err; + let newMobileID; + + // With noCreate, we don't find an entry. + try { + newMobileID = store.idForGUID("mobile", true); + _("New mobile ID: " + newMobileID); + } catch (ex) { + err = ex; + _("Error: " + Utils.exceptionStr(err)); + } + + do_check_true(!err); + + // With !noCreate, lookup works, and it's different. + newMobileID = store.idForGUID("mobile", false); + _("New mobile ID: " + newMobileID); + do_check_true(!!newMobileID); + do_check_neq(newMobileID, mobileID); + + // And it's repeatable, even with creation enabled. + do_check_eq(newMobileID, store.idForGUID("mobile", false)); + + do_check_eq(store.GUIDForId(mobileID), "abcdefghijkl"); + server.stop(run_next_test); }); function serverForFoo(engine) { @@ -212,7 +97,7 @@ function serverForFoo(engine) { }); } -add_task(function* test_processIncoming_error_orderChildren() { +add_test(function test_processIncoming_error_orderChildren() { _("Ensure that _orderChildren() is called even when _processIncoming() throws an error."); let engine = new BookmarksEngine(Service); @@ -259,11 +144,11 @@ add_task(function* test_processIncoming_error_orderChildren() { let error; try { - yield sync_engine_and_validate_telem(engine, true) + engine.sync(); } catch(ex) { error = ex; } - ok(!!error); + do_check_true(!!error); // Verify that the bookmark order has been applied. let new_children = store.createRecord(folder1_guid).children; @@ -278,11 +163,11 @@ add_task(function* test_processIncoming_error_orderChildren() { store.wipe(); Svc.Prefs.resetBranch(""); Service.recordManager.clearCache(); - yield new Promise(resolve => server.stop(resolve)); + server.stop(run_next_test); } }); -add_task(function* test_restorePromptsReupload() { +add_task(function test_restorePromptsReupload() { _("Ensure that restoring from a backup will reupload all records."); let engine = new BookmarksEngine(Service); let store = engine._store; @@ -319,7 +204,8 @@ add_task(function* test_restorePromptsReupload() { backupFile.append("t_b_e_" + Date.now() + ".json"); _("Backing up to file " + backupFile.path); - yield BookmarkJSONUtils.exportToFile(backupFile.path); + backupFile.create(Ci.nsILocalFile.NORMAL_FILE_TYPE, 0600); + yield BookmarkJSONUtils.exportToFile(backupFile); _("Create a different record and sync."); let bmk2_id = PlacesUtils.bookmarks.insertBookmark( @@ -331,17 +217,17 @@ add_task(function* test_restorePromptsReupload() { let error; try { - yield sync_engine_and_validate_telem(engine, false); + engine.sync(); } catch(ex) { error = ex; - _("Got error: " + Log.exceptionStr(ex)); + _("Got error: " + Utils.exceptionStr(ex)); } do_check_true(!error); _("Verify that there's only one bookmark on the server, and it's Thunderbird."); // Of course, there's also the Bookmarks Toolbar and Bookmarks Menu... let wbos = collection.keys(function (id) { - return ["menu", "toolbar", "mobile", "unfiled", folder1_guid].indexOf(id) == -1; + return ["menu", "toolbar", "mobile", folder1_guid].indexOf(id) == -1; }); do_check_eq(wbos.length, 1); do_check_eq(wbos[0], bmk2_guid); @@ -371,14 +257,14 @@ add_task(function* test_restorePromptsReupload() { do_check_true(found); _("Have the correct number of IDs locally, too."); - do_check_eq(count, ["menu", "toolbar", "mobile", "unfiled", folder1_id, bmk1_id].length); + do_check_eq(count, ["menu", "toolbar", folder1_id, bmk1_id].length); _("Sync again. This'll wipe bookmarks from the server."); try { - yield sync_engine_and_validate_telem(engine, false); + engine.sync(); } catch(ex) { error = ex; - _("Got error: " + Log.exceptionStr(ex)); + _("Got error: " + Utils.exceptionStr(ex)); } do_check_true(!error); @@ -391,9 +277,7 @@ add_task(function* test_restorePromptsReupload() { let folderWBOs = payloads.filter(function (wbo) { return ((wbo.type == "folder") && (wbo.id != "menu") && - (wbo.id != "toolbar") && - (wbo.id != "unfiled") && - (wbo.id != "mobile")); + (wbo.id != "toolbar")); }); do_check_eq(bookmarkWBOs.length, 1); @@ -420,12 +304,10 @@ function FakeRecord(constructor, r) { for (let x in r) { this[x] = r[x]; } - // Borrow the constructor's conversion functions. - this.toSyncBookmark = constructor.prototype.toSyncBookmark; } // Bug 632287. -add_task(function* test_mismatched_types() { +add_test(function test_mismatched_types() { _("Ensure that handling a record that changes type causes deletion " + "then re-adding."); @@ -437,7 +319,6 @@ add_task(function* test_mismatched_types() { "description":null, "parentid": "toolbar" }; - oldRecord.cleartext = oldRecord; let newRecord = { "id": "l1nZZXfB8nC7", @@ -453,7 +334,6 @@ add_task(function* test_mismatched_types() { "oT74WwV8_j4P", "IztsItWVSo3-"], "parentid": "toolbar" }; - newRecord.cleartext = newRecord; let engine = new BookmarksEngine(Service); let store = engine._store; @@ -466,8 +346,8 @@ add_task(function* test_mismatched_types() { let bms = PlacesUtils.bookmarks; let oldR = new FakeRecord(BookmarkFolder, oldRecord); let newR = new FakeRecord(Livemark, newRecord); - oldR.parentid = PlacesUtils.bookmarks.toolbarGuid; - newR.parentid = PlacesUtils.bookmarks.toolbarGuid; + oldR._parent = PlacesUtils.bookmarks.toolbarFolder; + newR._parent = PlacesUtils.bookmarks.toolbarFolder; store.applyIncoming(oldR); _("Applied old. It's a folder."); @@ -490,11 +370,11 @@ add_task(function* test_mismatched_types() { store.wipe(); Svc.Prefs.resetBranch(""); Service.recordManager.clearCache(); - yield new Promise(r => server.stop(r)); + server.stop(run_next_test); } }); -add_task(function* test_bookmark_guidMap_fail() { +add_test(function test_bookmark_guidMap_fail() { _("Ensure that failures building the GUID map cause early death."); let engine = new BookmarksEngine(Service); @@ -514,9 +394,7 @@ add_task(function* test_bookmark_guidMap_fail() { engine.lastSync = 1; // So we don't back up. // Make building the GUID map fail. - - let pbt = PlacesUtils.promiseBookmarksTree; - PlacesUtils.promiseBookmarksTree = function() { return Promise.reject("Nooo"); }; + store.getAllIDs = function () { throw "Nooo"; }; // Ensure that we throw when accessing _guidMap. engine._syncStartup(); @@ -542,11 +420,26 @@ add_task(function* test_bookmark_guidMap_fail() { } do_check_eq(err, "Nooo"); - PlacesUtils.promiseBookmarksTree = pbt; - yield new Promise(r => server.stop(r)); + server.stop(run_next_test); +}); + +add_test(function test_bookmark_is_taggable() { + let engine = new BookmarksEngine(Service); + let store = engine._store; + + do_check_true(store.isTaggable("bookmark")); + do_check_true(store.isTaggable("microsummary")); + do_check_true(store.isTaggable("query")); + do_check_false(store.isTaggable("folder")); + do_check_false(store.isTaggable("livemark")); + do_check_false(store.isTaggable(null)); + do_check_false(store.isTaggable(undefined)); + do_check_false(store.isTaggable("")); + + run_next_test(); }); -add_task(function* test_bookmark_tag_but_no_uri() { +add_test(function test_bookmark_tag_but_no_uri() { _("Ensure that a bookmark record with tags, but no URI, doesn't throw an exception."); let engine = new BookmarksEngine(Service); @@ -555,43 +448,30 @@ add_task(function* test_bookmark_tag_but_no_uri() { // We're simply checking that no exception is thrown, so // no actual checks in this test. - yield PlacesSyncUtils.bookmarks.insert({ - kind: PlacesSyncUtils.bookmarks.KINDS.BOOKMARK, - syncId: Utils.makeGUID(), - parentSyncId: "toolbar", - url: "http://example.com", - tags: ["foo"], - }); - yield PlacesSyncUtils.bookmarks.insert({ - kind: PlacesSyncUtils.bookmarks.KINDS.BOOKMARK, - syncId: Utils.makeGUID(), - parentSyncId: "toolbar", - url: "http://example.org", - tags: null, - }); - yield PlacesSyncUtils.bookmarks.insert({ - kind: PlacesSyncUtils.bookmarks.KINDS.BOOKMARK, - syncId: Utils.makeGUID(), - url: "about:fake", - parentSyncId: "toolbar", - tags: null, - }); + store._tagURI(null, ["foo"]); + store._tagURI(null, null); + store._tagURI(Utils.makeURI("about:fake"), null); - let record = new FakeRecord(BookmarkFolder, { - parentid: "toolbar", + let record = { + _parent: PlacesUtils.bookmarks.toolbarFolder, id: Utils.makeGUID(), description: "", tags: ["foo"], title: "Taggy tag", type: "folder" - }); + }; + + // Because update() walks the cleartext. + record.cleartext = record; store.create(record); record.tags = ["bar"]; store.update(record); + + run_next_test(); }); -add_task(function* test_misreconciled_root() { +add_test(function test_misreconciled_root() { _("Ensure that we don't reconcile an arbitrary record with a root."); let engine = new BookmarksEngine(Service); @@ -636,9 +516,6 @@ add_task(function* test_misreconciled_root() { _("Applying record."); engine._processIncoming({ - getBatched() { - return this.get(); - }, get: function () { this.recordHandler(encrypted); return {success: true} @@ -655,7 +532,7 @@ add_task(function* test_misreconciled_root() { do_check_eq(parentGUIDBefore, parentGUIDAfter); do_check_eq(parentIDBefore, parentIDAfter); - yield new Promise(r => server.stop(r)); + server.stop(run_next_test); }); function run_test() { diff --git a/services/sync/tests/unit/test_bookmark_invalid.js b/services/sync/tests/unit/test_bookmark_invalid.js deleted file mode 100644 index af476a7f9..000000000 --- a/services/sync/tests/unit/test_bookmark_invalid.js +++ /dev/null @@ -1,63 +0,0 @@ -Cu.import("resource://gre/modules/PlacesUtils.jsm"); -Cu.import("resource://gre/modules/Log.jsm"); -Cu.import("resource://gre/modules/Task.jsm"); -Cu.import("resource://services-sync/engines.js"); -Cu.import("resource://services-sync/engines/bookmarks.js"); -Cu.import("resource://services-sync/service.js"); -Cu.import("resource://services-sync/util.js"); - -Service.engineManager.register(BookmarksEngine); - -var engine = Service.engineManager.get("bookmarks"); -var store = engine._store; -var tracker = engine._tracker; - -add_task(function* test_ignore_invalid_uri() { - _("Ensure that we don't die with invalid bookmarks."); - - // First create a valid bookmark. - let bmid = PlacesUtils.bookmarks.insertBookmark(PlacesUtils.unfiledBookmarksFolderId, - Services.io.newURI("http://example.com/", null, null), - PlacesUtils.bookmarks.DEFAULT_INDEX, - "the title"); - - // Now update moz_places with an invalid url. - yield PlacesUtils.withConnectionWrapper("test_ignore_invalid_uri", Task.async(function* (db) { - yield db.execute( - `UPDATE moz_places SET url = :url, url_hash = hash(:url) - WHERE id = (SELECT b.fk FROM moz_bookmarks b - WHERE b.id = :id LIMIT 1)`, - { id: bmid, url: "" }); - })); - - // Ensure that this doesn't throw even though the DB is now in a bad state (a - // bookmark has an illegal url). - engine._buildGUIDMap(); -}); - -add_task(function* test_ignore_missing_uri() { - _("Ensure that we don't die with a bookmark referencing an invalid bookmark id."); - - // First create a valid bookmark. - let bmid = PlacesUtils.bookmarks.insertBookmark(PlacesUtils.unfiledBookmarksFolderId, - Services.io.newURI("http://example.com/", null, null), - PlacesUtils.bookmarks.DEFAULT_INDEX, - "the title"); - - // Now update moz_bookmarks to reference a non-existing places ID - yield PlacesUtils.withConnectionWrapper("test_ignore_missing_uri", Task.async(function* (db) { - yield db.execute( - `UPDATE moz_bookmarks SET fk = 999999 - WHERE id = :id` - , { id: bmid }); - })); - - // Ensure that this doesn't throw even though the DB is now in a bad state (a - // bookmark has an illegal url). - engine._buildGUIDMap(); -}); - -function run_test() { - initTestLogging('Trace'); - run_next_test(); -} diff --git a/services/sync/tests/unit/test_bookmark_legacy_microsummaries_support.js b/services/sync/tests/unit/test_bookmark_legacy_microsummaries_support.js index 207372ed6..a7e3a4647 100644 --- a/services/sync/tests/unit/test_bookmark_legacy_microsummaries_support.js +++ b/services/sync/tests/unit/test_bookmark_legacy_microsummaries_support.js @@ -85,12 +85,12 @@ function run_test() { do_check_eq(PlacesUtils.bookmarks.getKeywordForBookmark(id), null); do_check_throws( - () => PlacesUtils.annotations.getItemAnnotation(id, GENERATORURI_ANNO), + function () PlacesUtils.annotations.getItemAnnotation(id, GENERATORURI_ANNO), Cr.NS_ERROR_NOT_AVAILABLE ); do_check_throws( - () => PlacesUtils.annotations.getItemAnnotation(id, STATICTITLE_ANNO), + function () PlacesUtils.annotations.getItemAnnotation(id, STATICTITLE_ANNO), Cr.NS_ERROR_NOT_AVAILABLE ); diff --git a/services/sync/tests/unit/test_bookmark_livemarks.js b/services/sync/tests/unit/test_bookmark_livemarks.js index 8adde76d8..d7cda091b 100644 --- a/services/sync/tests/unit/test_bookmark_livemarks.js +++ b/services/sync/tests/unit/test_bookmark_livemarks.js @@ -12,11 +12,11 @@ Cu.import("resource://testing-common/services/common/utils.js"); const DESCRIPTION_ANNO = "bookmarkProperties/description"; -var engine = Service.engineManager.get("bookmarks"); -var store = engine._store; +let engine = Service.engineManager.get("bookmarks"); +let store = engine._store; // Record borrowed from Bug 631361. -var record631361 = { +let record631361 = { id: "M5bwUKK8hPyF", index: 150, modified: 1296768176.49, @@ -103,11 +103,20 @@ add_test(function test_livemark_descriptions() { add_test(function test_livemark_invalid() { _("Livemarks considered invalid by nsLivemarkService are skipped."); + _("Parent is 0, which is invalid. Will be set to unfiled."); + let noParentRec = makeLivemark(record631361.payload, true); + noParentRec._parent = 0; + store.create(noParentRec); + let recID = store.idForGUID(noParentRec.id, true); + do_check_true(recID > 0); + do_check_eq(PlacesUtils.bookmarks.getFolderIdForItem(recID), PlacesUtils.bookmarks.unfiledBookmarksFolder); + _("Parent is unknown. Will be set to unfiled."); let lateParentRec = makeLivemark(record631361.payload, true); let parentGUID = Utils.makeGUID(); lateParentRec.parentid = parentGUID; - do_check_eq(-1, store.idForGUID(parentGUID)); + lateParentRec._parent = store.idForGUID(parentGUID); // Usually done by applyIncoming. + do_check_eq(-1, lateParentRec._parent); store.create(lateParentRec); recID = store.idForGUID(lateParentRec.id, true); @@ -124,7 +133,7 @@ add_test(function test_livemark_invalid() { _("Parent is a Livemark. Will be skipped."); let lmParentRec = makeLivemark(record631361.payload, true); - lmParentRec.parentid = store.GUIDForId(recID); + lmParentRec._parent = recID; store.create(lmParentRec); // No exception, but no creation occurs. do_check_eq(-1, store.idForGUID(lmParentRec.id, true)); diff --git a/services/sync/tests/unit/test_bookmark_order.js b/services/sync/tests/unit/test_bookmark_order.js index 7625a813f..56806dba0 100644 --- a/services/sync/tests/unit/test_bookmark_order.js +++ b/services/sync/tests/unit/test_bookmark_order.js @@ -2,61 +2,53 @@ http://creativecommons.org/publicdomain/zero/1.0/ */ _("Making sure after processing incoming bookmarks, they show up in the right order"); -Cu.import("resource://gre/modules/PlacesUtils.jsm"); -Cu.import("resource://gre/modules/Task.jsm"); +Cu.import("resource://gre/modules/PlacesUtils.jsm", this); Cu.import("resource://services-sync/engines/bookmarks.js"); Cu.import("resource://services-sync/service.js"); Cu.import("resource://services-sync/util.js"); -var check = Task.async(function* (expected, message) { - let root = yield PlacesUtils.promiseBookmarksTree(); +function getBookmarks(folderId) { + let bookmarks = []; + + let pos = 0; + while (true) { + let itemId = PlacesUtils.bookmarks.getIdForItemAt(folderId, pos); + _("Got itemId", itemId, "under", folderId, "at", pos); + if (itemId == -1) + break; + + switch (PlacesUtils.bookmarks.getItemType(itemId)) { + case PlacesUtils.bookmarks.TYPE_BOOKMARK: + bookmarks.push(PlacesUtils.bookmarks.getItemTitle(itemId)); + break; + case PlacesUtils.bookmarks.TYPE_FOLDER: + bookmarks.push(getBookmarks(itemId)); + break; + default: + _("Unsupported item type.."); + } + + pos++; + } + + return bookmarks; +} - let bookmarks = (function mapTree(children) { - return children.map(child => { - let result = { - guid: child.guid, - index: child.index, - }; - if (child.children) { - result.children = mapTree(child.children); - } - if (child.annos) { - let orphanAnno = child.annos.find( - anno => anno.name == "sync/parent"); - if (orphanAnno) { - result.requestedParent = orphanAnno.value; - } - } - return result; - }); - }(root.children)); +function check(expected) { + let bookmarks = getBookmarks(PlacesUtils.bookmarks.unfiledBookmarksFolder); _("Checking if the bookmark structure is", JSON.stringify(expected)); _("Got bookmarks:", JSON.stringify(bookmarks)); - deepEqual(bookmarks, expected); -}); + do_check_true(Utils.deepEquals(bookmarks, expected)); +} -add_task(function* test_bookmark_order() { +function run_test() { let store = new BookmarksEngine(Service)._store; initTestLogging("Trace"); _("Starting with a clean slate of no bookmarks"); store.wipe(); - yield check([{ - guid: PlacesUtils.bookmarks.menuGuid, - index: 0, - }, { - guid: PlacesUtils.bookmarks.toolbarGuid, - index: 1, - }, { - // Index 2 is the tags root. (Root indices depend on the order of the - // `CreateRoot` calls in `Database::CreateBookmarkRoots`). - guid: PlacesUtils.bookmarks.unfiledGuid, - index: 3, - }, { - guid: PlacesUtils.bookmarks.mobileGuid, - index: 4, - }], "clean slate"); + check([]); function bookmark(name, parent) { let bookmark = new Bookmark("http://weave.server/my-bookmark"); @@ -83,447 +75,64 @@ add_task(function* test_bookmark_order() { store._orderChildren(); delete store._childrenToOrder; } - let id10 = "10_aaaaaaaaa"; + _("basic add first bookmark"); - apply(bookmark(id10, "")); - yield check([{ - guid: PlacesUtils.bookmarks.menuGuid, - index: 0, - }, { - guid: PlacesUtils.bookmarks.toolbarGuid, - index: 1, - }, { - guid: PlacesUtils.bookmarks.unfiledGuid, - index: 3, - children: [{ - guid: id10, - index: 0, - }], - }, { - guid: PlacesUtils.bookmarks.mobileGuid, - index: 4, - }], "basic add first bookmark"); - let id20 = "20_aaaaaaaaa"; + apply(bookmark("10", "")); + check(["10"]); + _("basic append behind 10"); - apply(bookmark(id20, "")); - yield check([{ - guid: PlacesUtils.bookmarks.menuGuid, - index: 0, - }, { - guid: PlacesUtils.bookmarks.toolbarGuid, - index: 1, - }, { - guid: PlacesUtils.bookmarks.unfiledGuid, - index: 3, - children: [{ - guid: id10, - index: 0, - }, { - guid: id20, - index: 1, - }], - }, { - guid: PlacesUtils.bookmarks.mobileGuid, - index: 4, - }], "basic append behind 10"); + apply(bookmark("20", "")); + check(["10", "20"]); - let id31 = "31_aaaaaaaaa"; - let id30 = "f30_aaaaaaaa"; _("basic create in folder"); - apply(bookmark(id31, id30)); - let f30 = folder(id30, "", [id31]); + apply(bookmark("31", "f30")); + let f30 = folder("f30", "", ["31"]); apply(f30); - yield check([{ - guid: PlacesUtils.bookmarks.menuGuid, - index: 0, - }, { - guid: PlacesUtils.bookmarks.toolbarGuid, - index: 1, - }, { - guid: PlacesUtils.bookmarks.unfiledGuid, - index: 3, - children: [{ - guid: id10, - index: 0, - }, { - guid: id20, - index: 1, - }, { - guid: id30, - index: 2, - children: [{ - guid: id31, - index: 0, - }], - }], - }, { - guid: PlacesUtils.bookmarks.mobileGuid, - index: 4, - }], "basic create in folder"); + check(["10", "20", ["31"]]); - let id41 = "41_aaaaaaaaa"; - let id40 = "f40_aaaaaaaa"; _("insert missing parent -> append to unfiled"); - apply(bookmark(id41, id40)); - yield check([{ - guid: PlacesUtils.bookmarks.menuGuid, - index: 0, - }, { - guid: PlacesUtils.bookmarks.toolbarGuid, - index: 1, - }, { - guid: PlacesUtils.bookmarks.unfiledGuid, - index: 3, - children: [{ - guid: id10, - index: 0, - }, { - guid: id20, - index: 1, - }, { - guid: id30, - index: 2, - children: [{ - guid: id31, - index: 0, - }], - }, { - guid: id41, - index: 3, - requestedParent: id40, - }], - }, { - guid: PlacesUtils.bookmarks.mobileGuid, - index: 4, - }], "insert missing parent -> append to unfiled"); - - let id42 = "42_aaaaaaaaa"; + apply(bookmark("41", "f40")); + check(["10", "20", ["31"], "41"]); _("insert another missing parent -> append"); - apply(bookmark(id42, id40)); - yield check([{ - guid: PlacesUtils.bookmarks.menuGuid, - index: 0, - }, { - guid: PlacesUtils.bookmarks.toolbarGuid, - index: 1, - }, { - guid: PlacesUtils.bookmarks.unfiledGuid, - index: 3, - children: [{ - guid: id10, - index: 0, - }, { - guid: id20, - index: 1, - }, { - guid: id30, - index: 2, - children: [{ - guid: id31, - index: 0, - }], - }, { - guid: id41, - index: 3, - requestedParent: id40, - }, { - guid: id42, - index: 4, - requestedParent: id40, - }], - }, { - guid: PlacesUtils.bookmarks.mobileGuid, - index: 4, - }], "insert another missing parent -> append"); + apply(bookmark("42", "f40")); + check(["10", "20", ["31"], "41", "42"]); _("insert folder -> move children and followers"); - let f40 = folder(id40, "", [id41, id42]); + let f40 = folder("f40", "", ["41", "42"]); apply(f40); - yield check([{ - guid: PlacesUtils.bookmarks.menuGuid, - index: 0, - }, { - guid: PlacesUtils.bookmarks.toolbarGuid, - index: 1, - }, { - guid: PlacesUtils.bookmarks.unfiledGuid, - index: 3, - children: [{ - guid: id10, - index: 0, - }, { - guid: id20, - index: 1, - }, { - guid: id30, - index: 2, - children: [{ - guid: id31, - index: 0, - }], - }, { - guid: id40, - index: 3, - children: [{ - guid: id41, - index: 0, - }, { - guid: id42, - index: 1, - }] - }], - }, { - guid: PlacesUtils.bookmarks.mobileGuid, - index: 4, - }], "insert folder -> move children and followers"); + check(["10", "20", ["31"], ["41", "42"]]); _("Moving 41 behind 42 -> update f40"); - f40.children = [id42, id41]; + f40.children = ["42", "41"]; apply(f40); - yield check([{ - guid: PlacesUtils.bookmarks.menuGuid, - index: 0, - }, { - guid: PlacesUtils.bookmarks.toolbarGuid, - index: 1, - }, { - guid: PlacesUtils.bookmarks.unfiledGuid, - index: 3, - children: [{ - guid: id10, - index: 0, - }, { - guid: id20, - index: 1, - }, { - guid: id30, - index: 2, - children: [{ - guid: id31, - index: 0, - }], - }, { - guid: id40, - index: 3, - children: [{ - guid: id42, - index: 0, - }, { - guid: id41, - index: 1, - }] - }], - }, { - guid: PlacesUtils.bookmarks.mobileGuid, - index: 4, - }], "Moving 41 behind 42 -> update f40"); + check(["10", "20", ["31"], ["42", "41"]]); _("Moving 10 back to front -> update 10, 20"); - f40.children = [id41, id42]; + f40.children = ["41", "42"]; apply(f40); - yield check([{ - guid: PlacesUtils.bookmarks.menuGuid, - index: 0, - }, { - guid: PlacesUtils.bookmarks.toolbarGuid, - index: 1, - }, { - guid: PlacesUtils.bookmarks.unfiledGuid, - index: 3, - children: [{ - guid: id10, - index: 0, - }, { - guid: id20, - index: 1, - }, { - guid: id30, - index: 2, - children: [{ - guid: id31, - index: 0, - }], - }, { - guid: id40, - index: 3, - children: [{ - guid: id41, - index: 0, - }, { - guid: id42, - index: 1, - }] - }], - }, { - guid: PlacesUtils.bookmarks.mobileGuid, - index: 4, - }], "Moving 10 back to front -> update 10, 20"); + check(["10", "20", ["31"], ["41", "42"]]); _("Moving 20 behind 42 in f40 -> update 50"); - apply(bookmark(id20, id40)); - yield check([{ - guid: PlacesUtils.bookmarks.menuGuid, - index: 0, - }, { - guid: PlacesUtils.bookmarks.toolbarGuid, - index: 1, - }, { - guid: PlacesUtils.bookmarks.unfiledGuid, - index: 3, - children: [{ - guid: id10, - index: 0, - }, { - guid: id30, - index: 1, - children: [{ - guid: id31, - index: 0, - }], - }, { - guid: id40, - index: 2, - children: [{ - guid: id41, - index: 0, - }, { - guid: id42, - index: 1, - }, { - guid: id20, - index: 2, - }] - }], - }, { - guid: PlacesUtils.bookmarks.mobileGuid, - index: 4, - }], "Moving 20 behind 42 in f40 -> update 50"); + apply(bookmark("20", "f40")); + check(["10", ["31"], ["41", "42", "20"]]); _("Moving 10 in front of 31 in f30 -> update 10, f30"); - apply(bookmark(id10, id30)); - f30.children = [id10, id31]; + apply(bookmark("10", "f30")); + f30.children = ["10", "31"]; apply(f30); - yield check([{ - guid: PlacesUtils.bookmarks.menuGuid, - index: 0, - }, { - guid: PlacesUtils.bookmarks.toolbarGuid, - index: 1, - }, { - guid: PlacesUtils.bookmarks.unfiledGuid, - index: 3, - children: [{ - guid: id30, - index: 0, - children: [{ - guid: id10, - index: 0, - }, { - guid: id31, - index: 1, - }], - }, { - guid: id40, - index: 1, - children: [{ - guid: id41, - index: 0, - }, { - guid: id42, - index: 1, - }, { - guid: id20, - index: 2, - }] - }], - }, { - guid: PlacesUtils.bookmarks.mobileGuid, - index: 4, - }], "Moving 10 in front of 31 in f30 -> update 10, f30"); + check([["10", "31"], ["41", "42", "20"]]); _("Moving 20 from f40 to f30 -> update 20, f30"); - apply(bookmark(id20, id30)); - f30.children = [id10, id20, id31]; + apply(bookmark("20", "f30")); + f30.children = ["10", "20", "31"]; apply(f30); - yield check([{ - guid: PlacesUtils.bookmarks.menuGuid, - index: 0, - }, { - guid: PlacesUtils.bookmarks.toolbarGuid, - index: 1, - }, { - guid: PlacesUtils.bookmarks.unfiledGuid, - index: 3, - children: [{ - guid: id30, - index: 0, - children: [{ - guid: id10, - index: 0, - }, { - guid: id20, - index: 1, - }, { - guid: id31, - index: 2, - }], - }, { - guid: id40, - index: 1, - children: [{ - guid: id41, - index: 0, - }, { - guid: id42, - index: 1, - }] - }], - }, { - guid: PlacesUtils.bookmarks.mobileGuid, - index: 4, - }], "Moving 20 from f40 to f30 -> update 20, f30"); + check([["10", "20", "31"], ["41", "42"]]); _("Move 20 back to front -> update 20, f30"); - apply(bookmark(id20, "")); - f30.children = [id10, id31]; + apply(bookmark("20", "")); + f30.children = ["10", "31"]; apply(f30); - yield check([{ - guid: PlacesUtils.bookmarks.menuGuid, - index: 0, - }, { - guid: PlacesUtils.bookmarks.toolbarGuid, - index: 1, - }, { - guid: PlacesUtils.bookmarks.unfiledGuid, - index: 3, - children: [{ - guid: id30, - index: 0, - children: [{ - guid: id10, - index: 0, - }, { - guid: id31, - index: 1, - }], - }, { - guid: id40, - index: 1, - children: [{ - guid: id41, - index: 0, - }, { - guid: id42, - index: 1, - }], - }, { - guid: id20, - index: 2, - }], - }, { - guid: PlacesUtils.bookmarks.mobileGuid, - index: 4, - }], "Move 20 back to front -> update 20, f30"); + check([["10", "31"], ["41", "42"], "20"]); -}); +} diff --git a/services/sync/tests/unit/test_bookmark_places_query_rewriting.js b/services/sync/tests/unit/test_bookmark_places_query_rewriting.js index 0ddf81583..8b764d675 100644 --- a/services/sync/tests/unit/test_bookmark_places_query_rewriting.js +++ b/services/sync/tests/unit/test_bookmark_places_query_rewriting.js @@ -7,54 +7,45 @@ Cu.import("resource://services-sync/engines/bookmarks.js"); Cu.import("resource://services-sync/service.js"); Cu.import("resource://services-sync/util.js"); -var engine = new BookmarksEngine(Service); -var store = engine._store; - -function makeTagRecord(id, uri) { - let tagRecord = new BookmarkQuery("bookmarks", id); - tagRecord.queryId = "MagicTags"; - tagRecord.parentName = "Bookmarks Toolbar"; - tagRecord.bmkUri = uri; - tagRecord.title = "tagtag"; - tagRecord.folderName = "bar"; - tagRecord.parentid = PlacesUtils.bookmarks.toolbarGuid; - return tagRecord; -} +let engine = new BookmarksEngine(Service); +let store = engine._store; function run_test() { initTestLogging("Trace"); Log.repository.getLogger("Sync.Engine.Bookmarks").level = Log.Level.Trace; Log.repository.getLogger("Sync.Store.Bookmarks").level = Log.Level.Trace; + let tagRecord = new BookmarkQuery("bookmarks", "abcdefabcdef"); let uri = "place:folder=499&type=7&queryType=1"; - let tagRecord = makeTagRecord("abcdefabcdef", uri); + tagRecord.queryId = "MagicTags"; + tagRecord.parentName = "Bookmarks Toolbar"; + tagRecord.bmkUri = uri; + tagRecord.title = "tagtag"; + tagRecord.folderName = "bar"; _("Type: " + tagRecord.type); _("Folder name: " + tagRecord.folderName); - store.applyIncoming(tagRecord); + store.preprocessTagQuery(tagRecord); + + _("Verify that the URI has been rewritten."); + do_check_neq(tagRecord.bmkUri, uri); - let tags = PlacesUtils.getFolderContents(PlacesUtils.tagsFolderId).root; + let tags = store._getNode(PlacesUtils.tagsFolderId); + tags.containerOpen = true; let tagID; - try { - for (let i = 0; i < tags.childCount; ++i) { - let child = tags.getChild(i); - if (child.title == "bar") { - tagID = child.itemId; - } - } - } finally { - tags.containerOpen = false; + for (let i = 0; i < tags.childCount; ++i) { + let child = tags.getChild(i); + if (child.title == "bar") + tagID = child.itemId; } + tags.containerOpen = false; _("Tag ID: " + tagID); - let insertedRecord = store.createRecord("abcdefabcdef", "bookmarks"); - do_check_eq(insertedRecord.bmkUri, uri.replace("499", tagID)); + do_check_eq(tagRecord.bmkUri, uri.replace("499", tagID)); _("... but not if the type is wrong."); let wrongTypeURI = "place:folder=499&type=2&queryType=1"; - let wrongTypeRecord = makeTagRecord("fedcbafedcba", wrongTypeURI); - store.applyIncoming(wrongTypeRecord); - - insertedRecord = store.createRecord("fedcbafedcba", "bookmarks"); - do_check_eq(insertedRecord.bmkUri, wrongTypeURI); + tagRecord.bmkUri = wrongTypeURI; + store.preprocessTagQuery(tagRecord); + do_check_eq(tagRecord.bmkUri, wrongTypeURI); } diff --git a/services/sync/tests/unit/test_bookmark_smart_bookmarks.js b/services/sync/tests/unit/test_bookmark_smart_bookmarks.js index 942cf2761..4e9b2834d 100644 --- a/services/sync/tests/unit/test_bookmark_smart_bookmarks.js +++ b/services/sync/tests/unit/test_bookmark_smart_bookmarks.js @@ -16,8 +16,8 @@ var IOService = Cc["@mozilla.org/network/io-service;1"] Service.engineManager.register(BookmarksEngine); -var engine = Service.engineManager.get("bookmarks"); -var store = engine._store; +let engine = Service.engineManager.get("bookmarks"); +let store = engine._store; // Clean up after other tests. Only necessary in XULRunner. store.wipe(); @@ -57,7 +57,7 @@ function serverForFoo(engine) { // Verify that Places smart bookmarks have their annotation uploaded and // handled locally. -add_task(function *test_annotation_uploaded() { +add_test(function test_annotation_uploaded() { let server = serverForFoo(engine); new SyncTestingInfrastructure(server.server); @@ -110,9 +110,9 @@ add_task(function *test_annotation_uploaded() { let collection = server.user("foo").collection("bookmarks"); try { - yield sync_engine_and_validate_telem(engine, false); + engine.sync(); let wbos = collection.keys(function (id) { - return ["menu", "toolbar", "mobile", "unfiled"].indexOf(id) == -1; + return ["menu", "toolbar", "mobile"].indexOf(id) == -1; }); do_check_eq(wbos.length, 1); @@ -141,7 +141,7 @@ add_task(function *test_annotation_uploaded() { do_check_eq(smartBookmarkCount(), startCount); _("Sync. Verify that the downloaded record carries the annotation."); - yield sync_engine_and_validate_telem(engine, false); + engine.sync(); _("Verify that the Places DB now has an annotated bookmark."); _("Our count has increased again."); diff --git a/services/sync/tests/unit/test_bookmark_store.js b/services/sync/tests/unit/test_bookmark_store.js index 902206ba6..53ea433e6 100644 --- a/services/sync/tests/unit/test_bookmark_store.js +++ b/services/sync/tests/unit/test_bookmark_store.js @@ -11,17 +11,17 @@ const PARENT_ANNO = "sync/parent"; Service.engineManager.register(BookmarksEngine); -var engine = Service.engineManager.get("bookmarks"); -var store = engine._store; -var tracker = engine._tracker; +let engine = Service.engineManager.get("bookmarks"); +let store = engine._store; +let tracker = engine._tracker; // Don't write some persistence files asynchronously. tracker.persistChangedIDs = false; -var fxuri = Utils.makeURI("http://getfirefox.com/"); -var tburi = Utils.makeURI("http://getthunderbird.com/"); +let fxuri = Utils.makeURI("http://getfirefox.com/"); +let tburi = Utils.makeURI("http://getthunderbird.com/"); -add_task(function* test_ignore_specials() { +add_test(function test_ignore_specials() { _("Ensure that we can't delete bookmark roots."); // Belt... @@ -30,7 +30,6 @@ add_task(function* test_ignore_specials() { do_check_neq(null, store.idForGUID("toolbar")); store.applyIncoming(record); - yield store.deletePending(); // Ensure that the toolbar exists. do_check_neq(null, store.idForGUID("toolbar")); @@ -40,11 +39,11 @@ add_task(function* test_ignore_specials() { // Braces... store.remove(record); - yield store.deletePending(); do_check_neq(null, store.idForGUID("toolbar")); engine._buildGUIDMap(); store.wipe(); + run_next_test(); }); add_test(function test_bookmark_create() { @@ -81,8 +80,8 @@ add_test(function test_bookmark_create() { _("Have the store create a new record object. Verify that it has the same data."); let newrecord = store.createRecord(fxrecord.id); do_check_true(newrecord instanceof Bookmark); - for (let property of ["type", "bmkUri", "description", "title", - "keyword", "parentName", "parentid"]) { + for each (let property in ["type", "bmkUri", "description", "title", + "keyword", "parentName", "parentid"]) { do_check_eq(newrecord[property], fxrecord[property]); } do_check_true(Utils.deepEquals(newrecord.tags.sort(), @@ -167,7 +166,7 @@ add_test(function test_bookmark_createRecord() { _("Verify that the record is created accordingly."); let record = store.createRecord(bmk1_guid); - do_check_eq(record.title, ""); + do_check_eq(record.title, null); do_check_eq(record.description, null); do_check_eq(record.keyword, null); @@ -198,7 +197,7 @@ add_test(function test_folder_create() { _("Have the store create a new record object. Verify that it has the same data."); let newrecord = store.createRecord(folder.id); do_check_true(newrecord instanceof BookmarkFolder); - for (let property of ["title", "parentName", "parentid"]) + for each (let property in ["title", "parentName", "parentid"]) do_check_eq(newrecord[property], folder[property]); _("Folders have high sort index to ensure they're synced first."); @@ -244,7 +243,7 @@ add_test(function test_folder_createRecord() { } }); -add_task(function* test_deleted() { +add_test(function test_deleted() { try { _("Create a bookmark that will be deleted."); let bmk1_id = PlacesUtils.bookmarks.insertBookmark( @@ -256,7 +255,7 @@ add_task(function* test_deleted() { let record = new PlacesItem("bookmarks", bmk1_guid); record.deleted = true; store.applyIncoming(record); - yield store.deletePending(); + _("Ensure it has been deleted."); let error; try { @@ -272,6 +271,7 @@ add_task(function* test_deleted() { } finally { _("Clean up."); store.wipe(); + run_next_test(); } }); @@ -428,106 +428,6 @@ add_test(function test_empty_query_doesnt_die() { run_next_test(); }); -function assertDeleted(id) { - let error; - try { - PlacesUtils.bookmarks.getItemType(id); - } catch (e) { - error = e; - } - equal(error.result, Cr.NS_ERROR_ILLEGAL_VALUE) -} - -add_task(function* test_delete_buffering() { - store.wipe(); - try { - _("Create a folder with two bookmarks."); - let folder = new BookmarkFolder("bookmarks", "testfolder-1"); - folder.parentName = "Bookmarks Toolbar"; - folder.parentid = "toolbar"; - folder.title = "Test Folder"; - store.applyIncoming(folder); - - - let fxRecord = new Bookmark("bookmarks", "get-firefox1"); - fxRecord.bmkUri = fxuri.spec; - fxRecord.title = "Get Firefox!"; - fxRecord.parentName = "Test Folder"; - fxRecord.parentid = "testfolder-1"; - - let tbRecord = new Bookmark("bookmarks", "get-tndrbrd1"); - tbRecord.bmkUri = tburi.spec; - tbRecord.title = "Get Thunderbird!"; - tbRecord.parentName = "Test Folder"; - tbRecord.parentid = "testfolder-1"; - - store.applyIncoming(fxRecord); - store.applyIncoming(tbRecord); - - let folderId = store.idForGUID(folder.id); - let fxRecordId = store.idForGUID(fxRecord.id); - let tbRecordId = store.idForGUID(tbRecord.id); - - _("Check everything was created correctly."); - - equal(PlacesUtils.bookmarks.getItemType(fxRecordId), - PlacesUtils.bookmarks.TYPE_BOOKMARK); - equal(PlacesUtils.bookmarks.getItemType(tbRecordId), - PlacesUtils.bookmarks.TYPE_BOOKMARK); - equal(PlacesUtils.bookmarks.getItemType(folderId), - PlacesUtils.bookmarks.TYPE_FOLDER); - - equal(PlacesUtils.bookmarks.getFolderIdForItem(fxRecordId), folderId); - equal(PlacesUtils.bookmarks.getFolderIdForItem(tbRecordId), folderId); - equal(PlacesUtils.bookmarks.getFolderIdForItem(folderId), - PlacesUtils.bookmarks.toolbarFolder); - - _("Delete the folder and one bookmark."); - - let deleteFolder = new PlacesItem("bookmarks", "testfolder-1"); - deleteFolder.deleted = true; - - let deleteFxRecord = new PlacesItem("bookmarks", "get-firefox1"); - deleteFxRecord.deleted = true; - - store.applyIncoming(deleteFolder); - store.applyIncoming(deleteFxRecord); - - _("Check that we haven't deleted them yet, but that the deletions are queued"); - // these will throw if we've deleted them - equal(PlacesUtils.bookmarks.getItemType(fxRecordId), - PlacesUtils.bookmarks.TYPE_BOOKMARK); - - equal(PlacesUtils.bookmarks.getItemType(folderId), - PlacesUtils.bookmarks.TYPE_FOLDER); - - equal(PlacesUtils.bookmarks.getFolderIdForItem(fxRecordId), folderId); - - ok(store._foldersToDelete.has(folder.id)); - ok(store._atomsToDelete.has(fxRecord.id)); - ok(!store._atomsToDelete.has(tbRecord.id)); - - _("Process pending deletions and ensure that the right things are deleted."); - let updatedGuids = yield store.deletePending(); - - deepEqual(updatedGuids.sort(), ["get-tndrbrd1", "toolbar"]); - - assertDeleted(fxRecordId); - assertDeleted(folderId); - - ok(!store._foldersToDelete.has(folder.id)); - ok(!store._atomsToDelete.has(fxRecord.id)); - - equal(PlacesUtils.bookmarks.getFolderIdForItem(tbRecordId), - PlacesUtils.bookmarks.toolbarFolder); - - } finally { - _("Clean up."); - store.wipe(); - } -}); - - function run_test() { initTestLogging('Trace'); run_next_test(); diff --git a/services/sync/tests/unit/test_bookmark_tracker.js b/services/sync/tests/unit/test_bookmark_tracker.js index 9b9242579..6060fbae4 100644 --- a/services/sync/tests/unit/test_bookmark_tracker.js +++ b/services/sync/tests/unit/test_bookmark_tracker.js @@ -2,80 +2,24 @@ http://creativecommons.org/publicdomain/zero/1.0/ */ Cu.import("resource://gre/modules/PlacesUtils.jsm"); -Cu.import("resource://gre/modules/PlacesSyncUtils.jsm"); -Cu.import("resource://gre/modules/Task.jsm"); Cu.import("resource://services-sync/constants.js"); Cu.import("resource://services-sync/engines/bookmarks.js"); Cu.import("resource://services-sync/engines.js"); Cu.import("resource://services-sync/service.js"); Cu.import("resource://services-sync/util.js"); -Cu.import("resource:///modules/PlacesUIUtils.jsm"); Service.engineManager.register(BookmarksEngine); -var engine = Service.engineManager.get("bookmarks"); -var store = engine._store; -var tracker = engine._tracker; +let engine = Service.engineManager.get("bookmarks"); +let store = engine._store; +let tracker = engine._tracker; store.wipe(); tracker.persistChangedIDs = false; -const DAY_IN_MS = 24 * 60 * 60 * 1000; - -// Test helpers. -function* verifyTrackerEmpty() { - let changes = engine.pullNewChanges(); - equal(changes.count(), 0); - equal(tracker.score, 0); -} - -function* resetTracker() { - tracker.clearChangedIDs(); - tracker.resetScore(); -} - -function* cleanup() { - store.wipe(); - yield resetTracker(); - yield stopTracking(); -} - -// startTracking is a signal that the test wants to notice things that happen -// after this is called (ie, things already tracked should be discarded.) -function* startTracking() { - Svc.Obs.notify("weave:engine:start-tracking"); -} - -function* stopTracking() { - Svc.Obs.notify("weave:engine:stop-tracking"); -} - -function* verifyTrackedItems(tracked) { - let changes = engine.pullNewChanges(); - let trackedIDs = new Set(changes.ids()); - for (let guid of tracked) { - ok(changes.has(guid), `${guid} should be tracked`); - ok(changes.getModifiedTimestamp(guid) > 0, - `${guid} should have a modified time`); - trackedIDs.delete(guid); - } - equal(trackedIDs.size, 0, `Unhandled tracked IDs: ${ - JSON.stringify(Array.from(trackedIDs))}`); -} - -function* verifyTrackedCount(expected) { - let changes = engine.pullNewChanges(); - equal(changes.count(), expected); -} - -// Copied from PlacesSyncUtils.jsm. -function findAnnoItems(anno, val) { - let annos = PlacesUtils.annotations; - return annos.getItemsWithAnnotation(anno, {}).filter(id => - annos.getItemAnnotation(id, anno) == val); -} - -add_task(function* test_tracking() { - _("Test starting and stopping the tracker"); +function test_tracking() { + _("Verify we've got an empty tracker to work with."); + let tracker = engine._tracker; + do_check_empty(tracker.changedIDs); let folder = PlacesUtils.bookmarks.createFolder( PlacesUtils.bookmarks.bookmarksMenuFolder, @@ -89,630 +33,60 @@ add_task(function* test_tracking() { try { _("Create bookmark. Won't show because we haven't started tracking yet"); createBmk(); - yield verifyTrackedCount(0); + do_check_empty(tracker.changedIDs); do_check_eq(tracker.score, 0); _("Tell the tracker to start tracking changes."); - yield startTracking(); + Svc.Obs.notify("weave:engine:start-tracking"); createBmk(); // We expect two changed items because the containing folder // changed as well (new child). - yield verifyTrackedCount(2); + do_check_attribute_count(tracker.changedIDs, 2); do_check_eq(tracker.score, SCORE_INCREMENT_XLARGE * 2); _("Notifying twice won't do any harm."); - yield startTracking(); + Svc.Obs.notify("weave:engine:start-tracking"); createBmk(); - yield verifyTrackedCount(3); + do_check_attribute_count(tracker.changedIDs, 3); do_check_eq(tracker.score, SCORE_INCREMENT_XLARGE * 4); _("Let's stop tracking again."); - yield resetTracker(); - yield stopTracking(); + tracker.clearChangedIDs(); + tracker.resetScore(); + Svc.Obs.notify("weave:engine:stop-tracking"); createBmk(); - yield verifyTrackedCount(0); + do_check_empty(tracker.changedIDs); do_check_eq(tracker.score, 0); _("Notifying twice won't do any harm."); - yield stopTracking(); + Svc.Obs.notify("weave:engine:stop-tracking"); createBmk(); - yield verifyTrackedCount(0); + do_check_empty(tracker.changedIDs); do_check_eq(tracker.score, 0); } finally { _("Clean up."); - yield cleanup(); - } -}); - -add_task(function* test_batch_tracking() { - _("Test tracker does the correct thing during and after a places 'batch'"); - - yield startTracking(); - - PlacesUtils.bookmarks.runInBatchMode({ - runBatched: function() { - let folder = PlacesUtils.bookmarks.createFolder( - PlacesUtils.bookmarks.bookmarksMenuFolder, - "Test Folder", PlacesUtils.bookmarks.DEFAULT_INDEX); - // We should be tracking the new folder and its parent (and need to jump - // through blocking hoops...) - Async.promiseSpinningly(Task.spawn(verifyTrackedCount(2))); - // But not have bumped the score. - do_check_eq(tracker.score, 0); - } - }, null); - - // Out of batch mode - tracker should be the same, but score should be up. - yield verifyTrackedCount(2); - do_check_eq(tracker.score, SCORE_INCREMENT_XLARGE); - yield cleanup(); -}); - -add_task(function* test_nested_batch_tracking() { - _("Test tracker does the correct thing if a places 'batch' is nested"); - - yield startTracking(); - - PlacesUtils.bookmarks.runInBatchMode({ - runBatched: function() { - - PlacesUtils.bookmarks.runInBatchMode({ - runBatched: function() { - let folder = PlacesUtils.bookmarks.createFolder( - PlacesUtils.bookmarks.bookmarksMenuFolder, - "Test Folder", PlacesUtils.bookmarks.DEFAULT_INDEX); - // We should be tracking the new folder and its parent (and need to jump - // through blocking hoops...) - Async.promiseSpinningly(Task.spawn(verifyTrackedCount(2))); - // But not have bumped the score. - do_check_eq(tracker.score, 0); - } - }, null); - _("inner batch complete."); - // should still not have a score as the outer batch is pending. - do_check_eq(tracker.score, 0); - } - }, null); - - // Out of both batches - tracker should be the same, but score should be up. - yield verifyTrackedCount(2); - do_check_eq(tracker.score, SCORE_INCREMENT_XLARGE); - yield cleanup(); -}); - -add_task(function* test_tracker_sql_batching() { - _("Test tracker does the correct thing when it is forced to batch SQL queries"); - - const SQLITE_MAX_VARIABLE_NUMBER = 999; - let numItems = SQLITE_MAX_VARIABLE_NUMBER * 2 + 10; - let createdIDs = []; - - yield startTracking(); - - PlacesUtils.bookmarks.runInBatchMode({ - runBatched: function() { - for (let i = 0; i < numItems; i++) { - let syncBmkID = PlacesUtils.bookmarks.insertBookmark( - PlacesUtils.bookmarks.unfiledBookmarksFolder, - Utils.makeURI("https://example.org/" + i), - PlacesUtils.bookmarks.DEFAULT_INDEX, - "Sync Bookmark " + i); - createdIDs.push(syncBmkID); - } - } - }, null); - - do_check_eq(createdIDs.length, numItems); - yield verifyTrackedCount(numItems + 1); // the folder is also tracked. - yield cleanup(); -}); - -add_task(function* test_onItemAdded() { - _("Items inserted via the synchronous bookmarks API should be tracked"); - - try { - yield startTracking(); - - _("Insert a folder using the sync API"); - let syncFolderID = PlacesUtils.bookmarks.createFolder( - PlacesUtils.bookmarks.bookmarksMenuFolder, "Sync Folder", - PlacesUtils.bookmarks.DEFAULT_INDEX); - let syncFolderGUID = engine._store.GUIDForId(syncFolderID); - yield verifyTrackedItems(["menu", syncFolderGUID]); - do_check_eq(tracker.score, SCORE_INCREMENT_XLARGE * 2); - - yield resetTracker(); - yield startTracking(); - - _("Insert a bookmark using the sync API"); - let syncBmkID = PlacesUtils.bookmarks.insertBookmark(syncFolderID, - Utils.makeURI("https://example.org/sync"), - PlacesUtils.bookmarks.DEFAULT_INDEX, - "Sync Bookmark"); - let syncBmkGUID = engine._store.GUIDForId(syncBmkID); - yield verifyTrackedItems([syncFolderGUID, syncBmkGUID]); - do_check_eq(tracker.score, SCORE_INCREMENT_XLARGE * 2); - - yield resetTracker(); - yield startTracking(); - - _("Insert a separator using the sync API"); - let syncSepID = PlacesUtils.bookmarks.insertSeparator( - PlacesUtils.bookmarks.bookmarksMenuFolder, - PlacesUtils.bookmarks.getItemIndex(syncFolderID)); - let syncSepGUID = engine._store.GUIDForId(syncSepID); - yield verifyTrackedItems(["menu", syncSepGUID]); - do_check_eq(tracker.score, SCORE_INCREMENT_XLARGE * 2); - } finally { - _("Clean up."); - yield cleanup(); - } -}); - -add_task(function* test_async_onItemAdded() { - _("Items inserted via the asynchronous bookmarks API should be tracked"); - - try { - yield startTracking(); - - _("Insert a folder using the async API"); - let asyncFolder = yield PlacesUtils.bookmarks.insert({ - type: PlacesUtils.bookmarks.TYPE_FOLDER, - parentGuid: PlacesUtils.bookmarks.menuGuid, - title: "Async Folder", - }); - yield verifyTrackedItems(["menu", asyncFolder.guid]); - do_check_eq(tracker.score, SCORE_INCREMENT_XLARGE * 2); - - yield resetTracker(); - yield startTracking(); - - _("Insert a bookmark using the async API"); - let asyncBmk = yield PlacesUtils.bookmarks.insert({ - type: PlacesUtils.bookmarks.TYPE_BOOKMARK, - parentGuid: asyncFolder.guid, - url: "https://example.org/async", - title: "Async Bookmark", - }); - yield verifyTrackedItems([asyncFolder.guid, asyncBmk.guid]); - do_check_eq(tracker.score, SCORE_INCREMENT_XLARGE * 2); - - yield resetTracker(); - yield startTracking(); - - _("Insert a separator using the async API"); - let asyncSep = yield PlacesUtils.bookmarks.insert({ - type: PlacesUtils.bookmarks.TYPE_SEPARATOR, - parentGuid: PlacesUtils.bookmarks.menuGuid, - index: asyncFolder.index, - }); - yield verifyTrackedItems(["menu", asyncSep.guid]); - do_check_eq(tracker.score, SCORE_INCREMENT_XLARGE * 2); - } finally { - _("Clean up."); - yield cleanup(); - } -}); - -add_task(function* test_async_onItemChanged() { - _("Items updated using the asynchronous bookmarks API should be tracked"); - - try { - yield stopTracking(); - - _("Insert a bookmark"); - let fxBmk = yield PlacesUtils.bookmarks.insert({ - type: PlacesUtils.bookmarks.TYPE_BOOKMARK, - parentGuid: PlacesUtils.bookmarks.menuGuid, - url: "http://getfirefox.com", - title: "Get Firefox!", - }); - _(`Firefox GUID: ${fxBmk.guid}`); - - yield startTracking(); - - _("Update the bookmark using the async API"); - yield PlacesUtils.bookmarks.update({ - guid: fxBmk.guid, - title: "Download Firefox", - url: "https://www.mozilla.org/firefox", - // PlacesUtils.bookmarks.update rejects last modified dates older than - // the added date. - lastModified: new Date(Date.now() + DAY_IN_MS), - }); - - yield verifyTrackedItems([fxBmk.guid]); - do_check_eq(tracker.score, SCORE_INCREMENT_XLARGE * 3); - } finally { - _("Clean up."); - yield cleanup(); - } -}); - -add_task(function* test_onItemChanged_itemDates() { - _("Changes to item dates should be tracked"); - - try { - yield stopTracking(); - - _("Insert a bookmark"); - let fx_id = PlacesUtils.bookmarks.insertBookmark( - PlacesUtils.bookmarks.bookmarksMenuFolder, - Utils.makeURI("http://getfirefox.com"), - PlacesUtils.bookmarks.DEFAULT_INDEX, - "Get Firefox!"); - let fx_guid = engine._store.GUIDForId(fx_id); - _(`Firefox GUID: ${fx_guid}`); - - yield startTracking(); - - _("Reset the bookmark's added date"); - // Convert to microseconds for PRTime. - let dateAdded = (Date.now() - DAY_IN_MS) * 1000; - PlacesUtils.bookmarks.setItemDateAdded(fx_id, dateAdded); - yield verifyTrackedItems([fx_guid]); - do_check_eq(tracker.score, SCORE_INCREMENT_XLARGE); - yield resetTracker(); - - _("Set the bookmark's last modified date"); - let dateModified = Date.now() * 1000; - PlacesUtils.bookmarks.setItemLastModified(fx_id, dateModified); - yield verifyTrackedItems([fx_guid]); - do_check_eq(tracker.score, SCORE_INCREMENT_XLARGE); - } finally { - _("Clean up."); - yield cleanup(); - } -}); - -add_task(function* test_onItemChanged_changeBookmarkURI() { - _("Changes to bookmark URIs should be tracked"); - - try { - yield stopTracking(); - - _("Insert a bookmark"); - let fx_id = PlacesUtils.bookmarks.insertBookmark( - PlacesUtils.bookmarks.bookmarksMenuFolder, - Utils.makeURI("http://getfirefox.com"), - PlacesUtils.bookmarks.DEFAULT_INDEX, - "Get Firefox!"); - let fx_guid = engine._store.GUIDForId(fx_id); - _(`Firefox GUID: ${fx_guid}`); - - _("Set a tracked annotation to make sure we only notify once"); - PlacesUtils.annotations.setItemAnnotation( - fx_id, PlacesSyncUtils.bookmarks.DESCRIPTION_ANNO, "A test description", 0, - PlacesUtils.annotations.EXPIRE_NEVER); - - yield startTracking(); - - _("Change the bookmark's URI"); - PlacesUtils.bookmarks.changeBookmarkURI(fx_id, - Utils.makeURI("https://www.mozilla.org/firefox")); - yield verifyTrackedItems([fx_guid]); - do_check_eq(tracker.score, SCORE_INCREMENT_XLARGE); - } finally { - _("Clean up."); - yield cleanup(); - } -}); - -add_task(function* test_onItemTagged() { - _("Items tagged using the synchronous API should be tracked"); - - try { - yield stopTracking(); - - _("Create a folder"); - let folder = PlacesUtils.bookmarks.createFolder( - PlacesUtils.bookmarks.bookmarksMenuFolder, "Parent", - PlacesUtils.bookmarks.DEFAULT_INDEX); - let folderGUID = engine._store.GUIDForId(folder); - _("Folder ID: " + folder); - _("Folder GUID: " + folderGUID); - - _("Track changes to tags"); - let uri = Utils.makeURI("http://getfirefox.com"); - let b = PlacesUtils.bookmarks.insertBookmark( - folder, uri, - PlacesUtils.bookmarks.DEFAULT_INDEX, "Get Firefox!"); - let bGUID = engine._store.GUIDForId(b); - _("New item is " + b); - _("GUID: " + bGUID); - - yield startTracking(); - - _("Tag the item"); - PlacesUtils.tagging.tagURI(uri, ["foo"]); - - // bookmark should be tracked, folder should not be. - yield verifyTrackedItems([bGUID]); - do_check_eq(tracker.score, SCORE_INCREMENT_XLARGE * 5); - } finally { - _("Clean up."); - yield cleanup(); - } -}); - -add_task(function* test_onItemUntagged() { - _("Items untagged using the synchronous API should be tracked"); - - try { - yield stopTracking(); - - _("Insert tagged bookmarks"); - let uri = Utils.makeURI("http://getfirefox.com"); - let fx1ID = PlacesUtils.bookmarks.insertBookmark( - PlacesUtils.bookmarks.bookmarksMenuFolder, uri, - PlacesUtils.bookmarks.DEFAULT_INDEX, "Get Firefox!"); - let fx1GUID = engine._store.GUIDForId(fx1ID); - // Different parent and title; same URL. - let fx2ID = PlacesUtils.bookmarks.insertBookmark( - PlacesUtils.bookmarks.toolbarFolder, uri, - PlacesUtils.bookmarks.DEFAULT_INDEX, "Download Firefox"); - let fx2GUID = engine._store.GUIDForId(fx2ID); - PlacesUtils.tagging.tagURI(uri, ["foo"]); - - yield startTracking(); - - _("Remove the tag"); - PlacesUtils.tagging.untagURI(uri, ["foo"]); - - yield verifyTrackedItems([fx1GUID, fx2GUID]); - do_check_eq(tracker.score, SCORE_INCREMENT_XLARGE * 2); - } finally { - _("Clean up."); - yield cleanup(); - } -}); - -add_task(function* test_async_onItemUntagged() { - _("Items untagged using the asynchronous API should be tracked"); - - try { - yield stopTracking(); - - _("Insert tagged bookmarks"); - let fxBmk1 = yield PlacesUtils.bookmarks.insert({ - type: PlacesUtils.bookmarks.TYPE_BOOKMARK, - parentGuid: PlacesUtils.bookmarks.menuGuid, - url: "http://getfirefox.com", - title: "Get Firefox!", - }); - let fxBmk2 = yield PlacesUtils.bookmarks.insert({ - type: PlacesUtils.bookmarks.TYPE_BOOKMARK, - parentGuid: PlacesUtils.bookmarks.toolbarGuid, - url: "http://getfirefox.com", - title: "Download Firefox", - }); - let tag = yield PlacesUtils.bookmarks.insert({ - type: PlacesUtils.bookmarks.TYPE_FOLDER, - parentGuid: PlacesUtils.bookmarks.tagsGuid, - title: "some tag", - }); - let fxTag = yield PlacesUtils.bookmarks.insert({ - type: PlacesUtils.bookmarks.TYPE_BOOKMARK, - parentGuid: tag.guid, - url: "http://getfirefox.com", - }); - - yield startTracking(); - - _("Remove the tag using the async bookmarks API"); - yield PlacesUtils.bookmarks.remove(fxTag.guid); - - yield verifyTrackedItems([fxBmk1.guid, fxBmk2.guid]); - do_check_eq(tracker.score, SCORE_INCREMENT_XLARGE * 2); - } finally { - _("Clean up."); - yield cleanup(); - } -}); - -add_task(function* test_async_onItemTagged() { - _("Items tagged using the asynchronous API should be tracked"); - - try { - yield stopTracking(); - - _("Insert untagged bookmarks"); - let folder1 = yield PlacesUtils.bookmarks.insert({ - type: PlacesUtils.bookmarks.TYPE_FOLDER, - parentGuid: PlacesUtils.bookmarks.menuGuid, - title: "Folder 1", - }); - let fxBmk1 = yield PlacesUtils.bookmarks.insert({ - type: PlacesUtils.bookmarks.TYPE_BOOKMARK, - parentGuid: folder1.guid, - url: "http://getfirefox.com", - title: "Get Firefox!", - }); - let folder2 = yield PlacesUtils.bookmarks.insert({ - type: PlacesUtils.bookmarks.TYPE_FOLDER, - parentGuid: PlacesUtils.bookmarks.menuGuid, - title: "Folder 2", - }); - // Different parent and title; same URL. - let fxBmk2 = yield PlacesUtils.bookmarks.insert({ - type: PlacesUtils.bookmarks.TYPE_BOOKMARK, - parentGuid: folder2.guid, - url: "http://getfirefox.com", - title: "Download Firefox", - }); - - yield startTracking(); - - // This will change once tags are moved into a separate table (bug 424160). - // We specifically test this case because Bookmarks.jsm updates tagged - // bookmarks and notifies observers. - _("Insert a tag using the async bookmarks API"); - let tag = yield PlacesUtils.bookmarks.insert({ - type: PlacesUtils.bookmarks.TYPE_FOLDER, - parentGuid: PlacesUtils.bookmarks.tagsGuid, - title: "some tag", - }); - - _("Tag an item using the async bookmarks API"); - yield PlacesUtils.bookmarks.insert({ - type: PlacesUtils.bookmarks.TYPE_BOOKMARK, - parentGuid: tag.guid, - url: "http://getfirefox.com", - }); - - yield verifyTrackedItems([fxBmk1.guid, fxBmk2.guid]); - do_check_eq(tracker.score, SCORE_INCREMENT_XLARGE * 6); - } finally { - _("Clean up."); - yield cleanup(); - } -}); - -add_task(function* test_onItemKeywordChanged() { - _("Keyword changes via the synchronous API should be tracked"); - - try { - yield stopTracking(); - let folder = PlacesUtils.bookmarks.createFolder( - PlacesUtils.bookmarks.bookmarksMenuFolder, "Parent", - PlacesUtils.bookmarks.DEFAULT_INDEX); - let folderGUID = engine._store.GUIDForId(folder); - _("Track changes to keywords"); - let uri = Utils.makeURI("http://getfirefox.com"); - let b = PlacesUtils.bookmarks.insertBookmark( - folder, uri, - PlacesUtils.bookmarks.DEFAULT_INDEX, "Get Firefox!"); - let bGUID = engine._store.GUIDForId(b); - _("New item is " + b); - _("GUID: " + bGUID); - - yield startTracking(); - - _("Give the item a keyword"); - PlacesUtils.bookmarks.setKeywordForBookmark(b, "the_keyword"); - - // bookmark should be tracked, folder should not be. - yield verifyTrackedItems([bGUID]); - do_check_eq(tracker.score, SCORE_INCREMENT_XLARGE); - - } finally { - _("Clean up."); - yield cleanup(); + store.wipe(); + tracker.clearChangedIDs(); + tracker.resetScore(); + Svc.Obs.notify("weave:engine:stop-tracking"); } -}); - -add_task(function* test_async_onItemKeywordChanged() { - _("Keyword changes via the asynchronous API should be tracked"); - - try { - yield stopTracking(); - - _("Insert two bookmarks with the same URL"); - let fxBmk1 = yield PlacesUtils.bookmarks.insert({ - type: PlacesUtils.bookmarks.TYPE_BOOKMARK, - parentGuid: PlacesUtils.bookmarks.menuGuid, - url: "http://getfirefox.com", - title: "Get Firefox!", - }); - let fxBmk2 = yield PlacesUtils.bookmarks.insert({ - type: PlacesUtils.bookmarks.TYPE_BOOKMARK, - parentGuid: PlacesUtils.bookmarks.toolbarGuid, - url: "http://getfirefox.com", - title: "Download Firefox", - }); - - yield startTracking(); - - _("Add a keyword for both items"); - yield PlacesUtils.keywords.insert({ - keyword: "the_keyword", - url: "http://getfirefox.com", - postData: "postData", - }); - - yield verifyTrackedItems([fxBmk1.guid, fxBmk2.guid]); - do_check_eq(tracker.score, SCORE_INCREMENT_XLARGE * 2); - } finally { - _("Clean up."); - yield cleanup(); - } -}); - -add_task(function* test_async_onItemKeywordDeleted() { - _("Keyword deletions via the asynchronous API should be tracked"); - - try { - yield stopTracking(); - - _("Insert two bookmarks with the same URL and keywords"); - let fxBmk1 = yield PlacesUtils.bookmarks.insert({ - type: PlacesUtils.bookmarks.TYPE_BOOKMARK, - parentGuid: PlacesUtils.bookmarks.menuGuid, - url: "http://getfirefox.com", - title: "Get Firefox!", - }); - let fxBmk2 = yield PlacesUtils.bookmarks.insert({ - type: PlacesUtils.bookmarks.TYPE_BOOKMARK, - parentGuid: PlacesUtils.bookmarks.toolbarGuid, - url: "http://getfirefox.com", - title: "Download Firefox", - }); - yield PlacesUtils.keywords.insert({ - keyword: "the_keyword", - url: "http://getfirefox.com", - }); - - yield startTracking(); - - _("Remove the keyword"); - yield PlacesUtils.keywords.remove("the_keyword"); - - yield verifyTrackedItems([fxBmk1.guid, fxBmk2.guid]); - do_check_eq(tracker.score, SCORE_INCREMENT_XLARGE * 2); - } finally { - _("Clean up."); - yield cleanup(); - } -}); - -add_task(function* test_onItemPostDataChanged() { - _("Post data changes should be tracked"); - - try { - yield stopTracking(); - - _("Insert a bookmark"); - let fx_id = PlacesUtils.bookmarks.insertBookmark( - PlacesUtils.bookmarks.bookmarksMenuFolder, - Utils.makeURI("http://getfirefox.com"), - PlacesUtils.bookmarks.DEFAULT_INDEX, - "Get Firefox!"); - let fx_guid = engine._store.GUIDForId(fx_id); - _(`Firefox GUID: ${fx_guid}`); - - yield startTracking(); +} - // PlacesUtils.setPostDataForBookmark is deprecated, but still used by - // PlacesTransactions.NewBookmark. - _("Post data for the bookmark should be ignored"); - yield PlacesUtils.setPostDataForBookmark(fx_id, "postData"); - yield verifyTrackerEmpty(); - } finally { - _("Clean up."); - yield cleanup(); - } -}); +function test_onItemChanged() { + // Anno that's in ANNOS_TO_TRACK. + const DESCRIPTION_ANNO = "bookmarkProperties/description"; -add_task(function* test_onItemAnnoChanged() { - _("Item annotations should be tracked"); + _("Verify we've got an empty tracker to work with."); + let tracker = engine._tracker; + do_check_empty(tracker.changedIDs); + do_check_eq(tracker.score, 0); try { - yield stopTracking(); + Svc.Obs.notify("weave:engine:stop-tracking"); let folder = PlacesUtils.bookmarks.createFolder( PlacesUtils.bookmarks.bookmarksMenuFolder, "Parent", PlacesUtils.bookmarks.DEFAULT_INDEX); - let folderGUID = engine._store.GUIDForId(folder); _("Track changes to annos."); let b = PlacesUtils.bookmarks.insertBookmark( folder, Utils.makeURI("http://getfirefox.com"), @@ -721,225 +95,27 @@ add_task(function* test_onItemAnnoChanged() { _("New item is " + b); _("GUID: " + bGUID); - yield startTracking(); + Svc.Obs.notify("weave:engine:start-tracking"); PlacesUtils.annotations.setItemAnnotation( - b, PlacesSyncUtils.bookmarks.DESCRIPTION_ANNO, "A test description", 0, + b, DESCRIPTION_ANNO, "A test description", 0, PlacesUtils.annotations.EXPIRE_NEVER); - // bookmark should be tracked, folder should not. - yield verifyTrackedItems([bGUID]); - do_check_eq(tracker.score, SCORE_INCREMENT_XLARGE); - yield resetTracker(); - - PlacesUtils.annotations.removeItemAnnotation(b, - PlacesSyncUtils.bookmarks.DESCRIPTION_ANNO); - yield verifyTrackedItems([bGUID]); + do_check_true(tracker.changedIDs[bGUID] > 0); do_check_eq(tracker.score, SCORE_INCREMENT_XLARGE); - } finally { - _("Clean up."); - yield cleanup(); - } -}); - -add_task(function* test_onItemAdded_filtered_root() { - _("Items outside the change roots should not be tracked"); - - try { - yield startTracking(); - - _("Create a new root"); - let rootID = PlacesUtils.bookmarks.createFolder( - PlacesUtils.bookmarks.placesRoot, - "New root", - PlacesUtils.bookmarks.DEFAULT_INDEX); - let rootGUID = engine._store.GUIDForId(rootID); - _(`New root GUID: ${rootGUID}`); - - _("Insert a bookmark underneath the new root"); - let untrackedBmkID = PlacesUtils.bookmarks.insertBookmark( - rootID, - Utils.makeURI("http://getthunderbird.com"), - PlacesUtils.bookmarks.DEFAULT_INDEX, - "Get Thunderbird!"); - let untrackedBmkGUID = engine._store.GUIDForId(untrackedBmkID); - _(`New untracked bookmark GUID: ${untrackedBmkGUID}`); - - _("Insert a bookmark underneath the Places root"); - let rootBmkID = PlacesUtils.bookmarks.insertBookmark( - PlacesUtils.bookmarks.placesRoot, - Utils.makeURI("http://getfirefox.com"), - PlacesUtils.bookmarks.DEFAULT_INDEX, "Get Firefox!"); - let rootBmkGUID = engine._store.GUIDForId(rootBmkID); - _(`New Places root bookmark GUID: ${rootBmkGUID}`); - - _("New root and bookmark should be ignored"); - yield verifyTrackedItems([]); - // ...But we'll still increment the score and filter out the changes at - // sync time. - do_check_eq(tracker.score, SCORE_INCREMENT_XLARGE * 6); - } finally { - _("Clean up."); - yield cleanup(); - } -}); - -add_task(function* test_onItemDeleted_filtered_root() { - _("Deleted items outside the change roots should be tracked"); - - try { - yield stopTracking(); - - _("Insert a bookmark underneath the Places root"); - let rootBmkID = PlacesUtils.bookmarks.insertBookmark( - PlacesUtils.bookmarks.placesRoot, - Utils.makeURI("http://getfirefox.com"), - PlacesUtils.bookmarks.DEFAULT_INDEX, "Get Firefox!"); - let rootBmkGUID = engine._store.GUIDForId(rootBmkID); - _(`New Places root bookmark GUID: ${rootBmkGUID}`); - - yield startTracking(); - - PlacesUtils.bookmarks.removeItem(rootBmkID); - - // We shouldn't upload tombstones for items in filtered roots, but the - // `onItemRemoved` observer doesn't have enough context to determine - // the root, so we'll end up uploading it. - yield verifyTrackedItems([rootBmkGUID]); - // We'll increment the counter twice (once for the removed item, and once - // for the Places root), then filter out the root. - do_check_eq(tracker.score, SCORE_INCREMENT_XLARGE * 2); - } finally { - _("Clean up."); - yield cleanup(); - } -}); -add_task(function* test_onPageAnnoChanged() { - _("Page annotations should not be tracked"); - - try { - yield stopTracking(); - - _("Insert a bookmark without an annotation"); - let pageURI = Utils.makeURI("http://getfirefox.com"); - PlacesUtils.bookmarks.insertBookmark( - PlacesUtils.bookmarks.bookmarksMenuFolder, - pageURI, - PlacesUtils.bookmarks.DEFAULT_INDEX, - "Get Firefox!"); - - yield startTracking(); - - _("Add a page annotation"); - PlacesUtils.annotations.setPageAnnotation(pageURI, "URIProperties/characterSet", - "UTF-8", 0, PlacesUtils.annotations.EXPIRE_NEVER); - yield verifyTrackerEmpty(); - yield resetTracker(); - - _("Remove the page annotation"); - PlacesUtils.annotations.removePageAnnotation(pageURI, - "URIProperties/characterSet"); - yield verifyTrackerEmpty(); - } finally { - _("Clean up."); - yield cleanup(); - } -}); - -add_task(function* test_onFaviconChanged() { - _("Favicon changes should not be tracked"); - - try { - yield stopTracking(); - - let pageURI = Utils.makeURI("http://getfirefox.com"); - let iconURI = Utils.makeURI("http://getfirefox.com/icon"); - PlacesUtils.bookmarks.insertBookmark( - PlacesUtils.bookmarks.bookmarksMenuFolder, - pageURI, - PlacesUtils.bookmarks.DEFAULT_INDEX, - "Get Firefox!"); - - yield PlacesTestUtils.addVisits(pageURI); - - yield startTracking(); - - _("Favicon annotations should be ignored"); - let iconURL = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAA" + - "AAAA6fptVAAAACklEQVQI12NgAAAAAgAB4iG8MwAAAABJRU5ErkJggg=="; - - PlacesUtils.favicons.replaceFaviconDataFromDataURL(iconURI, iconURL, 0, - Services.scriptSecurityManager.getSystemPrincipal()); - - yield new Promise(resolve => { - PlacesUtils.favicons.setAndFetchFaviconForPage(pageURI, iconURI, true, - PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE, (iconURI, dataLen, data, mimeType) => { - resolve(); - }, - Services.scriptSecurityManager.getSystemPrincipal()); - }); - yield verifyTrackerEmpty(); - } finally { - _("Clean up."); - yield cleanup(); - } -}); - -add_task(function* test_onLivemarkAdded() { - _("New livemarks should be tracked"); - - try { - yield startTracking(); - - _("Insert a livemark"); - let livemark = yield PlacesUtils.livemarks.addLivemark({ - parentGuid: PlacesUtils.bookmarks.menuGuid, - // Use a local address just in case, to avoid potential aborts for - // non-local connections. - feedURI: Utils.makeURI("http://localhost:0"), - }); - // Prevent the livemark refresh timer from requesting the URI. - livemark.terminate(); - - yield verifyTrackedItems(["menu", livemark.guid]); - // Three changes: one for the parent, one for creating the livemark - // folder, and one for setting the "livemark/feedURI" anno on the folder. - do_check_eq(tracker.score, SCORE_INCREMENT_XLARGE * 3); - } finally { - _("Clean up."); - yield cleanup(); - } -}); - -add_task(function* test_onLivemarkDeleted() { - _("Deleted livemarks should be tracked"); - - try { - yield stopTracking(); - - _("Insert a livemark"); - let livemark = yield PlacesUtils.livemarks.addLivemark({ - parentGuid: PlacesUtils.bookmarks.menuGuid, - feedURI: Utils.makeURI("http://localhost:0"), - }); - livemark.terminate(); - - yield startTracking(); - - _("Remove a livemark"); - yield PlacesUtils.livemarks.removeLivemark({ - guid: livemark.guid, - }); - - yield verifyTrackedItems(["menu", livemark.guid]); - do_check_eq(tracker.score, SCORE_INCREMENT_XLARGE * 2); } finally { _("Clean up."); - yield cleanup(); + store.wipe(); + tracker.clearChangedIDs(); + tracker.resetScore(); + Svc.Obs.notify("weave:engine:stop-tracking"); } -}); +} -add_task(function* test_onItemMoved() { - _("Items moved via the synchronous API should be tracked"); +function test_onItemMoved() { + _("Verify we've got an empty tracker to work with."); + let tracker = engine._tracker; + do_check_empty(tracker.changedIDs); + do_check_eq(tracker.score, 0); try { let fx_id = PlacesUtils.bookmarks.insertBookmark( @@ -948,590 +124,55 @@ add_task(function* test_onItemMoved() { PlacesUtils.bookmarks.DEFAULT_INDEX, "Get Firefox!"); let fx_guid = engine._store.GUIDForId(fx_id); - _("Firefox GUID: " + fx_guid); let tb_id = PlacesUtils.bookmarks.insertBookmark( PlacesUtils.bookmarks.bookmarksMenuFolder, Utils.makeURI("http://getthunderbird.com"), PlacesUtils.bookmarks.DEFAULT_INDEX, "Get Thunderbird!"); let tb_guid = engine._store.GUIDForId(tb_id); - _("Thunderbird GUID: " + tb_guid); - yield startTracking(); + Svc.Obs.notify("weave:engine:start-tracking"); // Moving within the folder will just track the folder. PlacesUtils.bookmarks.moveItem( tb_id, PlacesUtils.bookmarks.bookmarksMenuFolder, 0); - yield verifyTrackedItems(['menu']); + do_check_true(tracker.changedIDs['menu'] > 0); + do_check_eq(tracker.changedIDs['toolbar'], undefined); + do_check_eq(tracker.changedIDs[fx_guid], undefined); + do_check_eq(tracker.changedIDs[tb_guid], undefined); do_check_eq(tracker.score, SCORE_INCREMENT_XLARGE); - yield resetTracker(); + tracker.clearChangedIDs(); + tracker.resetScore(); // Moving a bookmark to a different folder will track the old // folder, the new folder and the bookmark. - PlacesUtils.bookmarks.moveItem(fx_id, PlacesUtils.bookmarks.toolbarFolder, + PlacesUtils.bookmarks.moveItem(tb_id, PlacesUtils.bookmarks.toolbarFolder, PlacesUtils.bookmarks.DEFAULT_INDEX); - yield verifyTrackedItems(['menu', 'toolbar', fx_guid]); - do_check_eq(tracker.score, SCORE_INCREMENT_XLARGE * 3); - - } finally { - _("Clean up."); - yield cleanup(); - } -}); - -add_task(function* test_async_onItemMoved_update() { - _("Items moved via the asynchronous API should be tracked"); - - try { - yield stopTracking(); - - let fxBmk = yield PlacesUtils.bookmarks.insert({ - type: PlacesUtils.bookmarks.TYPE_BOOKMARK, - parentGuid: PlacesUtils.bookmarks.menuGuid, - url: "http://getfirefox.com", - title: "Get Firefox!", - }); - let tbBmk = yield PlacesUtils.bookmarks.insert({ - type: PlacesUtils.bookmarks.TYPE_BOOKMARK, - parentGuid: PlacesUtils.bookmarks.menuGuid, - url: "http://getthunderbird.com", - title: "Get Thunderbird!", - }); - - yield startTracking(); - - _("Repositioning a bookmark should track the folder"); - yield PlacesUtils.bookmarks.update({ - guid: tbBmk.guid, - parentGuid: PlacesUtils.bookmarks.menuGuid, - index: 0, - }); - yield verifyTrackedItems(['menu']); - do_check_eq(tracker.score, SCORE_INCREMENT_XLARGE); - yield resetTracker(); - - _("Reparenting a bookmark should track both folders and the bookmark"); - yield PlacesUtils.bookmarks.update({ - guid: tbBmk.guid, - parentGuid: PlacesUtils.bookmarks.toolbarGuid, - index: PlacesUtils.bookmarks.DEFAULT_INDEX, - }); - yield verifyTrackedItems(['menu', 'toolbar', tbBmk.guid]); - do_check_eq(tracker.score, SCORE_INCREMENT_XLARGE * 3); - } finally { - _("Clean up."); - yield cleanup(); - } -}); - -add_task(function* test_async_onItemMoved_reorder() { - _("Items reordered via the asynchronous API should be tracked"); - - try { - yield stopTracking(); - - _("Insert out-of-order bookmarks"); - let fxBmk = yield PlacesUtils.bookmarks.insert({ - type: PlacesUtils.bookmarks.TYPE_BOOKMARK, - parentGuid: PlacesUtils.bookmarks.menuGuid, - url: "http://getfirefox.com", - title: "Get Firefox!", - }); - _(`Firefox GUID: ${fxBmk.guid}`); - - let tbBmk = yield PlacesUtils.bookmarks.insert({ - type: PlacesUtils.bookmarks.TYPE_BOOKMARK, - parentGuid: PlacesUtils.bookmarks.menuGuid, - url: "http://getthunderbird.com", - title: "Get Thunderbird!", - }); - _(`Thunderbird GUID: ${tbBmk.guid}`); - - let mozBmk = yield PlacesUtils.bookmarks.insert({ - type: PlacesUtils.bookmarks.TYPE_BOOKMARK, - parentGuid: PlacesUtils.bookmarks.menuGuid, - url: "https://mozilla.org", - title: "Mozilla", - }); - _(`Mozilla GUID: ${mozBmk.guid}`); - - yield startTracking(); - - _("Reorder bookmarks"); - yield PlacesUtils.bookmarks.reorder(PlacesUtils.bookmarks.menuGuid, - [mozBmk.guid, fxBmk.guid, tbBmk.guid]); - - // As with setItemIndex, we should only track the folder if we reorder - // its children, but we should bump the score for every changed item. - yield verifyTrackedItems(["menu"]); - do_check_eq(tracker.score, SCORE_INCREMENT_XLARGE * 3); - } finally { - _("Clean up."); - yield cleanup(); - } -}); - -add_task(function* test_onItemMoved_setItemIndex() { - _("Items with updated indices should be tracked"); - - try { - yield stopTracking(); - - let folder_id = PlacesUtils.bookmarks.createFolder( - PlacesUtils.bookmarks.bookmarksMenuFolder, - "Test folder", - PlacesUtils.bookmarks.DEFAULT_INDEX); - let folder_guid = engine._store.GUIDForId(folder_id); - _(`Folder GUID: ${folder_guid}`); - - let tb_id = PlacesUtils.bookmarks.insertBookmark( - folder_id, - Utils.makeURI("http://getthunderbird.com"), - PlacesUtils.bookmarks.DEFAULT_INDEX, - "Thunderbird"); - let tb_guid = engine._store.GUIDForId(tb_id); - _(`Thunderbird GUID: ${tb_guid}`); - - let fx_id = PlacesUtils.bookmarks.insertBookmark( - folder_id, - Utils.makeURI("http://getfirefox.com"), - PlacesUtils.bookmarks.DEFAULT_INDEX, - "Firefox"); - let fx_guid = engine._store.GUIDForId(fx_id); - _(`Firefox GUID: ${fx_guid}`); - - let moz_id = PlacesUtils.bookmarks.insertBookmark( - PlacesUtils.bookmarks.bookmarksMenuFolder, - Utils.makeURI("https://mozilla.org"), - PlacesUtils.bookmarks.DEFAULT_INDEX, - "Mozilla" - ); - let moz_guid = engine._store.GUIDForId(moz_id); - _(`Mozilla GUID: ${moz_guid}`); - - yield startTracking(); - - // PlacesSortFolderByNameTransaction exercises - // PlacesUtils.bookmarks.setItemIndex. - let txn = new PlacesSortFolderByNameTransaction(folder_id); - - // We're reordering items within the same folder, so only the folder - // should be tracked. - _("Execute the sort folder transaction"); - txn.doTransaction(); - yield verifyTrackedItems([folder_guid]); - do_check_eq(tracker.score, SCORE_INCREMENT_XLARGE); - yield resetTracker(); - - _("Undo the sort folder transaction"); - txn.undoTransaction(); - yield verifyTrackedItems([folder_guid]); - do_check_eq(tracker.score, SCORE_INCREMENT_XLARGE); - } finally { - _("Clean up."); - yield cleanup(); - } -}); - -add_task(function* test_onItemDeleted_removeFolderTransaction() { - _("Folders removed in a transaction should be tracked"); - - try { - yield stopTracking(); - - _("Create a folder with two children"); - let folder_id = PlacesUtils.bookmarks.createFolder( - PlacesUtils.bookmarks.bookmarksMenuFolder, - "Test folder", - PlacesUtils.bookmarks.DEFAULT_INDEX); - let folder_guid = engine._store.GUIDForId(folder_id); - _(`Folder GUID: ${folder_guid}`); - let fx_id = PlacesUtils.bookmarks.insertBookmark( - folder_id, - Utils.makeURI("http://getfirefox.com"), - PlacesUtils.bookmarks.DEFAULT_INDEX, - "Get Firefox!"); - let fx_guid = engine._store.GUIDForId(fx_id); - _(`Firefox GUID: ${fx_guid}`); - let tb_id = PlacesUtils.bookmarks.insertBookmark( - folder_id, - Utils.makeURI("http://getthunderbird.com"), - PlacesUtils.bookmarks.DEFAULT_INDEX, - "Get Thunderbird!"); - let tb_guid = engine._store.GUIDForId(tb_id); - _(`Thunderbird GUID: ${tb_guid}`); - - yield startTracking(); - - let txn = PlacesUtils.bookmarks.getRemoveFolderTransaction(folder_id); - // We haven't executed the transaction yet. - yield verifyTrackerEmpty(); - - _("Execute the remove folder transaction"); - txn.doTransaction(); - yield verifyTrackedItems(["menu", folder_guid, fx_guid, tb_guid]); - do_check_eq(tracker.score, SCORE_INCREMENT_XLARGE * 6); - yield resetTracker(); - - _("Undo the remove folder transaction"); - txn.undoTransaction(); - - // At this point, the restored folder has the same ID, but a different GUID. - let new_folder_guid = yield PlacesUtils.promiseItemGuid(folder_id); - - yield verifyTrackedItems(["menu", new_folder_guid]); - do_check_eq(tracker.score, SCORE_INCREMENT_XLARGE * 2); - yield resetTracker(); - - _("Redo the transaction"); - txn.redoTransaction(); - yield verifyTrackedItems(["menu", new_folder_guid]); - do_check_eq(tracker.score, SCORE_INCREMENT_XLARGE * 2); - } finally { - _("Clean up."); - yield cleanup(); - } -}); - -add_task(function* test_treeMoved() { - _("Moving an entire tree of bookmarks should track the parents"); - - try { - // Create a couple of parent folders. - let folder1_id = PlacesUtils.bookmarks.createFolder( - PlacesUtils.bookmarks.bookmarksMenuFolder, - "First test folder", - PlacesUtils.bookmarks.DEFAULT_INDEX); - let folder1_guid = engine._store.GUIDForId(folder1_id); - - // A second folder in the first. - let folder2_id = PlacesUtils.bookmarks.createFolder( - folder1_id, - "Second test folder", - PlacesUtils.bookmarks.DEFAULT_INDEX); - let folder2_guid = engine._store.GUIDForId(folder2_id); - - // Create a couple of bookmarks in the second folder. - let fx_id = PlacesUtils.bookmarks.insertBookmark( - folder2_id, - Utils.makeURI("http://getfirefox.com"), - PlacesUtils.bookmarks.DEFAULT_INDEX, - "Get Firefox!"); - let fx_guid = engine._store.GUIDForId(fx_id); - let tb_id = PlacesUtils.bookmarks.insertBookmark( - folder2_id, - Utils.makeURI("http://getthunderbird.com"), - PlacesUtils.bookmarks.DEFAULT_INDEX, - "Get Thunderbird!"); - let tb_guid = engine._store.GUIDForId(tb_id); - - yield startTracking(); - - // Move folder 2 to be a sibling of folder1. - PlacesUtils.bookmarks.moveItem( - folder2_id, PlacesUtils.bookmarks.bookmarksMenuFolder, 0); - // the menu and both folders should be tracked, the children should not be. - yield verifyTrackedItems(['menu', folder1_guid, folder2_guid]); + do_check_true(tracker.changedIDs['menu'] > 0); + do_check_true(tracker.changedIDs['toolbar'] > 0); + do_check_eq(tracker.changedIDs[fx_guid], undefined); + do_check_true(tracker.changedIDs[tb_guid] > 0); do_check_eq(tracker.score, SCORE_INCREMENT_XLARGE * 3); - } finally { - _("Clean up."); - yield cleanup(); - } -}); - -add_task(function* test_onItemDeleted() { - _("Bookmarks deleted via the synchronous API should be tracked"); - - try { - let fx_id = PlacesUtils.bookmarks.insertBookmark( - PlacesUtils.bookmarks.bookmarksMenuFolder, - Utils.makeURI("http://getfirefox.com"), - PlacesUtils.bookmarks.DEFAULT_INDEX, - "Get Firefox!"); - let fx_guid = engine._store.GUIDForId(fx_id); - let tb_id = PlacesUtils.bookmarks.insertBookmark( - PlacesUtils.bookmarks.bookmarksMenuFolder, - Utils.makeURI("http://getthunderbird.com"), - PlacesUtils.bookmarks.DEFAULT_INDEX, - "Get Thunderbird!"); - let tb_guid = engine._store.GUIDForId(tb_id); - - yield startTracking(); - - // Delete the last item - the item and parent should be tracked. - PlacesUtils.bookmarks.removeItem(tb_id); - - yield verifyTrackedItems(['menu', tb_guid]); - do_check_eq(tracker.score, SCORE_INCREMENT_XLARGE * 2); - } finally { - _("Clean up."); - yield cleanup(); - } -}); - -add_task(function* test_async_onItemDeleted() { - _("Bookmarks deleted via the asynchronous API should be tracked"); - - try { - yield stopTracking(); - - let fxBmk = yield PlacesUtils.bookmarks.insert({ - type: PlacesUtils.bookmarks.TYPE_BOOKMARK, - parentGuid: PlacesUtils.bookmarks.menuGuid, - url: "http://getfirefox.com", - title: "Get Firefox!", - }); - let tbBmk = yield PlacesUtils.bookmarks.insert({ - type: PlacesUtils.bookmarks.TYPE_BOOKMARK, - parentGuid: PlacesUtils.bookmarks.menuGuid, - url: "http://getthunderbird.com", - title: "Get Thunderbird!", - }); - - yield startTracking(); - - _("Delete the first item"); - yield PlacesUtils.bookmarks.remove(fxBmk.guid); - yield verifyTrackedItems(["menu", fxBmk.guid]); - do_check_eq(tracker.score, SCORE_INCREMENT_XLARGE * 2); } finally { _("Clean up."); - yield cleanup(); + store.wipe(); + tracker.clearChangedIDs(); + tracker.resetScore(); + Svc.Obs.notify("weave:engine:stop-tracking"); } -}); - -add_task(function* test_async_onItemDeleted_eraseEverything() { - _("Erasing everything should track all deleted items"); - - try { - yield stopTracking(); - let fxBmk = yield PlacesUtils.bookmarks.insert({ - type: PlacesUtils.bookmarks.TYPE_BOOKMARK, - parentGuid: PlacesUtils.bookmarks.mobileGuid, - url: "http://getfirefox.com", - title: "Get Firefox!", - }); - _(`Firefox GUID: ${fxBmk.guid}`); - let tbBmk = yield PlacesUtils.bookmarks.insert({ - type: PlacesUtils.bookmarks.TYPE_BOOKMARK, - parentGuid: PlacesUtils.bookmarks.mobileGuid, - url: "http://getthunderbird.com", - title: "Get Thunderbird!", - }); - _(`Thunderbird GUID: ${tbBmk.guid}`); - let mozBmk = yield PlacesUtils.bookmarks.insert({ - type: PlacesUtils.bookmarks.TYPE_BOOKMARK, - parentGuid: PlacesUtils.bookmarks.menuGuid, - url: "https://mozilla.org", - title: "Mozilla", - }); - _(`Mozilla GUID: ${mozBmk.guid}`); - let mdnBmk = yield PlacesUtils.bookmarks.insert({ - type: PlacesUtils.bookmarks.TYPE_BOOKMARK, - parentGuid: PlacesUtils.bookmarks.menuGuid, - url: "https://developer.mozilla.org", - title: "MDN", - }); - _(`MDN GUID: ${mdnBmk.guid}`); - let bugsFolder = yield PlacesUtils.bookmarks.insert({ - type: PlacesUtils.bookmarks.TYPE_FOLDER, - parentGuid: PlacesUtils.bookmarks.toolbarGuid, - title: "Bugs", - }); - _(`Bugs folder GUID: ${bugsFolder.guid}`); - let bzBmk = yield PlacesUtils.bookmarks.insert({ - type: PlacesUtils.bookmarks.TYPE_BOOKMARK, - parentGuid: bugsFolder.guid, - url: "https://bugzilla.mozilla.org", - title: "Bugzilla", - }); - _(`Bugzilla GUID: ${bzBmk.guid}`); - let bugsChildFolder = yield PlacesUtils.bookmarks.insert({ - type: PlacesUtils.bookmarks.TYPE_FOLDER, - parentGuid: bugsFolder.guid, - title: "Bugs child", - }); - _(`Bugs child GUID: ${bugsChildFolder.guid}`); - let bugsGrandChildBmk = yield PlacesUtils.bookmarks.insert({ - type: PlacesUtils.bookmarks.TYPE_BOOKMARK, - parentGuid: bugsChildFolder.guid, - url: "https://example.com", - title: "Bugs grandchild", - }); - _(`Bugs grandchild GUID: ${bugsGrandChildBmk.guid}`); - - yield startTracking(); - - yield PlacesUtils.bookmarks.eraseEverything(); - - // `eraseEverything` removes all items from the database before notifying - // observers. Because of this, grandchild lookup in the tracker's - // `onItemRemoved` observer will fail. That means we won't track - // (bzBmk.guid, bugsGrandChildBmk.guid, bugsChildFolder.guid), even - // though we should. - yield verifyTrackedItems(["menu", mozBmk.guid, mdnBmk.guid, "toolbar", - bugsFolder.guid, "mobile", fxBmk.guid, - tbBmk.guid]); - do_check_eq(tracker.score, SCORE_INCREMENT_XLARGE * 10); - } finally { - _("Clean up."); - yield cleanup(); - } -}); - -add_task(function* test_onItemDeleted_removeFolderChildren() { - _("Removing a folder's children should track the folder and its children"); - - try { - let fx_id = PlacesUtils.bookmarks.insertBookmark( - PlacesUtils.mobileFolderId, - Utils.makeURI("http://getfirefox.com"), - PlacesUtils.bookmarks.DEFAULT_INDEX, - "Get Firefox!"); - let fx_guid = engine._store.GUIDForId(fx_id); - _(`Firefox GUID: ${fx_guid}`); - - let tb_id = PlacesUtils.bookmarks.insertBookmark( - PlacesUtils.mobileFolderId, - Utils.makeURI("http://getthunderbird.com"), - PlacesUtils.bookmarks.DEFAULT_INDEX, - "Get Thunderbird!"); - let tb_guid = engine._store.GUIDForId(tb_id); - _(`Thunderbird GUID: ${tb_guid}`); - - let moz_id = PlacesUtils.bookmarks.insertBookmark( - PlacesUtils.bookmarks.bookmarksMenuFolder, - Utils.makeURI("https://mozilla.org"), - PlacesUtils.bookmarks.DEFAULT_INDEX, - "Mozilla" - ); - let moz_guid = engine._store.GUIDForId(moz_id); - _(`Mozilla GUID: ${moz_guid}`); - - yield startTracking(); - - _(`Mobile root ID: ${PlacesUtils.mobileFolderId}`); - PlacesUtils.bookmarks.removeFolderChildren(PlacesUtils.mobileFolderId); - - yield verifyTrackedItems(["mobile", fx_guid, tb_guid]); - do_check_eq(tracker.score, SCORE_INCREMENT_XLARGE * 4); - } finally { - _("Clean up."); - yield cleanup(); - } -}); - -add_task(function* test_onItemDeleted_tree() { - _("Deleting a tree of bookmarks should track all items"); - - try { - // Create a couple of parent folders. - let folder1_id = PlacesUtils.bookmarks.createFolder( - PlacesUtils.bookmarks.bookmarksMenuFolder, - "First test folder", - PlacesUtils.bookmarks.DEFAULT_INDEX); - let folder1_guid = engine._store.GUIDForId(folder1_id); - - // A second folder in the first. - let folder2_id = PlacesUtils.bookmarks.createFolder( - folder1_id, - "Second test folder", - PlacesUtils.bookmarks.DEFAULT_INDEX); - let folder2_guid = engine._store.GUIDForId(folder2_id); - - // Create a couple of bookmarks in the second folder. - let fx_id = PlacesUtils.bookmarks.insertBookmark( - folder2_id, - Utils.makeURI("http://getfirefox.com"), - PlacesUtils.bookmarks.DEFAULT_INDEX, - "Get Firefox!"); - let fx_guid = engine._store.GUIDForId(fx_id); - let tb_id = PlacesUtils.bookmarks.insertBookmark( - folder2_id, - Utils.makeURI("http://getthunderbird.com"), - PlacesUtils.bookmarks.DEFAULT_INDEX, - "Get Thunderbird!"); - let tb_guid = engine._store.GUIDForId(tb_id); - - yield startTracking(); - - // Delete folder2 - everything we created should be tracked. - PlacesUtils.bookmarks.removeItem(folder2_id); - - yield verifyTrackedItems([fx_guid, tb_guid, folder1_guid, folder2_guid]); - do_check_eq(tracker.score, SCORE_INCREMENT_XLARGE * 6); - } finally { - _("Clean up."); - yield cleanup(); - } -}); - -add_task(function* test_mobile_query() { - _("Ensure we correctly create the mobile query"); - - try { - // Creates the organizer queries as a side effect. - let leftPaneId = PlacesUIUtils.leftPaneFolderId; - _(`Left pane root ID: ${leftPaneId}`); - - let allBookmarksIds = findAnnoItems("PlacesOrganizer/OrganizerQuery", "AllBookmarks"); - equal(allBookmarksIds.length, 1, "Should create folder with all bookmarks queries"); - let allBookmarkGuid = yield PlacesUtils.promiseItemGuid(allBookmarksIds[0]); - - _("Try creating query after organizer is ready"); - tracker._ensureMobileQuery(); - let queryIds = findAnnoItems("PlacesOrganizer/OrganizerQuery", "MobileBookmarks"); - equal(queryIds.length, 0, "Should not create query without any mobile bookmarks"); - - _("Insert mobile bookmark, then create query"); - yield PlacesUtils.bookmarks.insert({ - parentGuid: PlacesUtils.bookmarks.mobileGuid, - url: "https://mozilla.org", - }); - tracker._ensureMobileQuery(); - queryIds = findAnnoItems("PlacesOrganizer/OrganizerQuery", "MobileBookmarks", {}); - equal(queryIds.length, 1, "Should create query once mobile bookmarks exist"); - - let queryId = queryIds[0]; - let queryGuid = yield PlacesUtils.promiseItemGuid(queryId); +} - let queryInfo = yield PlacesUtils.bookmarks.fetch(queryGuid); - equal(queryInfo.url, `place:folder=${PlacesUtils.mobileFolderId}`, "Query should point to mobile root"); - equal(queryInfo.title, "Mobile Bookmarks", "Query title should be localized"); - equal(queryInfo.parentGuid, allBookmarkGuid, "Should append mobile query to all bookmarks queries"); +function run_test() { + initTestLogging("Trace"); - _("Rename root and query, then recreate"); - yield PlacesUtils.bookmarks.update({ - guid: PlacesUtils.bookmarks.mobileGuid, - title: "renamed root", - }); - yield PlacesUtils.bookmarks.update({ - guid: queryGuid, - title: "renamed query", - }); - tracker._ensureMobileQuery(); - let rootInfo = yield PlacesUtils.bookmarks.fetch(PlacesUtils.bookmarks.mobileGuid); - equal(rootInfo.title, "Mobile Bookmarks", "Should fix root title"); - queryInfo = yield PlacesUtils.bookmarks.fetch(queryGuid); - equal(queryInfo.title, "Mobile Bookmarks", "Should fix query title"); + Log.repository.getLogger("Sync.Engine.Bookmarks").level = Log.Level.Trace; + Log.repository.getLogger("Sync.Store.Bookmarks").level = Log.Level.Trace; + Log.repository.getLogger("Sync.Tracker.Bookmarks").level = Log.Level.Trace; - _("Point query to different folder"); - yield PlacesUtils.bookmarks.update({ - guid: queryGuid, - url: "place:folder=BOOKMARKS_MENU", - }); - tracker._ensureMobileQuery(); - queryInfo = yield PlacesUtils.bookmarks.fetch(queryGuid); - equal(queryInfo.url.href, `place:folder=${PlacesUtils.mobileFolderId}`, - "Should fix query URL to point to mobile root"); + test_tracking(); + test_onItemChanged(); + test_onItemMoved(); +} - _("We shouldn't track the query or the left pane root"); - yield verifyTrackedCount(0); - do_check_eq(tracker.score, 0); - } finally { - _("Clean up."); - yield cleanup(); - } -}); diff --git a/services/sync/tests/unit/test_bookmark_validator.js b/services/sync/tests/unit/test_bookmark_validator.js deleted file mode 100644 index cc0b3b08f..000000000 --- a/services/sync/tests/unit/test_bookmark_validator.js +++ /dev/null @@ -1,347 +0,0 @@ -/* Any copyright is dedicated to the Public Domain. - http://creativecommons.org/publicdomain/zero/1.0/ */ - -Components.utils.import("resource://services-sync/bookmark_validator.js"); -Components.utils.import("resource://services-sync/util.js"); - -function inspectServerRecords(data) { - return new BookmarkValidator().inspectServerRecords(data); -} - -add_test(function test_isr_rootOnServer() { - let c = inspectServerRecords([{ - id: 'places', - type: 'folder', - children: [], - }]); - ok(c.problemData.rootOnServer); - run_next_test(); -}); - -add_test(function test_isr_empty() { - let c = inspectServerRecords([]); - ok(!c.problemData.rootOnServer); - notEqual(c.root, null); - run_next_test(); -}); - -add_test(function test_isr_cycles() { - let c = inspectServerRecords([ - {id: 'C', type: 'folder', children: ['A', 'B'], parentid: 'places'}, - {id: 'A', type: 'folder', children: ['B'], parentid: 'B'}, - {id: 'B', type: 'folder', children: ['A'], parentid: 'A'}, - ]).problemData; - - equal(c.cycles.length, 1); - ok(c.cycles[0].indexOf('A') >= 0); - ok(c.cycles[0].indexOf('B') >= 0); - run_next_test(); -}); - -add_test(function test_isr_orphansMultiParents() { - let c = inspectServerRecords([ - { id: 'A', type: 'bookmark', parentid: 'D' }, - { id: 'B', type: 'folder', parentid: 'places', children: ['A']}, - { id: 'C', type: 'folder', parentid: 'places', children: ['A']}, - - ]).problemData; - deepEqual(c.orphans, [{ id: "A", parent: "D" }]); - equal(c.multipleParents.length, 1) - ok(c.multipleParents[0].parents.indexOf('B') >= 0); - ok(c.multipleParents[0].parents.indexOf('C') >= 0); - run_next_test(); -}); - -add_test(function test_isr_orphansMultiParents2() { - let c = inspectServerRecords([ - { id: 'A', type: 'bookmark', parentid: 'D' }, - { id: 'B', type: 'folder', parentid: 'places', children: ['A']}, - ]).problemData; - equal(c.orphans.length, 1); - equal(c.orphans[0].id, 'A'); - equal(c.multipleParents.length, 0); - run_next_test(); -}); - -add_test(function test_isr_deletedParents() { - let c = inspectServerRecords([ - { id: 'A', type: 'bookmark', parentid: 'B' }, - { id: 'B', type: 'folder', parentid: 'places', children: ['A']}, - { id: 'B', type: 'item', deleted: true}, - ]).problemData; - deepEqual(c.deletedParents, ['A']) - run_next_test(); -}); - -add_test(function test_isr_badChildren() { - let c = inspectServerRecords([ - { id: 'A', type: 'bookmark', parentid: 'places', children: ['B', 'C'] }, - { id: 'C', type: 'bookmark', parentid: 'A' } - ]).problemData; - deepEqual(c.childrenOnNonFolder, ['A']) - deepEqual(c.missingChildren, [{parent: 'A', child: 'B'}]); - deepEqual(c.parentNotFolder, ['C']); - run_next_test(); -}); - - -add_test(function test_isr_parentChildMismatches() { - let c = inspectServerRecords([ - { id: 'A', type: 'folder', parentid: 'places', children: [] }, - { id: 'B', type: 'bookmark', parentid: 'A' } - ]).problemData; - deepEqual(c.parentChildMismatches, [{parent: 'A', child: 'B'}]); - run_next_test(); -}); - -add_test(function test_isr_duplicatesAndMissingIDs() { - let c = inspectServerRecords([ - {id: 'A', type: 'folder', parentid: 'places', children: []}, - {id: 'A', type: 'folder', parentid: 'places', children: []}, - {type: 'folder', parentid: 'places', children: []} - ]).problemData; - equal(c.missingIDs, 1); - deepEqual(c.duplicates, ['A']); - run_next_test(); -}); - -add_test(function test_isr_duplicateChildren() { - let c = inspectServerRecords([ - {id: 'A', type: 'folder', parentid: 'places', children: ['B', 'B']}, - {id: 'B', type: 'bookmark', parentid: 'A'}, - ]).problemData; - deepEqual(c.duplicateChildren, ['A']); - run_next_test(); -}); - -// Each compareServerWithClient test mutates these, so we can't just keep them -// global -function getDummyServerAndClient() { - let server = [ - { - id: 'menu', - parentid: 'places', - type: 'folder', - parentName: '', - title: 'foo', - children: ['bbbbbbbbbbbb', 'cccccccccccc'] - }, - { - id: 'bbbbbbbbbbbb', - type: 'bookmark', - parentid: 'menu', - parentName: 'foo', - title: 'bar', - bmkUri: 'http://baz.com' - }, - { - id: 'cccccccccccc', - parentid: 'menu', - parentName: 'foo', - title: '', - type: 'query', - bmkUri: 'place:type=6&sort=14&maxResults=10' - } - ]; - - let client = { - "guid": "root________", - "title": "", - "id": 1, - "type": "text/x-moz-place-container", - "children": [ - { - "guid": "menu________", - "title": "foo", - "id": 1000, - "type": "text/x-moz-place-container", - "children": [ - { - "guid": "bbbbbbbbbbbb", - "title": "bar", - "id": 1001, - "type": "text/x-moz-place", - "uri": "http://baz.com" - }, - { - "guid": "cccccccccccc", - "title": "", - "id": 1002, - "annos": [{ - "name": "Places/SmartBookmark", - "flags": 0, - "expires": 4, - "value": "RecentTags" - }], - "type": "text/x-moz-place", - "uri": "place:type=6&sort=14&maxResults=10" - } - ] - } - ] - }; - return {server, client}; -} - - -add_test(function test_cswc_valid() { - let {server, client} = getDummyServerAndClient(); - - let c = new BookmarkValidator().compareServerWithClient(server, client).problemData; - equal(c.clientMissing.length, 0); - equal(c.serverMissing.length, 0); - equal(c.differences.length, 0); - run_next_test(); -}); - -add_test(function test_cswc_serverMissing() { - let {server, client} = getDummyServerAndClient(); - // remove c - server.pop(); - server[0].children.pop(); - - let c = new BookmarkValidator().compareServerWithClient(server, client).problemData; - deepEqual(c.serverMissing, ['cccccccccccc']); - equal(c.clientMissing.length, 0); - deepEqual(c.structuralDifferences, [{id: 'menu', differences: ['childGUIDs']}]); - run_next_test(); -}); - -add_test(function test_cswc_clientMissing() { - let {server, client} = getDummyServerAndClient(); - client.children[0].children.pop(); - - let c = new BookmarkValidator().compareServerWithClient(server, client).problemData; - deepEqual(c.clientMissing, ['cccccccccccc']); - equal(c.serverMissing.length, 0); - deepEqual(c.structuralDifferences, [{id: 'menu', differences: ['childGUIDs']}]); - run_next_test(); -}); - -add_test(function test_cswc_differences() { - { - let {server, client} = getDummyServerAndClient(); - client.children[0].children[0].title = 'asdf'; - let c = new BookmarkValidator().compareServerWithClient(server, client).problemData; - equal(c.clientMissing.length, 0); - equal(c.serverMissing.length, 0); - deepEqual(c.differences, [{id: 'bbbbbbbbbbbb', differences: ['title']}]); - } - - { - let {server, client} = getDummyServerAndClient(); - server[2].type = 'bookmark'; - let c = new BookmarkValidator().compareServerWithClient(server, client).problemData; - equal(c.clientMissing.length, 0); - equal(c.serverMissing.length, 0); - deepEqual(c.differences, [{id: 'cccccccccccc', differences: ['type']}]); - } - run_next_test(); -}); - -add_test(function test_cswc_serverUnexpected() { - let {server, client} = getDummyServerAndClient(); - client.children.push({ - "guid": "dddddddddddd", - "title": "", - "id": 2000, - "annos": [{ - "name": "places/excludeFromBackup", - "flags": 0, - "expires": 4, - "value": 1 - }, { - "name": "PlacesOrganizer/OrganizerFolder", - "flags": 0, - "expires": 4, - "value": 7 - }], - "type": "text/x-moz-place-container", - "children": [{ - "guid": "eeeeeeeeeeee", - "title": "History", - "annos": [{ - "name": "places/excludeFromBackup", - "flags": 0, - "expires": 4, - "value": 1 - }, { - "name": "PlacesOrganizer/OrganizerQuery", - "flags": 0, - "expires": 4, - "value": "History" - }], - "type": "text/x-moz-place", - "uri": "place:type=3&sort=4" - }] - }); - server.push({ - id: 'dddddddddddd', - parentid: 'places', - parentName: '', - title: '', - type: 'folder', - children: ['eeeeeeeeeeee'] - }, { - id: 'eeeeeeeeeeee', - parentid: 'dddddddddddd', - parentName: '', - title: 'History', - type: 'query', - bmkUri: 'place:type=3&sort=4' - }); - - let c = new BookmarkValidator().compareServerWithClient(server, client).problemData; - equal(c.clientMissing.length, 0); - equal(c.serverMissing.length, 0); - equal(c.serverUnexpected.length, 2); - deepEqual(c.serverUnexpected, ["dddddddddddd", "eeeeeeeeeeee"]); - run_next_test(); -}); - -function validationPing(server, client, duration) { - return wait_for_ping(function() { - // fake this entirely - Svc.Obs.notify("weave:service:sync:start"); - Svc.Obs.notify("weave:engine:sync:start", null, "bookmarks"); - Svc.Obs.notify("weave:engine:sync:finish", null, "bookmarks"); - let validator = new BookmarkValidator(); - let data = { - // We fake duration and version just so that we can verify they're passed through. - duration, - version: validator.version, - recordCount: server.length, - problems: validator.compareServerWithClient(server, client).problemData, - }; - Svc.Obs.notify("weave:engine:validate:finish", data, "bookmarks"); - Svc.Obs.notify("weave:service:sync:finish"); - }, true); // Allow "failing" pings, since having validation info indicates failure. -} - -add_task(function *test_telemetry_integration() { - let {server, client} = getDummyServerAndClient(); - // remove "c" - server.pop(); - server[0].children.pop(); - const duration = 50; - let ping = yield validationPing(server, client, duration); - ok(ping.engines); - let bme = ping.engines.find(e => e.name === "bookmarks"); - ok(bme); - ok(bme.validation); - ok(bme.validation.problems) - equal(bme.validation.checked, server.length); - equal(bme.validation.took, duration); - bme.validation.problems.sort((a, b) => String.localeCompare(a.name, b.name)); - equal(bme.validation.version, new BookmarkValidator().version); - deepEqual(bme.validation.problems, [ - { name: "badClientRoots", count: 3 }, - { name: "sdiff:childGUIDs", count: 1 }, - { name: "serverMissing", count: 1 }, - { name: "structuralDifferences", count: 1 }, - ]); -}); - -function run_test() { - run_next_test(); -} diff --git a/services/sync/tests/unit/test_browserid_identity.js b/services/sync/tests/unit/test_browserid_identity.js index 531c01bf6..f3cde9f8f 100644 --- a/services/sync/tests/unit/test_browserid_identity.js +++ b/services/sync/tests/unit/test_browserid_identity.js @@ -16,14 +16,13 @@ Cu.import("resource://gre/modules/FxAccountsCommon.js"); Cu.import("resource://services-sync/service.js"); Cu.import("resource://services-sync/status.js"); Cu.import("resource://services-sync/constants.js"); -Cu.import("resource://services-common/tokenserverclient.js"); const SECOND_MS = 1000; const MINUTE_MS = SECOND_MS * 60; const HOUR_MS = MINUTE_MS * 60; -var identityConfig = makeIdentityConfig(); -var browseridManager = new BrowserIDManager(); +let identityConfig = makeIdentityConfig(); +let browseridManager = new BrowserIDManager(); configureFxAccountIdentity(browseridManager, identityConfig); /** @@ -32,14 +31,11 @@ configureFxAccountIdentity(browseridManager, identityConfig); * headers. We will use this to test clock skew compensation in these headers * below. */ -var MockFxAccountsClient = function() { +let MockFxAccountsClient = function() { FxAccountsClient.apply(this); }; MockFxAccountsClient.prototype = { - __proto__: FxAccountsClient.prototype, - accountStatus() { - return Promise.resolve(true); - } + __proto__: FxAccountsClient.prototype }; function MockFxAccounts() { @@ -77,7 +73,7 @@ add_test(function test_initial_state() { } ); -add_task(function* test_initialializeWithCurrentIdentity() { +add_task(function test_initialializeWithCurrentIdentity() { _("Verify start after initializeWithCurrentIdentity"); browseridManager.initializeWithCurrentIdentity(); yield browseridManager.whenReadyToAuthenticate.promise; @@ -87,57 +83,7 @@ add_task(function* test_initialializeWithCurrentIdentity() { } ); -add_task(function* test_initialializeWithAuthErrorAndDeletedAccount() { - _("Verify sync unpair after initializeWithCurrentIdentity with auth error + account deleted"); - - var identityConfig = makeIdentityConfig(); - var browseridManager = new BrowserIDManager(); - - // Use the real `_getAssertion` method that calls - // `mockFxAClient.signCertificate`. - let fxaInternal = makeFxAccountsInternalMock(identityConfig); - delete fxaInternal._getAssertion; - - configureFxAccountIdentity(browseridManager, identityConfig, fxaInternal); - browseridManager._fxaService.internal.initialize(); - - let signCertificateCalled = false; - let accountStatusCalled = false; - - let MockFxAccountsClient = function() { - FxAccountsClient.apply(this); - }; - MockFxAccountsClient.prototype = { - __proto__: FxAccountsClient.prototype, - signCertificate() { - signCertificateCalled = true; - return Promise.reject({ - code: 401, - errno: ERRNO_INVALID_AUTH_TOKEN, - }); - }, - accountStatus() { - accountStatusCalled = true; - return Promise.resolve(false); - } - }; - - let mockFxAClient = new MockFxAccountsClient(); - browseridManager._fxaService.internal._fxAccountsClient = mockFxAClient; - - yield browseridManager.initializeWithCurrentIdentity(); - yield Assert.rejects(browseridManager.whenReadyToAuthenticate.promise, - "should reject due to an auth error"); - - do_check_true(signCertificateCalled); - do_check_true(accountStatusCalled); - do_check_false(browseridManager.account); - do_check_false(browseridManager._token); - do_check_false(browseridManager.hasValidToken()); - do_check_false(browseridManager.account); -}); - -add_task(function* test_initialializeWithNoKeys() { +add_task(function test_initialializeWithNoKeys() { _("Verify start after initializeWithCurrentIdentity without kA, kB or keyFetchToken"); let identityConfig = makeIdentityConfig(); delete identityConfig.fxaccount.user.kA; @@ -306,7 +252,7 @@ add_test(function test_RESTResourceAuthenticatorSkew() { run_next_test(); }); -add_task(function* test_ensureLoggedIn() { +add_task(function test_ensureLoggedIn() { configureFxAccountIdentity(browseridManager); yield browseridManager.initializeWithCurrentIdentity(); yield browseridManager.whenReadyToAuthenticate.promise; @@ -318,8 +264,8 @@ add_task(function* test_ensureLoggedIn() { // arrange for no logged in user. let fxa = browseridManager._fxaService - let signedInUser = fxa.internal.currentAccountState.storageManager.accountData; - fxa.internal.currentAccountState.storageManager.accountData = null; + let signedInUser = fxa.internal.currentAccountState.signedInUser; + fxa.internal.currentAccountState.signedInUser = null; browseridManager.initializeWithCurrentIdentity(); Assert.ok(!browseridManager._shouldHaveSyncKeyBundle, "_shouldHaveSyncKeyBundle should be false so we know we are testing what we think we are."); @@ -327,8 +273,7 @@ add_task(function* test_ensureLoggedIn() { yield Assert.rejects(browseridManager.ensureLoggedIn(), "expecting rejection due to no user"); Assert.ok(browseridManager._shouldHaveSyncKeyBundle, "_shouldHaveSyncKeyBundle should always be true after ensureLogin completes."); - // Restore the logged in user to what it was. - fxa.internal.currentAccountState.storageManager.accountData = signedInUser; + fxa.internal.currentAccountState.signedInUser = signedInUser; Status.login = LOGIN_FAILED_LOGIN_REJECTED; yield Assert.rejects(browseridManager.ensureLoggedIn(), "LOGIN_FAILED_LOGIN_REJECTED should have caused immediate rejection"); @@ -404,7 +349,7 @@ add_test(function test_computeXClientStateHeader() { run_next_test(); }); -add_task(function* test_getTokenErrors() { +add_task(function test_getTokenErrors() { _("BrowserIDManager correctly handles various failures to get a token."); _("Arrange for a 401 - Sync should reflect an auth error."); @@ -437,75 +382,7 @@ add_task(function* test_getTokenErrors() { Assert.equal(Status.login, LOGIN_FAILED_NETWORK_ERROR, "login state is LOGIN_FAILED_NETWORK_ERROR"); }); -add_task(function* test_refreshCertificateOn401() { - _("BrowserIDManager refreshes the FXA certificate after a 401."); - var identityConfig = makeIdentityConfig(); - var browseridManager = new BrowserIDManager(); - // Use the real `_getAssertion` method that calls - // `mockFxAClient.signCertificate`. - let fxaInternal = makeFxAccountsInternalMock(identityConfig); - delete fxaInternal._getAssertion; - configureFxAccountIdentity(browseridManager, identityConfig, fxaInternal); - browseridManager._fxaService.internal.initialize(); - - let getCertCount = 0; - - let MockFxAccountsClient = function() { - FxAccountsClient.apply(this); - }; - MockFxAccountsClient.prototype = { - __proto__: FxAccountsClient.prototype, - signCertificate() { - ++getCertCount; - } - }; - - let mockFxAClient = new MockFxAccountsClient(); - browseridManager._fxaService.internal._fxAccountsClient = mockFxAClient; - - let didReturn401 = false; - let didReturn200 = false; - let mockTSC = mockTokenServer(() => { - if (getCertCount <= 1) { - didReturn401 = true; - return { - status: 401, - headers: {"content-type": "application/json"}, - body: JSON.stringify({}), - }; - } else { - didReturn200 = true; - return { - status: 200, - headers: {"content-type": "application/json"}, - body: JSON.stringify({ - id: "id", - key: "key", - api_endpoint: "http://example.com/", - uid: "uid", - duration: 300, - }) - }; - } - }); - - browseridManager._tokenServerClient = mockTSC; - - yield browseridManager.initializeWithCurrentIdentity(); - yield browseridManager.whenReadyToAuthenticate.promise; - - do_check_eq(getCertCount, 2); - do_check_true(didReturn401); - do_check_true(didReturn200); - do_check_true(browseridManager.account); - do_check_true(browseridManager._token); - do_check_true(browseridManager.hasValidToken()); - do_check_true(browseridManager.account); -}); - - - -add_task(function* test_getTokenErrorWithRetry() { +add_task(function test_getTokenErrorWithRetry() { _("tokenserver sends an observer notification on various backoff headers."); // Set Sync's backoffInterval to zero - after we simulated the backoff header @@ -547,7 +424,7 @@ add_task(function* test_getTokenErrorWithRetry() { Assert.ok(Status.backoffInterval >= 200000); }); -add_task(function* test_getKeysErrorWithBackoff() { +add_task(function test_getKeysErrorWithBackoff() { _("Auth server (via hawk) sends an observer notification on backoff headers."); // Set Sync's backoffInterval to zero - after we simulated the backoff header @@ -581,7 +458,7 @@ add_task(function* test_getKeysErrorWithBackoff() { Assert.ok(Status.backoffInterval >= 100000); }); -add_task(function* test_getKeysErrorWithRetry() { +add_task(function test_getKeysErrorWithRetry() { _("Auth server (via hawk) sends an observer notification on retry headers."); // Set Sync's backoffInterval to zero - after we simulated the backoff header @@ -615,7 +492,7 @@ add_task(function* test_getKeysErrorWithRetry() { Assert.ok(Status.backoffInterval >= 100000); }); -add_task(function* test_getHAWKErrors() { +add_task(function test_getHAWKErrors() { _("BrowserIDManager correctly handles various HAWK failures."); _("Arrange for a 401 - Sync should reflect an auth error."); @@ -648,7 +525,7 @@ add_task(function* test_getHAWKErrors() { Assert.equal(Status.login, LOGIN_FAILED_NETWORK_ERROR, "login state is LOGIN_FAILED_NETWORK_ERROR"); }); -add_task(function* test_getGetKeysFailing401() { +add_task(function test_getGetKeysFailing401() { _("BrowserIDManager correctly handles 401 responses fetching keys."); _("Arrange for a 401 - Sync should reflect an auth error."); @@ -669,7 +546,7 @@ add_task(function* test_getGetKeysFailing401() { Assert.equal(Status.login, LOGIN_FAILED_LOGIN_REJECTED, "login was rejected"); }); -add_task(function* test_getGetKeysFailing503() { +add_task(function test_getGetKeysFailing503() { _("BrowserIDManager correctly handles 5XX responses fetching keys."); _("Arrange for a 503 - Sync should reflect a network error."); @@ -690,7 +567,7 @@ add_task(function* test_getGetKeysFailing503() { Assert.equal(Status.login, LOGIN_FAILED_NETWORK_ERROR, "state reflects network error"); }); -add_task(function* test_getKeysMissing() { +add_task(function test_getKeysMissing() { _("BrowserIDManager correctly handles getKeys succeeding but not returning keys."); let browseridManager = new BrowserIDManager(); @@ -708,17 +585,7 @@ add_task(function* test_getKeysMissing() { fetchAndUnwrapKeys: function () { return Promise.resolve({}); }, - fxAccountsClient: new MockFxAccountsClient(), - newAccountState(credentials) { - // We only expect this to be called with null indicating the (mock) - // storage should be read. - if (credentials) { - throw new Error("Not expecting to have credentials passed"); - } - let storageManager = new MockFxaStorageManager(); - storageManager.initialize(identityConfig.fxaccount.user); - return new AccountState(storageManager); - }, + fxAccountsClient: new MockFxAccountsClient() }); // Add a mock to the currentAccountState object. @@ -730,6 +597,9 @@ add_task(function* test_getKeysMissing() { return Promise.resolve(this.cert.cert); }; + // Ensure the new FxAccounts mock has a signed-in user. + fxa.internal.currentAccountState.signedInUser = browseridManager._fxaService.internal.currentAccountState.signedInUser; + browseridManager._fxaService = fxa; yield browseridManager.initializeWithCurrentIdentity(); @@ -744,41 +614,6 @@ add_task(function* test_getKeysMissing() { Assert.ok(ex.message.indexOf("missing kA or kB") >= 0); }); -add_task(function* test_signedInUserMissing() { - _("BrowserIDManager detects getSignedInUser returning incomplete account data"); - - let browseridManager = new BrowserIDManager(); - let config = makeIdentityConfig(); - // Delete stored keys and the key fetch token. - delete identityConfig.fxaccount.user.kA; - delete identityConfig.fxaccount.user.kB; - delete identityConfig.fxaccount.user.keyFetchToken; - - configureFxAccountIdentity(browseridManager, identityConfig); - - let fxa = new FxAccounts({ - fetchAndUnwrapKeys: function () { - return Promise.resolve({}); - }, - fxAccountsClient: new MockFxAccountsClient(), - newAccountState(credentials) { - // We only expect this to be called with null indicating the (mock) - // storage should be read. - if (credentials) { - throw new Error("Not expecting to have credentials passed"); - } - let storageManager = new MockFxaStorageManager(); - storageManager.initialize(identityConfig.fxaccount.user); - return new AccountState(storageManager); - }, - }); - - browseridManager._fxaService = fxa; - - let status = yield browseridManager.unlockAndVerifyAuthState(); - Assert.equal(status, LOGIN_FAILED_LOGIN_REJECTED); -}); - // End of tests // Utility functions follow @@ -803,17 +638,7 @@ function* initializeIdentityWithHAWKResponseFactory(config, cbGetResponse) { callback.call(this); }, get: function(callback) { - // Skip /status requests (browserid_identity checks if the account still - // exists after an auth error) - if (this._uri.startsWith("http://mockedserver:9999/account/status")) { - this.response = { - status: 200, - headers: {"content-type": "application/json"}, - body: JSON.stringify({exists: true}), - }; - } else { - this.response = cbGetResponse("get", null, this._uri, this._credentials, this._extra); - } + this.response = cbGetResponse("get", null, this._uri, this._credentials, this._extra); callback.call(this); } } @@ -833,18 +658,11 @@ function* initializeIdentityWithHAWKResponseFactory(config, cbGetResponse) { fxaClient.hawk = new MockedHawkClient(); let internal = { fxAccountsClient: fxaClient, - newAccountState(credentials) { - // We only expect this to be called with null indicating the (mock) - // storage should be read. - if (credentials) { - throw new Error("Not expecting to have credentials passed"); - } - let storageManager = new MockFxaStorageManager(); - storageManager.initialize(config.fxaccount.user); - return new AccountState(storageManager); - }, } let fxa = new FxAccounts(internal); + fxa.internal.currentAccountState.signedInUser = { + accountData: config.fxaccount.user, + }; browseridManager._fxaService = fxa; browseridManager._signedInUser = null; @@ -862,29 +680,3 @@ function getTimestampDelta(hawkAuthHeader, now=Date.now()) { return Math.abs(getTimestamp(hawkAuthHeader) - now); } -function mockTokenServer(func) { - let requestLog = Log.repository.getLogger("testing.mock-rest"); - if (!requestLog.appenders.length) { // might as well see what it says :) - requestLog.addAppender(new Log.DumpAppender()); - requestLog.level = Log.Level.Trace; - } - function MockRESTRequest(url) {}; - MockRESTRequest.prototype = { - _log: requestLog, - setHeader: function() {}, - get: function(callback) { - this.response = func(); - callback.call(this); - } - } - // The mocked TokenServer client which will get the response. - function MockTSC() { } - MockTSC.prototype = new TokenServerClient(); - MockTSC.prototype.constructor = MockTSC; - MockTSC.prototype.newRESTRequest = function(url) { - return new MockRESTRequest(url); - } - // Arrange for the same observerPrefix as browserid_identity uses. - MockTSC.prototype.observerPrefix = "weave:service"; - return new MockTSC(); -} diff --git a/services/sync/tests/unit/test_clients_engine.js b/services/sync/tests/unit/test_clients_engine.js index d2123f80a..919913f82 100644 --- a/services/sync/tests/unit/test_clients_engine.js +++ b/services/sync/tests/unit/test_clients_engine.js @@ -12,7 +12,7 @@ Cu.import("resource://testing-common/services/sync/utils.js"); const MORE_THAN_CLIENTS_TTL_REFRESH = 691200; // 8 days const LESS_THAN_CLIENTS_TTL_REFRESH = 86400; // 1 day -var engine = Service.clientsEngine; +let engine = Service.clientsEngine; /** * Unpack the record with this ID, and verify that it has the same version that @@ -31,10 +31,10 @@ function check_record_version(user, id) { let cleartext = rec.decrypt(Service.collectionKeys.keyForCollection("clients")); _("Payload is " + JSON.stringify(cleartext)); - equal(Services.appinfo.version, cleartext.version); - equal(2, cleartext.protocols.length); - equal("1.1", cleartext.protocols[0]); - equal("1.5", cleartext.protocols[1]); + do_check_eq(Services.appinfo.version, cleartext.version); + do_check_eq(2, cleartext.protocols.length); + do_check_eq("1.1", cleartext.protocols[0]); + do_check_eq("1.5", cleartext.protocols[1]); } add_test(function test_bad_hmac() { @@ -64,7 +64,7 @@ add_test(function test_bad_hmac() { let coll = user.collection("clients"); // Treat a non-existent collection as empty. - equal(expectedCount, coll ? coll.count() : 0, stack); + do_check_eq(expectedCount, coll ? coll.count() : 0, stack); } function check_client_deleted(id) { @@ -77,7 +77,7 @@ add_test(function test_bad_hmac() { generateNewKeys(Service.collectionKeys); let serverKeys = Service.collectionKeys.asWBO("crypto", "keys"); serverKeys.encrypt(Service.identity.syncKeyBundle); - ok(serverKeys.upload(Service.resource(Service.cryptoKeysURL)).success); + do_check_true(serverKeys.upload(Service.resource(Service.cryptoKeysURL)).success); } try { @@ -89,11 +89,11 @@ add_test(function test_bad_hmac() { generateNewKeys(Service.collectionKeys); _("First sync, client record is uploaded"); - equal(engine.lastRecordUpload, 0); + do_check_eq(engine.lastRecordUpload, 0); check_clients_count(0); engine._sync(); check_clients_count(1); - ok(engine.lastRecordUpload > 0); + do_check_true(engine.lastRecordUpload > 0); // Our uploaded record has a version. check_record_version(user, engine.localID); @@ -109,7 +109,7 @@ add_test(function test_bad_hmac() { generateNewKeys(Service.collectionKeys); let serverKeys = Service.collectionKeys.asWBO("crypto", "keys"); serverKeys.encrypt(Service.identity.syncKeyBundle); - ok(serverKeys.upload(Service.resource(Service.cryptoKeysURL)).success); + do_check_true(serverKeys.upload(Service.resource(Service.cryptoKeysURL)).success); _("Sync."); engine._sync(); @@ -130,8 +130,8 @@ add_test(function test_bad_hmac() { engine._sync(); _("Old record was not deleted, new one uploaded."); - equal(deletedCollections.length, 0); - equal(deletedItems.length, 0); + do_check_eq(deletedCollections.length, 0); + do_check_eq(deletedItems.length, 0); check_clients_count(2); _("Now try the scenario where our keys are wrong *and* there's a bad record."); @@ -162,14 +162,14 @@ add_test(function test_bad_hmac() { generateNewKeys(Service.collectionKeys); let oldKey = Service.collectionKeys.keyForCollection(); - equal(deletedCollections.length, 0); - equal(deletedItems.length, 0); + do_check_eq(deletedCollections.length, 0); + do_check_eq(deletedItems.length, 0); engine._sync(); - equal(deletedItems.length, 1); + do_check_eq(deletedItems.length, 1); check_client_deleted(oldLocalID); check_clients_count(1); let newKey = Service.collectionKeys.keyForCollection(); - ok(!oldKey.equals(newKey)); + do_check_false(oldKey.equals(newKey)); } finally { Svc.Prefs.resetBranch(""); @@ -181,91 +181,18 @@ add_test(function test_bad_hmac() { add_test(function test_properties() { _("Test lastRecordUpload property"); try { - equal(Svc.Prefs.get("clients.lastRecordUpload"), undefined); - equal(engine.lastRecordUpload, 0); + do_check_eq(Svc.Prefs.get("clients.lastRecordUpload"), undefined); + do_check_eq(engine.lastRecordUpload, 0); let now = Date.now(); engine.lastRecordUpload = now / 1000; - equal(engine.lastRecordUpload, Math.floor(now / 1000)); + do_check_eq(engine.lastRecordUpload, Math.floor(now / 1000)); } finally { Svc.Prefs.resetBranch(""); run_next_test(); } }); -add_test(function test_full_sync() { - _("Ensure that Clients engine fetches all records for each sync."); - - let now = Date.now() / 1000; - let contents = { - meta: {global: {engines: {clients: {version: engine.version, - syncID: engine.syncID}}}}, - clients: {}, - crypto: {} - }; - let server = serverForUsers({"foo": "password"}, contents); - let user = server.user("foo"); - - new SyncTestingInfrastructure(server.server); - generateNewKeys(Service.collectionKeys); - - let activeID = Utils.makeGUID(); - server.insertWBO("foo", "clients", new ServerWBO(activeID, encryptPayload({ - id: activeID, - name: "Active client", - type: "desktop", - commands: [], - version: "48", - protocols: ["1.5"], - }), now - 10)); - - let deletedID = Utils.makeGUID(); - server.insertWBO("foo", "clients", new ServerWBO(deletedID, encryptPayload({ - id: deletedID, - name: "Client to delete", - type: "desktop", - commands: [], - version: "48", - protocols: ["1.5"], - }), now - 10)); - - try { - let store = engine._store; - - _("First sync. 2 records downloaded; our record uploaded."); - strictEqual(engine.lastRecordUpload, 0); - engine._sync(); - ok(engine.lastRecordUpload > 0); - deepEqual(user.collection("clients").keys().sort(), - [activeID, deletedID, engine.localID].sort(), - "Our record should be uploaded on first sync"); - deepEqual(Object.keys(store.getAllIDs()).sort(), - [activeID, deletedID, engine.localID].sort(), - "Other clients should be downloaded on first sync"); - - _("Delete a record, then sync again"); - let collection = server.getCollection("foo", "clients"); - collection.remove(deletedID); - // Simulate a timestamp update in info/collections. - engine.lastModified = now; - engine._sync(); - - _("Record should be updated"); - deepEqual(Object.keys(store.getAllIDs()).sort(), - [activeID, engine.localID].sort(), - "Deleted client should be removed on next sync"); - } finally { - Svc.Prefs.resetBranch(""); - Service.recordManager.clearCache(); - - try { - server.deleteCollections("foo"); - } finally { - server.stop(run_next_test); - } - } -}); - add_test(function test_sync() { _("Ensure that Clients engine uploads a new client record once a week."); @@ -288,30 +215,30 @@ add_test(function test_sync() { try { _("First sync. Client record is uploaded."); - equal(clientWBO(), undefined); - equal(engine.lastRecordUpload, 0); + do_check_eq(clientWBO(), undefined); + do_check_eq(engine.lastRecordUpload, 0); engine._sync(); - ok(!!clientWBO().payload); - ok(engine.lastRecordUpload > 0); + do_check_true(!!clientWBO().payload); + do_check_true(engine.lastRecordUpload > 0); _("Let's time travel more than a week back, new record should've been uploaded."); engine.lastRecordUpload -= MORE_THAN_CLIENTS_TTL_REFRESH; let lastweek = engine.lastRecordUpload; clientWBO().payload = undefined; engine._sync(); - ok(!!clientWBO().payload); - ok(engine.lastRecordUpload > lastweek); + do_check_true(!!clientWBO().payload); + do_check_true(engine.lastRecordUpload > lastweek); _("Remove client record."); engine.removeClientData(); - equal(clientWBO().payload, undefined); + do_check_eq(clientWBO().payload, undefined); _("Time travel one day back, no record uploaded."); engine.lastRecordUpload -= LESS_THAN_CLIENTS_TTL_REFRESH; let yesterday = engine.lastRecordUpload; engine._sync(); - equal(clientWBO().payload, undefined); - equal(engine.lastRecordUpload, yesterday); + do_check_eq(clientWBO().payload, undefined); + do_check_eq(engine.lastRecordUpload, yesterday); } finally { Svc.Prefs.resetBranch(""); @@ -336,16 +263,16 @@ add_test(function test_client_name_change() { let initialScore = tracker.score; - equal(Object.keys(tracker.changedIDs).length, 0); + do_check_eq(Object.keys(tracker.changedIDs).length, 0); Svc.Prefs.set("client.name", "new name"); _("new name: " + engine.localName); - notEqual(initialName, engine.localName); - equal(Object.keys(tracker.changedIDs).length, 1); - ok(engine.localID in tracker.changedIDs); - ok(tracker.score > initialScore); - ok(tracker.score >= SCORE_INCREMENT_XLARGE); + do_check_neq(initialName, engine.localName); + do_check_eq(Object.keys(tracker.changedIDs).length, 1); + do_check_true(engine.localID in tracker.changedIDs); + do_check_true(tracker.score > initialScore); + do_check_true(tracker.score >= SCORE_INCREMENT_XLARGE); Svc.Obs.notify("weave:engine:stop-tracking"); @@ -369,16 +296,15 @@ add_test(function test_send_command() { engine._sendCommandToClient(action, args, remoteId); let newRecord = store._remoteClients[remoteId]; - let clientCommands = engine._readCommands()[remoteId]; - notEqual(newRecord, undefined); - equal(clientCommands.length, 1); + do_check_neq(newRecord, undefined); + do_check_eq(newRecord.commands.length, 1); - let command = clientCommands[0]; - equal(command.command, action); - equal(command.args.length, 2); - deepEqual(command.args, args); + let command = newRecord.commands[0]; + do_check_eq(command.command, action); + do_check_eq(command.args.length, 2); + do_check_eq(command.args, args); - notEqual(tracker.changedIDs[remoteId], undefined); + do_check_neq(tracker.changedIDs[remoteId], undefined); run_next_test(); }); @@ -402,7 +328,7 @@ add_test(function test_command_validation() { ["__UNKNOWN__", [], false] ]; - for (let [action, args, expectedResult] of testCommands) { + for each (let [action, args, expectedResult] in testCommands) { let remoteId = Utils.makeGUID(); let rec = new ClientsRec("clients", remoteId); @@ -412,26 +338,24 @@ add_test(function test_command_validation() { engine.sendCommand(action, args, remoteId); let newRecord = store._remoteClients[remoteId]; - notEqual(newRecord, undefined); - - let clientCommands = engine._readCommands()[remoteId]; + do_check_neq(newRecord, undefined); if (expectedResult) { _("Ensuring command is sent: " + action); - equal(clientCommands.length, 1); + do_check_eq(newRecord.commands.length, 1); - let command = clientCommands[0]; - equal(command.command, action); - deepEqual(command.args, args); + let command = newRecord.commands[0]; + do_check_eq(command.command, action); + do_check_eq(command.args, args); - notEqual(engine._tracker, undefined); - notEqual(engine._tracker.changedIDs[remoteId], undefined); + do_check_neq(engine._tracker, undefined); + do_check_neq(engine._tracker.changedIDs[remoteId], undefined); } else { _("Ensuring command is scrubbed: " + action); - equal(clientCommands, undefined); + do_check_eq(newRecord.commands, undefined); if (store._tracker) { - equal(engine._tracker[remoteId], undefined); + do_check_eq(engine._tracker[remoteId], undefined); } } @@ -455,11 +379,10 @@ add_test(function test_command_duplication() { engine.sendCommand(action, args, remoteId); let newRecord = store._remoteClients[remoteId]; - let clientCommands = engine._readCommands()[remoteId]; - equal(clientCommands.length, 1); + do_check_eq(newRecord.commands.length, 1); _("Check variant args length"); - engine._saveCommands({}); + newRecord.commands = []; action = "resetEngine"; engine.sendCommand(action, [{ x: "foo" }], remoteId); @@ -468,8 +391,7 @@ add_test(function test_command_duplication() { _("Make sure we spot a real dupe argument."); engine.sendCommand(action, [{ x: "bar" }], remoteId); - clientCommands = engine._readCommands()[remoteId]; - equal(clientCommands.length, 2); + do_check_eq(newRecord.commands.length, 2); run_next_test(); }); @@ -486,7 +408,7 @@ add_test(function test_command_invalid_client() { error = ex; } - equal(error.message.indexOf("Unknown remote client ID: "), 0); + do_check_eq(error.message.indexOf("Unknown remote client ID: "), 0); run_next_test(); }); @@ -500,174 +422,13 @@ add_test(function test_process_incoming_commands() { var handler = function() { Svc.Obs.remove(ev, handler); - - Svc.Prefs.resetBranch(""); - Service.recordManager.clearCache(); - engine._resetClient(); - run_next_test(); }; Svc.Obs.add(ev, handler); // logout command causes processIncomingCommands to return explicit false. - ok(!engine.processIncomingCommands()); -}); - -add_test(function test_filter_duplicate_names() { - _("Ensure that we exclude clients with identical names that haven't synced in a week."); - - let now = Date.now() / 1000; - let contents = { - meta: {global: {engines: {clients: {version: engine.version, - syncID: engine.syncID}}}}, - clients: {}, - crypto: {} - }; - let server = serverForUsers({"foo": "password"}, contents); - let user = server.user("foo"); - - new SyncTestingInfrastructure(server.server); - generateNewKeys(Service.collectionKeys); - - // Synced recently. - let recentID = Utils.makeGUID(); - server.insertWBO("foo", "clients", new ServerWBO(recentID, encryptPayload({ - id: recentID, - name: "My Phone", - type: "mobile", - commands: [], - version: "48", - protocols: ["1.5"], - }), now - 10)); - - // Dupe of our client, synced more than 1 week ago. - let dupeID = Utils.makeGUID(); - server.insertWBO("foo", "clients", new ServerWBO(dupeID, encryptPayload({ - id: dupeID, - name: engine.localName, - type: "desktop", - commands: [], - version: "48", - protocols: ["1.5"], - }), now - 604810)); - - // Synced more than 1 week ago, but not a dupe. - let oldID = Utils.makeGUID(); - server.insertWBO("foo", "clients", new ServerWBO(oldID, encryptPayload({ - id: oldID, - name: "My old desktop", - type: "desktop", - commands: [], - version: "48", - protocols: ["1.5"], - }), now - 604820)); - - try { - let store = engine._store; - - _("First sync"); - strictEqual(engine.lastRecordUpload, 0); - engine._sync(); - ok(engine.lastRecordUpload > 0); - deepEqual(user.collection("clients").keys().sort(), - [recentID, dupeID, oldID, engine.localID].sort(), - "Our record should be uploaded on first sync"); - - deepEqual(Object.keys(store.getAllIDs()).sort(), - [recentID, dupeID, oldID, engine.localID].sort(), - "Duplicate ID should remain in getAllIDs"); - ok(engine._store.itemExists(dupeID), "Dupe ID should be considered as existing for Sync methods."); - ok(!engine.remoteClientExists(dupeID), "Dupe ID should not be considered as existing for external methods."); - - // dupe desktop should not appear in .deviceTypes. - equal(engine.deviceTypes.get("desktop"), 2); - equal(engine.deviceTypes.get("mobile"), 1); - - // dupe desktop should not appear in stats - deepEqual(engine.stats, { - hasMobile: 1, - names: [engine.localName, "My Phone", "My old desktop"], - numClients: 3, - }); - - ok(engine.remoteClientExists(oldID), "non-dupe ID should exist."); - ok(!engine.remoteClientExists(dupeID), "dupe ID should not exist"); - equal(engine.remoteClients.length, 2, "dupe should not be in remoteClients"); - - // Check that a subsequent Sync doesn't report anything as being processed. - let counts; - Svc.Obs.add("weave:engine:sync:applied", function observe(subject, data) { - Svc.Obs.remove("weave:engine:sync:applied", observe); - counts = subject; - }); - - engine._sync(); - equal(counts.applied, 0); // We didn't report applying any records. - equal(counts.reconciled, 4); // We reported reconcilliation for all records - equal(counts.succeeded, 0); - equal(counts.failed, 0); - equal(counts.newFailed, 0); - - _("Broadcast logout to all clients"); - engine.sendCommand("logout", []); - engine._sync(); - - let collection = server.getCollection("foo", "clients"); - let recentPayload = JSON.parse(JSON.parse(collection.payload(recentID)).ciphertext); - deepEqual(recentPayload.commands, [{ command: "logout", args: [] }], - "Should send commands to the recent client"); - - let oldPayload = JSON.parse(JSON.parse(collection.payload(oldID)).ciphertext); - deepEqual(oldPayload.commands, [{ command: "logout", args: [] }], - "Should send commands to the week-old client"); - - let dupePayload = JSON.parse(JSON.parse(collection.payload(dupeID)).ciphertext); - deepEqual(dupePayload.commands, [], - "Should not send commands to the dupe client"); - - _("Update the dupe client's modified time"); - server.insertWBO("foo", "clients", new ServerWBO(dupeID, encryptPayload({ - id: dupeID, - name: engine.localName, - type: "desktop", - commands: [], - version: "48", - protocols: ["1.5"], - }), now - 10)); - - _("Second sync."); - engine._sync(); - - deepEqual(Object.keys(store.getAllIDs()).sort(), - [recentID, oldID, dupeID, engine.localID].sort(), - "Stale client synced, so it should no longer be marked as a dupe"); - - ok(engine.remoteClientExists(dupeID), "Dupe ID should appear as it synced."); - - // Recently synced dupe desktop should appear in .deviceTypes. - equal(engine.deviceTypes.get("desktop"), 3); - - // Recently synced dupe desktop should now appear in stats - deepEqual(engine.stats, { - hasMobile: 1, - names: [engine.localName, "My Phone", engine.localName, "My old desktop"], - numClients: 4, - }); - - ok(engine.remoteClientExists(dupeID), "recently synced dupe ID should now exist"); - equal(engine.remoteClients.length, 3, "recently synced dupe should now be in remoteClients"); - - } finally { - Svc.Prefs.resetBranch(""); - Service.recordManager.clearCache(); - - try { - server.deleteCollections("foo"); - } finally { - server.stop(run_next_test); - } - } + do_check_false(engine.processIncomingCommands()); }); add_test(function test_command_sync() { @@ -693,58 +454,40 @@ add_test(function test_command_sync() { } _("Create remote client record"); - server.insertWBO("foo", "clients", new ServerWBO(remoteId, encryptPayload({ - id: remoteId, - name: "Remote client", - type: "desktop", - commands: [], - version: "48", - protocols: ["1.5"], - }), Date.now() / 1000)); + let rec = new ClientsRec("clients", remoteId); + engine._store.create(rec); + let remoteRecord = engine._store.createRecord(remoteId, "clients"); + engine.sendCommand("wipeAll", []); + + let clientRecord = engine._store._remoteClients[remoteId]; + do_check_neq(clientRecord, undefined); + do_check_eq(clientRecord.commands.length, 1); try { _("Syncing."); engine._sync(); - - _("Checking remote record was downloaded."); - let clientRecord = engine._store._remoteClients[remoteId]; - notEqual(clientRecord, undefined); - equal(clientRecord.commands.length, 0); - - _("Send a command to the remote client."); - engine.sendCommand("wipeAll", []); - let clientCommands = engine._readCommands()[remoteId]; - equal(clientCommands.length, 1); - engine._sync(); - _("Checking record was uploaded."); - notEqual(clientWBO(engine.localID).payload, undefined); - ok(engine.lastRecordUpload > 0); + do_check_neq(clientWBO(engine.localID).payload, undefined); + do_check_true(engine.lastRecordUpload > 0); - notEqual(clientWBO(remoteId).payload, undefined); + do_check_neq(clientWBO(remoteId).payload, undefined); Svc.Prefs.set("client.GUID", remoteId); engine._resetClient(); - equal(engine.localID, remoteId); + do_check_eq(engine.localID, remoteId); _("Performing sync on resetted client."); engine._sync(); - notEqual(engine.localCommands, undefined); - equal(engine.localCommands.length, 1); + do_check_neq(engine.localCommands, undefined); + do_check_eq(engine.localCommands.length, 1); let command = engine.localCommands[0]; - equal(command.command, "wipeAll"); - equal(command.args.length, 0); + do_check_eq(command.command, "wipeAll"); + do_check_eq(command.args.length, 0); } finally { Svc.Prefs.resetBranch(""); Service.recordManager.clearCache(); - - try { - let collection = server.getCollection("foo", "clients"); - collection.remove(remoteId); - } finally { - server.stop(run_next_test); - } + server.stop(run_next_test); } }); @@ -769,19 +512,18 @@ add_test(function test_send_uri_to_client_for_display() { let newRecord = store._remoteClients[remoteId]; - notEqual(newRecord, undefined); - let clientCommands = engine._readCommands()[remoteId]; - equal(clientCommands.length, 1); + do_check_neq(newRecord, undefined); + do_check_eq(newRecord.commands.length, 1); - let command = clientCommands[0]; - equal(command.command, "displayURI"); - equal(command.args.length, 3); - equal(command.args[0], uri); - equal(command.args[1], engine.localID); - equal(command.args[2], title); + let command = newRecord.commands[0]; + do_check_eq(command.command, "displayURI"); + do_check_eq(command.args.length, 3); + do_check_eq(command.args[0], uri); + do_check_eq(command.args[1], engine.localID); + do_check_eq(command.args[2], title); - ok(tracker.score > initialScore); - ok(tracker.score - initialScore >= SCORE_INCREMENT_XLARGE); + do_check_true(tracker.score > initialScore); + do_check_true(tracker.score - initialScore >= SCORE_INCREMENT_XLARGE); _("Ensure unknown client IDs result in exception."); let unknownId = Utils.makeGUID(); @@ -793,11 +535,7 @@ add_test(function test_send_uri_to_client_for_display() { error = ex; } - equal(error.message.indexOf("Unknown remote client ID: "), 0); - - Svc.Prefs.resetBranch(""); - Service.recordManager.clearCache(); - engine._resetClient(); + do_check_eq(error.message.indexOf("Unknown remote client ID: "), 0); run_next_test(); }); @@ -821,26 +559,22 @@ add_test(function test_receive_display_uri() { // Received 'displayURI' command should result in the topic defined below // being called. - let ev = "weave:engine:clients:display-uris"; + let ev = "weave:engine:clients:display-uri"; let handler = function(subject, data) { Svc.Obs.remove(ev, handler); - equal(subject[0].uri, uri); - equal(subject[0].clientId, remoteId); - equal(subject[0].title, title); - equal(data, null); + do_check_eq(subject.uri, uri); + do_check_eq(subject.client, remoteId); + do_check_eq(subject.title, title); + do_check_eq(data, null); run_next_test(); }; Svc.Obs.add(ev, handler); - ok(engine.processIncomingCommands()); - - Svc.Prefs.resetBranch(""); - Service.recordManager.clearCache(); - engine._resetClient(); + do_check_true(engine.processIncomingCommands()); }); add_test(function test_optional_client_fields() { @@ -848,590 +582,27 @@ add_test(function test_optional_client_fields() { const SUPPORTED_PROTOCOL_VERSIONS = ["1.1", "1.5"]; let local = engine._store.createRecord(engine.localID, "clients"); - equal(local.name, engine.localName); - equal(local.type, engine.localType); - equal(local.version, Services.appinfo.version); - deepEqual(local.protocols, SUPPORTED_PROTOCOL_VERSIONS); + do_check_eq(local.name, engine.localName); + do_check_eq(local.type, engine.localType); + do_check_eq(local.version, Services.appinfo.version); + do_check_array_eq(local.protocols, SUPPORTED_PROTOCOL_VERSIONS); // Optional fields. // Make sure they're what they ought to be... - equal(local.os, Services.appinfo.OS); - equal(local.appPackage, Services.appinfo.ID); + do_check_eq(local.os, Services.appinfo.OS); + do_check_eq(local.appPackage, Services.appinfo.ID); // ... and also that they're non-empty. - ok(!!local.os); - ok(!!local.appPackage); - ok(!!local.application); + do_check_true(!!local.os); + do_check_true(!!local.appPackage); + do_check_true(!!local.application); // We don't currently populate device or formfactor. // See Bug 1100722, Bug 1100723. - engine._resetClient(); run_next_test(); }); -add_test(function test_merge_commands() { - _("Verifies local commands for remote clients are merged with the server's"); - - let now = Date.now() / 1000; - let contents = { - meta: {global: {engines: {clients: {version: engine.version, - syncID: engine.syncID}}}}, - clients: {}, - crypto: {} - }; - let server = serverForUsers({"foo": "password"}, contents); - let user = server.user("foo"); - - new SyncTestingInfrastructure(server.server); - generateNewKeys(Service.collectionKeys); - - let desktopID = Utils.makeGUID(); - server.insertWBO("foo", "clients", new ServerWBO(desktopID, encryptPayload({ - id: desktopID, - name: "Desktop client", - type: "desktop", - commands: [{ - command: "displayURI", - args: ["https://example.com", engine.localID, "Yak Herders Anonymous"], - }], - version: "48", - protocols: ["1.5"], - }), now - 10)); - - let mobileID = Utils.makeGUID(); - server.insertWBO("foo", "clients", new ServerWBO(mobileID, encryptPayload({ - id: mobileID, - name: "Mobile client", - type: "mobile", - commands: [{ - command: "logout", - args: [], - }], - version: "48", - protocols: ["1.5"], - }), now - 10)); - - try { - let store = engine._store; - - _("First sync. 2 records downloaded."); - strictEqual(engine.lastRecordUpload, 0); - engine._sync(); - - _("Broadcast logout to all clients"); - engine.sendCommand("logout", []); - engine._sync(); - - let collection = server.getCollection("foo", "clients"); - let desktopPayload = JSON.parse(JSON.parse(collection.payload(desktopID)).ciphertext); - deepEqual(desktopPayload.commands, [{ - command: "displayURI", - args: ["https://example.com", engine.localID, "Yak Herders Anonymous"], - }, { - command: "logout", - args: [], - }], "Should send the logout command to the desktop client"); - - let mobilePayload = JSON.parse(JSON.parse(collection.payload(mobileID)).ciphertext); - deepEqual(mobilePayload.commands, [{ command: "logout", args: [] }], - "Should not send a duplicate logout to the mobile client"); - } finally { - Svc.Prefs.resetBranch(""); - Service.recordManager.clearCache(); - engine._resetClient(); - - try { - server.deleteCollections("foo"); - } finally { - server.stop(run_next_test); - } - } -}); - -add_test(function test_duplicate_remote_commands() { - _("Verifies local commands for remote clients are sent only once (bug 1289287)"); - - let now = Date.now() / 1000; - let contents = { - meta: {global: {engines: {clients: {version: engine.version, - syncID: engine.syncID}}}}, - clients: {}, - crypto: {} - }; - let server = serverForUsers({"foo": "password"}, contents); - let user = server.user("foo"); - - new SyncTestingInfrastructure(server.server); - generateNewKeys(Service.collectionKeys); - - let desktopID = Utils.makeGUID(); - server.insertWBO("foo", "clients", new ServerWBO(desktopID, encryptPayload({ - id: desktopID, - name: "Desktop client", - type: "desktop", - commands: [], - version: "48", - protocols: ["1.5"], - }), now - 10)); - - try { - let store = engine._store; - - _("First sync. 1 record downloaded."); - strictEqual(engine.lastRecordUpload, 0); - engine._sync(); - - _("Send tab to client"); - engine.sendCommand("displayURI", ["https://example.com", engine.localID, "Yak Herders Anonymous"]); - engine._sync(); - - _("Simulate the desktop client consuming the command and syncing to the server"); - server.insertWBO("foo", "clients", new ServerWBO(desktopID, encryptPayload({ - id: desktopID, - name: "Desktop client", - type: "desktop", - commands: [], - version: "48", - protocols: ["1.5"], - }), now - 10)); - - _("Send another tab to the desktop client"); - engine.sendCommand("displayURI", ["https://foobar.com", engine.localID, "Foo bar!"], desktopID); - engine._sync(); - - let collection = server.getCollection("foo", "clients"); - let desktopPayload = JSON.parse(JSON.parse(collection.payload(desktopID)).ciphertext); - deepEqual(desktopPayload.commands, [{ - command: "displayURI", - args: ["https://foobar.com", engine.localID, "Foo bar!"], - }], "Should only send the second command to the desktop client"); - } finally { - Svc.Prefs.resetBranch(""); - Service.recordManager.clearCache(); - engine._resetClient(); - - try { - server.deleteCollections("foo"); - } finally { - server.stop(run_next_test); - } - } -}); - -add_test(function test_upload_after_reboot() { - _("Multiple downloads, reboot, then upload (bug 1289287)"); - - let now = Date.now() / 1000; - let contents = { - meta: {global: {engines: {clients: {version: engine.version, - syncID: engine.syncID}}}}, - clients: {}, - crypto: {} - }; - let server = serverForUsers({"foo": "password"}, contents); - let user = server.user("foo"); - - new SyncTestingInfrastructure(server.server); - generateNewKeys(Service.collectionKeys); - - let deviceBID = Utils.makeGUID(); - let deviceCID = Utils.makeGUID(); - server.insertWBO("foo", "clients", new ServerWBO(deviceBID, encryptPayload({ - id: deviceBID, - name: "Device B", - type: "desktop", - commands: [{ - command: "displayURI", args: ["https://deviceclink.com", deviceCID, "Device C link"] - }], - version: "48", - protocols: ["1.5"], - }), now - 10)); - server.insertWBO("foo", "clients", new ServerWBO(deviceCID, encryptPayload({ - id: deviceCID, - name: "Device C", - type: "desktop", - commands: [], - version: "48", - protocols: ["1.5"], - }), now - 10)); - - try { - let store = engine._store; - - _("First sync. 2 records downloaded."); - strictEqual(engine.lastRecordUpload, 0); - engine._sync(); - - _("Send tab to client"); - engine.sendCommand("displayURI", ["https://example.com", engine.localID, "Yak Herders Anonymous"], deviceBID); - - const oldUploadOutgoing = SyncEngine.prototype._uploadOutgoing; - SyncEngine.prototype._uploadOutgoing = () => engine._onRecordsWritten.call(engine, [], [deviceBID]); - engine._sync(); - - let collection = server.getCollection("foo", "clients"); - let deviceBPayload = JSON.parse(JSON.parse(collection.payload(deviceBID)).ciphertext); - deepEqual(deviceBPayload.commands, [{ - command: "displayURI", args: ["https://deviceclink.com", deviceCID, "Device C link"] - }], "Should be the same because the upload failed"); - - _("Simulate the client B consuming the command and syncing to the server"); - server.insertWBO("foo", "clients", new ServerWBO(deviceBID, encryptPayload({ - id: deviceBID, - name: "Device B", - type: "desktop", - commands: [], - version: "48", - protocols: ["1.5"], - }), now - 10)); - - // Simulate reboot - SyncEngine.prototype._uploadOutgoing = oldUploadOutgoing; - engine = Service.clientsEngine = new ClientEngine(Service); - - engine._sync(); - - deviceBPayload = JSON.parse(JSON.parse(collection.payload(deviceBID)).ciphertext); - deepEqual(deviceBPayload.commands, [{ - command: "displayURI", - args: ["https://example.com", engine.localID, "Yak Herders Anonymous"], - }], "Should only had written our outgoing command"); - } finally { - Svc.Prefs.resetBranch(""); - Service.recordManager.clearCache(); - engine._resetClient(); - - try { - server.deleteCollections("foo"); - } finally { - server.stop(run_next_test); - } - } -}); - -add_test(function test_keep_cleared_commands_after_reboot() { - _("Download commands, fail upload, reboot, then apply new commands (bug 1289287)"); - - let now = Date.now() / 1000; - let contents = { - meta: {global: {engines: {clients: {version: engine.version, - syncID: engine.syncID}}}}, - clients: {}, - crypto: {} - }; - let server = serverForUsers({"foo": "password"}, contents); - let user = server.user("foo"); - - new SyncTestingInfrastructure(server.server); - generateNewKeys(Service.collectionKeys); - - let deviceBID = Utils.makeGUID(); - let deviceCID = Utils.makeGUID(); - server.insertWBO("foo", "clients", new ServerWBO(engine.localID, encryptPayload({ - id: engine.localID, - name: "Device A", - type: "desktop", - commands: [{ - command: "displayURI", args: ["https://deviceblink.com", deviceBID, "Device B link"] - }, - { - command: "displayURI", args: ["https://deviceclink.com", deviceCID, "Device C link"] - }], - version: "48", - protocols: ["1.5"], - }), now - 10)); - server.insertWBO("foo", "clients", new ServerWBO(deviceBID, encryptPayload({ - id: deviceBID, - name: "Device B", - type: "desktop", - commands: [], - version: "48", - protocols: ["1.5"], - }), now - 10)); - server.insertWBO("foo", "clients", new ServerWBO(deviceCID, encryptPayload({ - id: deviceCID, - name: "Device C", - type: "desktop", - commands: [], - version: "48", - protocols: ["1.5"], - }), now - 10)); - - try { - let store = engine._store; - - _("First sync. Download remote and our record."); - strictEqual(engine.lastRecordUpload, 0); - - let collection = server.getCollection("foo", "clients"); - const oldUploadOutgoing = SyncEngine.prototype._uploadOutgoing; - SyncEngine.prototype._uploadOutgoing = () => engine._onRecordsWritten.call(engine, [], [deviceBID]); - let commandsProcessed = 0; - engine._handleDisplayURIs = (uris) => { commandsProcessed = uris.length }; - - engine._sync(); - engine.processIncomingCommands(); // Not called by the engine.sync(), gotta call it ourselves - equal(commandsProcessed, 2, "We processed 2 commands"); - - let localRemoteRecord = JSON.parse(JSON.parse(collection.payload(engine.localID)).ciphertext); - deepEqual(localRemoteRecord.commands, [{ - command: "displayURI", args: ["https://deviceblink.com", deviceBID, "Device B link"] - }, - { - command: "displayURI", args: ["https://deviceclink.com", deviceCID, "Device C link"] - }], "Should be the same because the upload failed"); - - // Another client sends another link - server.insertWBO("foo", "clients", new ServerWBO(engine.localID, encryptPayload({ - id: engine.localID, - name: "Device A", - type: "desktop", - commands: [{ - command: "displayURI", args: ["https://deviceblink.com", deviceBID, "Device B link"] - }, - { - command: "displayURI", args: ["https://deviceclink.com", deviceCID, "Device C link"] - }, - { - command: "displayURI", args: ["https://deviceclink2.com", deviceCID, "Device C link 2"] - }], - version: "48", - protocols: ["1.5"], - }), now - 10)); - - // Simulate reboot - SyncEngine.prototype._uploadOutgoing = oldUploadOutgoing; - engine = Service.clientsEngine = new ClientEngine(Service); - - commandsProcessed = 0; - engine._handleDisplayURIs = (uris) => { commandsProcessed = uris.length }; - engine._sync(); - engine.processIncomingCommands(); - equal(commandsProcessed, 1, "We processed one command (the other were cleared)"); - - localRemoteRecord = JSON.parse(JSON.parse(collection.payload(deviceBID)).ciphertext); - deepEqual(localRemoteRecord.commands, [], "Should be empty"); - } finally { - Svc.Prefs.resetBranch(""); - Service.recordManager.clearCache(); - - // Reset service (remove mocks) - engine = Service.clientsEngine = new ClientEngine(Service); - engine._resetClient(); - - try { - server.deleteCollections("foo"); - } finally { - server.stop(run_next_test); - } - } -}); - -add_test(function test_deleted_commands() { - _("Verifies commands for a deleted client are discarded"); - - let now = Date.now() / 1000; - let contents = { - meta: {global: {engines: {clients: {version: engine.version, - syncID: engine.syncID}}}}, - clients: {}, - crypto: {} - }; - let server = serverForUsers({"foo": "password"}, contents); - let user = server.user("foo"); - - new SyncTestingInfrastructure(server.server); - generateNewKeys(Service.collectionKeys); - - let activeID = Utils.makeGUID(); - server.insertWBO("foo", "clients", new ServerWBO(activeID, encryptPayload({ - id: activeID, - name: "Active client", - type: "desktop", - commands: [], - version: "48", - protocols: ["1.5"], - }), now - 10)); - - let deletedID = Utils.makeGUID(); - server.insertWBO("foo", "clients", new ServerWBO(deletedID, encryptPayload({ - id: deletedID, - name: "Client to delete", - type: "desktop", - commands: [], - version: "48", - protocols: ["1.5"], - }), now - 10)); - - try { - let store = engine._store; - - _("First sync. 2 records downloaded."); - engine._sync(); - - _("Delete a record on the server."); - let collection = server.getCollection("foo", "clients"); - collection.remove(deletedID); - - _("Broadcast a command to all clients"); - engine.sendCommand("logout", []); - engine._sync(); - - deepEqual(collection.keys().sort(), [activeID, engine.localID].sort(), - "Should not reupload deleted clients"); - - let activePayload = JSON.parse(JSON.parse(collection.payload(activeID)).ciphertext); - deepEqual(activePayload.commands, [{ command: "logout", args: [] }], - "Should send the command to the active client"); - } finally { - Svc.Prefs.resetBranch(""); - Service.recordManager.clearCache(); - engine._resetClient(); - - try { - server.deleteCollections("foo"); - } finally { - server.stop(run_next_test); - } - } -}); - -add_test(function test_send_uri_ack() { - _("Ensure a sent URI is deleted when the client syncs"); - - let now = Date.now() / 1000; - let contents = { - meta: {global: {engines: {clients: {version: engine.version, - syncID: engine.syncID}}}}, - clients: {}, - crypto: {} - }; - let server = serverForUsers({"foo": "password"}, contents); - let user = server.user("foo"); - - new SyncTestingInfrastructure(server.server); - generateNewKeys(Service.collectionKeys); - - try { - let fakeSenderID = Utils.makeGUID(); - - _("Initial sync for empty clients collection"); - engine._sync(); - let collection = server.getCollection("foo", "clients"); - let ourPayload = JSON.parse(JSON.parse(collection.payload(engine.localID)).ciphertext); - ok(ourPayload, "Should upload our client record"); - - _("Send a URL to the device on the server"); - ourPayload.commands = [{ - command: "displayURI", - args: ["https://example.com", fakeSenderID, "Yak Herders Anonymous"], - }]; - server.insertWBO("foo", "clients", new ServerWBO(engine.localID, encryptPayload(ourPayload), now)); - - _("Sync again"); - engine._sync(); - deepEqual(engine.localCommands, [{ - command: "displayURI", - args: ["https://example.com", fakeSenderID, "Yak Herders Anonymous"], - }], "Should receive incoming URI"); - ok(engine.processIncomingCommands(), "Should process incoming commands"); - const clearedCommands = engine._readCommands()[engine.localID]; - deepEqual(clearedCommands, [{ - command: "displayURI", - args: ["https://example.com", fakeSenderID, "Yak Herders Anonymous"], - }], "Should mark the commands as cleared after processing"); - - _("Check that the command was removed on the server"); - engine._sync(); - ourPayload = JSON.parse(JSON.parse(collection.payload(engine.localID)).ciphertext); - ok(ourPayload, "Should upload the synced client record"); - deepEqual(ourPayload.commands, [], "Should not reupload cleared commands"); - } finally { - Svc.Prefs.resetBranch(""); - Service.recordManager.clearCache(); - engine._resetClient(); - - try { - server.deleteCollections("foo"); - } finally { - server.stop(run_next_test); - } - } -}); - -add_test(function test_command_sync() { - _("Notify other clients when writing their record."); - - engine._store.wipe(); - generateNewKeys(Service.collectionKeys); - - let contents = { - meta: {global: {engines: {clients: {version: engine.version, - syncID: engine.syncID}}}}, - clients: {}, - crypto: {} - }; - let server = serverForUsers({"foo": "password"}, contents); - new SyncTestingInfrastructure(server.server); - - let user = server.user("foo"); - let collection = server.getCollection("foo", "clients"); - let remoteId = Utils.makeGUID(); - let remoteId2 = Utils.makeGUID(); - - function clientWBO(id) { - return user.collection("clients").wbo(id); - } - - _("Create remote client record 1"); - server.insertWBO("foo", "clients", new ServerWBO(remoteId, encryptPayload({ - id: remoteId, - name: "Remote client", - type: "desktop", - commands: [], - version: "48", - protocols: ["1.5"] - }), Date.now() / 1000)); - - _("Create remote client record 2"); - server.insertWBO("foo", "clients", new ServerWBO(remoteId2, encryptPayload({ - id: remoteId2, - name: "Remote client 2", - type: "mobile", - commands: [], - version: "48", - protocols: ["1.5"] - }), Date.now() / 1000)); - - try { - equal(collection.count(), 2, "2 remote records written"); - engine._sync(); - equal(collection.count(), 3, "3 remote records written (+1 for the synced local record)"); - - let notifiedIds; - engine.sendCommand("wipeAll", []); - engine._tracker.addChangedID(engine.localID); - engine.getClientFxaDeviceId = (id) => "fxa-" + id; - engine._notifyCollectionChanged = (ids) => (notifiedIds = ids); - _("Syncing."); - engine._sync(); - deepEqual(notifiedIds, ["fxa-fake-guid-00","fxa-fake-guid-01"]); - ok(!notifiedIds.includes(engine.getClientFxaDeviceId(engine.localID)), - "We never notify the local device"); - - } finally { - Svc.Prefs.resetBranch(""); - Service.recordManager.clearCache(); - - try { - server.deleteCollections("foo"); - } finally { - server.stop(run_next_test); - } - } -}); - function run_test() { initTestLogging("Trace"); Log.repository.getLogger("Sync.Engine.Clients").level = Log.Level.Trace; diff --git a/services/sync/tests/unit/test_collection_getBatched.js b/services/sync/tests/unit/test_collection_getBatched.js deleted file mode 100644 index c6523d497..000000000 --- a/services/sync/tests/unit/test_collection_getBatched.js +++ /dev/null @@ -1,195 +0,0 @@ -/* Any copyright is dedicated to the Public Domain. - http://creativecommons.org/publicdomain/zero/1.0/ */ - -Cu.import("resource://services-sync/record.js"); -Cu.import("resource://services-sync/service.js"); - -function run_test() { - initTestLogging("Trace"); - Log.repository.getLogger("Sync.Collection").level = Log.Level.Trace; - run_next_test(); -} - -function recordRange(lim, offset, total) { - let res = []; - for (let i = offset; i < Math.min(lim + offset, total); ++i) { - res.push(JSON.stringify({ id: String(i), payload: "test:" + i })); - } - return res.join("\n") + "\n"; -} - -function get_test_collection_info({ totalRecords, batchSize, lastModified, - throwAfter = Infinity, - interruptedAfter = Infinity }) { - let coll = new Collection("http://example.com/test/", WBORecord, Service); - coll.full = true; - let requests = []; - let responses = []; - let sawRecord = false; - coll.get = function() { - ok(!sawRecord); // make sure we call record handler after all requests. - let limit = +this.limit; - let offset = 0; - if (this.offset) { - equal(this.offset.slice(0, 6), "foobar"); - offset = +this.offset.slice(6); - } - requests.push({ - limit, - offset, - spec: this.spec, - headers: Object.assign({}, this.headers) - }); - if (--throwAfter === 0) { - throw "Some Network Error"; - } - let body = recordRange(limit, offset, totalRecords); - this._onProgress.call({ _data: body }); - let response = { - body, - success: true, - status: 200, - headers: {} - }; - if (--interruptedAfter === 0) { - response.success = false; - response.status = 412; - response.body = ""; - } else if (offset + limit < totalRecords) { - // Ensure we're treating this as an opaque string, since the docs say - // it might not be numeric. - response.headers["x-weave-next-offset"] = "foobar" + (offset + batchSize); - } - response.headers["x-last-modified"] = lastModified; - responses.push(response); - return response; - }; - - let records = []; - coll.recordHandler = function(record) { - sawRecord = true; - // ensure records are coming in in the right order - equal(record.id, String(records.length)); - equal(record.payload, "test:" + records.length); - records.push(record); - }; - return { records, responses, requests, coll }; -} - -add_test(function test_success() { - const totalRecords = 11; - const batchSize = 2; - const lastModified = "111111"; - let { records, responses, requests, coll } = get_test_collection_info({ - totalRecords, - batchSize, - lastModified, - }); - let response = coll.getBatched(batchSize); - - equal(requests.length, Math.ceil(totalRecords / batchSize)); - - // records are mostly checked in recordHandler, we just care about the length - equal(records.length, totalRecords); - - // ensure we're returning the last response - equal(responses[responses.length - 1], response); - - // check first separately since its a bit of a special case - ok(!requests[0].headers["x-if-unmodified-since"]); - ok(!requests[0].offset); - equal(requests[0].limit, batchSize); - let expectedOffset = 2; - for (let i = 1; i < requests.length; ++i) { - let req = requests[i]; - equal(req.headers["x-if-unmodified-since"], lastModified); - equal(req.limit, batchSize); - if (i !== requests.length - 1) { - equal(req.offset, expectedOffset); - } - - expectedOffset += batchSize; - } - - // ensure we cleaned up anything that would break further - // use of this collection. - ok(!coll._headers["x-if-unmodified-since"]); - ok(!coll.offset); - ok(!coll.limit || (coll.limit == Infinity)); - - run_next_test(); -}); - -add_test(function test_total_limit() { - _("getBatched respects the (initial) value of the limit property"); - const totalRecords = 100; - const recordLimit = 11; - const batchSize = 2; - const lastModified = "111111"; - let { records, responses, requests, coll } = get_test_collection_info({ - totalRecords, - batchSize, - lastModified, - }); - coll.limit = recordLimit; - let response = coll.getBatched(batchSize); - - equal(requests.length, Math.ceil(recordLimit / batchSize)); - equal(records.length, recordLimit); - - for (let i = 0; i < requests.length; ++i) { - let req = requests[i]; - if (i !== requests.length - 1) { - equal(req.limit, batchSize); - } else { - equal(req.limit, recordLimit % batchSize); - } - } - - equal(coll._limit, recordLimit); - - run_next_test(); -}); - -add_test(function test_412() { - _("We shouldn't record records if we get a 412 in the middle of a batch"); - const totalRecords = 11; - const batchSize = 2; - const lastModified = "111111"; - let { records, responses, requests, coll } = get_test_collection_info({ - totalRecords, - batchSize, - lastModified, - interruptedAfter: 3 - }); - let response = coll.getBatched(batchSize); - - equal(requests.length, 3); - equal(records.length, 0); // record handler shouldn't be called for anything - - // ensure we're returning the last response - equal(responses[responses.length - 1], response); - - ok(!response.success); - equal(response.status, 412); - run_next_test(); -}); - -add_test(function test_get_throws() { - _("We shouldn't record records if get() throws for some reason"); - const totalRecords = 11; - const batchSize = 2; - const lastModified = "111111"; - let { records, responses, requests, coll } = get_test_collection_info({ - totalRecords, - batchSize, - lastModified, - throwAfter: 3 - }); - - throws(() => coll.getBatched(batchSize), "Some Network Error"); - - equal(requests.length, 3); - equal(records.length, 0); - run_next_test(); -}); diff --git a/services/sync/tests/unit/test_collections_recovery.js b/services/sync/tests/unit/test_collections_recovery.js index 0e7f54676..377a05383 100644 --- a/services/sync/tests/unit/test_collections_recovery.js +++ b/services/sync/tests/unit/test_collections_recovery.js @@ -6,7 +6,7 @@ Cu.import("resource://services-sync/service.js"); Cu.import("resource://services-sync/util.js"); Cu.import("resource://testing-common/services/sync/utils.js"); -add_identity_test(this, function* test_missing_crypto_collection() { +add_identity_test(this, function test_missing_crypto_collection() { let johnHelper = track_collections_helper(); let johnU = johnHelper.with_updated_collection; let johnColls = johnHelper.collections; @@ -33,10 +33,7 @@ add_identity_test(this, function* test_missing_crypto_collection() { }; let collections = ["clients", "bookmarks", "forms", "history", "passwords", "prefs", "tabs"]; - // Disable addon sync because AddonManager won't be initialized here. - Service.engineManager.unregister("addons"); - - for (let coll of collections) { + for each (let coll in collections) { handlers["/1.1/johndoe/storage/" + coll] = johnU(coll, new ServerCollection({}, true).handler()); } @@ -53,7 +50,7 @@ add_identity_test(this, function* test_missing_crypto_collection() { }; _("Startup, no meta/global: freshStart called once."); - yield sync_and_validate_telem(); + Service.sync(); do_check_eq(fresh, 1); fresh = 0; @@ -63,12 +60,12 @@ add_identity_test(this, function* test_missing_crypto_collection() { _("Simulate a bad info/collections."); delete johnColls.crypto; - yield sync_and_validate_telem(); + Service.sync(); do_check_eq(fresh, 1); fresh = 0; _("Regular sync: no need to freshStart."); - yield sync_and_validate_telem(); + Service.sync(); do_check_eq(fresh, 0); } finally { diff --git a/services/sync/tests/unit/test_corrupt_keys.js b/services/sync/tests/unit/test_corrupt_keys.js index 009461c2a..2db080a8f 100644 --- a/services/sync/tests/unit/test_corrupt_keys.js +++ b/services/sync/tests/unit/test_corrupt_keys.js @@ -14,7 +14,7 @@ Cu.import("resource://services-sync/util.js"); Cu.import("resource://testing-common/services/sync/utils.js"); Cu.import("resource://gre/modules/Promise.jsm"); -add_task(function* test_locally_changed_keys() { +add_task(function test_locally_changed_keys() { let passphrase = "abcdeabcdeabcdeabcdeabcdea"; let hmacErrorCount = 0; @@ -51,7 +51,7 @@ add_task(function* test_locally_changed_keys() { }]}]}; delete Svc.Session; Svc.Session = { - getBrowserState: () => JSON.stringify(myTabs) + getBrowserState: function () JSON.stringify(myTabs) }; setBasicCredentials("johndoe", "password", passphrase); @@ -59,7 +59,6 @@ add_task(function* test_locally_changed_keys() { Service.clusterURL = server.baseURI; Service.engineManager.register(HistoryEngine); - Service.engineManager.unregister("addons"); function corrupt_local_keys() { Service.collectionKeys._default.keyPair = [Svc.Crypto.generateRandomKey(), @@ -87,7 +86,7 @@ add_task(function* test_locally_changed_keys() { do_check_true(Service.isLoggedIn); // Sync should upload records. - yield sync_and_validate_telem(); + Service.sync(); // Tabs exist. _("Tabs modified: " + johndoe.modified("tabs")); @@ -140,9 +139,7 @@ add_task(function* test_locally_changed_keys() { _("HMAC error count: " + hmacErrorCount); // Now syncing should succeed, after one HMAC error. - let ping = yield wait_for_ping(() => Service.sync(), true); - equal(ping.engines.find(e => e.name == "history").incoming.applied, 5); - + Service.sync(); do_check_eq(hmacErrorCount, 1); _("Keys now: " + Service.collectionKeys.keyForCollection("history").keyPair); @@ -186,9 +183,7 @@ add_task(function* test_locally_changed_keys() { Service.lastHMACEvent = 0; _("Syncing..."); - ping = yield sync_and_validate_telem(true); - - do_check_eq(ping.engines.find(e => e.name == "history").incoming.failed, 5); + Service.sync(); _("Keys now: " + Service.collectionKeys.keyForCollection("history").keyPair); _("Server keys have been updated, and we skipped over 5 more HMAC errors without adjusting history."); do_check_true(johndoe.modified("crypto") > old_key_time); @@ -209,7 +204,6 @@ add_task(function* test_locally_changed_keys() { function run_test() { let logger = Log.repository.rootLogger; Log.repository.rootLogger.addAppender(new Log.DumpAppender()); - validate_all_future_pings(); ensureLegacyIdentityManager(); diff --git a/services/sync/tests/unit/test_engine.js b/services/sync/tests/unit/test_engine.js index be637efc8..000cd5b4a 100644 --- a/services/sync/tests/unit/test_engine.js +++ b/services/sync/tests/unit/test_engine.js @@ -25,8 +25,8 @@ SteamTracker.prototype = { __proto__: Tracker.prototype }; -function SteamEngine(name, service) { - Engine.call(this, name, service); +function SteamEngine(service) { + Engine.call(this, "Steam", service); this.wasReset = false; this.wasSynced = false; } @@ -44,7 +44,7 @@ SteamEngine.prototype = { } }; -var engineObserver = { +let engineObserver = { topics: [], observe: function(subject, topic, data) { @@ -69,7 +69,7 @@ function run_test() { add_test(function test_members() { _("Engine object members"); - let engine = new SteamEngine("Steam", Service); + let engine = new SteamEngine(Service); do_check_eq(engine.Name, "Steam"); do_check_eq(engine.prefName, "steam"); do_check_true(engine._store instanceof SteamStore); @@ -79,7 +79,7 @@ add_test(function test_members() { add_test(function test_score() { _("Engine.score corresponds to tracker.score and is readonly"); - let engine = new SteamEngine("Steam", Service); + let engine = new SteamEngine(Service); do_check_eq(engine.score, 0); engine._tracker.score += 5; do_check_eq(engine.score, 5); @@ -97,7 +97,7 @@ add_test(function test_score() { add_test(function test_resetClient() { _("Engine.resetClient calls _resetClient"); - let engine = new SteamEngine("Steam", Service); + let engine = new SteamEngine(Service); do_check_false(engine.wasReset); engine.resetClient(); @@ -112,7 +112,7 @@ add_test(function test_resetClient() { add_test(function test_invalidChangedIDs() { _("Test that invalid changed IDs on disk don't end up live."); - let engine = new SteamEngine("Steam", Service); + let engine = new SteamEngine(Service); let tracker = engine._tracker; tracker.changedIDs = 5; tracker.saveChangedIDs(function onSaved() { @@ -127,7 +127,7 @@ add_test(function test_invalidChangedIDs() { add_test(function test_wipeClient() { _("Engine.wipeClient calls resetClient, wipes store, clears changed IDs"); - let engine = new SteamEngine("Steam", Service); + let engine = new SteamEngine(Service); do_check_false(engine.wasReset); do_check_false(engine._store.wasWiped); do_check_true(engine._tracker.addChangedID("a-changed-id")); @@ -150,7 +150,7 @@ add_test(function test_wipeClient() { add_test(function test_enabled() { _("Engine.enabled corresponds to preference"); - let engine = new SteamEngine("Steam", Service); + let engine = new SteamEngine(Service); try { do_check_false(engine.enabled); Svc.Prefs.set("engine.steam", true); @@ -165,18 +165,16 @@ add_test(function test_enabled() { }); add_test(function test_sync() { - let engine = new SteamEngine("Steam", Service); + let engine = new SteamEngine(Service); try { _("Engine.sync doesn't call _sync if it's not enabled"); do_check_false(engine.enabled); do_check_false(engine.wasSynced); engine.sync(); - do_check_false(engine.wasSynced); _("Engine.sync calls _sync if it's enabled"); engine.enabled = true; - engine.sync(); do_check_true(engine.wasSynced); do_check_eq(engineObserver.topics[0], "weave:engine:sync:start"); @@ -191,7 +189,7 @@ add_test(function test_sync() { add_test(function test_disabled_no_track() { _("When an engine is disabled, its tracker is not tracking."); - let engine = new SteamEngine("Steam", Service); + let engine = new SteamEngine(Service); let tracker = engine._tracker; do_check_eq(engine, tracker.engine); diff --git a/services/sync/tests/unit/test_errorhandler.js b/services/sync/tests/unit/test_errorhandler.js new file mode 100644 index 000000000..c087acc9f --- /dev/null +++ b/services/sync/tests/unit/test_errorhandler.js @@ -0,0 +1,1893 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +Cu.import("resource://services-sync/engines/clients.js"); +Cu.import("resource://services-sync/constants.js"); +Cu.import("resource://services-sync/engines.js"); +Cu.import("resource://services-sync/keys.js"); +Cu.import("resource://services-sync/policies.js"); +Cu.import("resource://services-sync/service.js"); +Cu.import("resource://services-sync/status.js"); +Cu.import("resource://services-sync/util.js"); +Cu.import("resource://testing-common/services/sync/utils.js"); +Cu.import("resource://gre/modules/FileUtils.jsm"); + +const FAKE_SERVER_URL = "http://dummy:9000/"; +const logsdir = FileUtils.getDir("ProfD", ["weave", "logs"], true); + +const PROLONGED_ERROR_DURATION = + (Svc.Prefs.get('errorhandler.networkFailureReportTimeout') * 2) * 1000; + +const NON_PROLONGED_ERROR_DURATION = + (Svc.Prefs.get('errorhandler.networkFailureReportTimeout') / 2) * 1000; + +Service.engineManager.clear(); + +function setLastSync(lastSyncValue) { + Svc.Prefs.set("lastSync", (new Date(Date.now() - lastSyncValue)).toString()); +} + +function CatapultEngine() { + SyncEngine.call(this, "Catapult", Service); +} +CatapultEngine.prototype = { + __proto__: SyncEngine.prototype, + exception: null, // tests fill this in + _sync: function _sync() { + if (this.exception) { + throw this.exception; + } + } +}; + +let engineManager = Service.engineManager; +engineManager.register(CatapultEngine); + +// This relies on Service/ErrorHandler being a singleton. Fixing this will take +// a lot of work. +let errorHandler = Service.errorHandler; + +function run_test() { + initTestLogging("Trace"); + + Log.repository.getLogger("Sync.Service").level = Log.Level.Trace; + Log.repository.getLogger("Sync.SyncScheduler").level = Log.Level.Trace; + Log.repository.getLogger("Sync.ErrorHandler").level = Log.Level.Trace; + + ensureLegacyIdentityManager(); + + run_next_test(); +} + +function generateCredentialsChangedFailure() { + // Make sync fail due to changed credentials. We simply re-encrypt + // the keys with a different Sync Key, without changing the local one. + let newSyncKeyBundle = new SyncKeyBundle("johndoe", "23456234562345623456234562"); + let keys = Service.collectionKeys.asWBO(); + keys.encrypt(newSyncKeyBundle); + keys.upload(Service.resource(Service.cryptoKeysURL)); +} + +function service_unavailable(request, response) { + let body = "Service Unavailable"; + response.setStatusLine(request.httpVersion, 503, "Service Unavailable"); + response.setHeader("Retry-After", "42"); + response.bodyOutputStream.write(body, body.length); +} + +function sync_httpd_setup() { + let global = new ServerWBO("global", { + syncID: Service.syncID, + storageVersion: STORAGE_VERSION, + engines: {clients: {version: Service.clientsEngine.version, + syncID: Service.clientsEngine.syncID}, + catapult: {version: engineManager.get("catapult").version, + syncID: engineManager.get("catapult").syncID}} + }); + let clientsColl = new ServerCollection({}, true); + + // Tracking info/collections. + let collectionsHelper = track_collections_helper(); + let upd = collectionsHelper.with_updated_collection; + + let handler_401 = httpd_handler(401, "Unauthorized"); + return httpd_setup({ + // Normal server behaviour. + "/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()), + + // Credentials are wrong or node reallocated. + "/1.1/janedoe/storage/meta/global": handler_401, + "/1.1/janedoe/info/collections": handler_401, + + // Maintenance or overloaded (503 + Retry-After) at info/collections. + "/maintenance/1.1/broken.info/info/collections": service_unavailable, + + // Maintenance or overloaded (503 + Retry-After) at meta/global. + "/maintenance/1.1/broken.meta/storage/meta/global": service_unavailable, + "/maintenance/1.1/broken.meta/info/collections": collectionsHelper.handler, + + // Maintenance or overloaded (503 + Retry-After) at crypto/keys. + "/maintenance/1.1/broken.keys/storage/meta/global": upd("meta", global.handler()), + "/maintenance/1.1/broken.keys/info/collections": collectionsHelper.handler, + "/maintenance/1.1/broken.keys/storage/crypto/keys": service_unavailable, + + // Maintenance or overloaded (503 + Retry-After) at wiping collection. + "/maintenance/1.1/broken.wipe/info/collections": collectionsHelper.handler, + "/maintenance/1.1/broken.wipe/storage/meta/global": upd("meta", global.handler()), + "/maintenance/1.1/broken.wipe/storage/crypto/keys": + upd("crypto", (new ServerWBO("keys")).handler()), + "/maintenance/1.1/broken.wipe/storage": service_unavailable, + "/maintenance/1.1/broken.wipe/storage/clients": upd("clients", clientsColl.handler()), + "/maintenance/1.1/broken.wipe/storage/catapult": service_unavailable + }); +} + +function setUp(server) { + return configureIdentity({username: "johndoe"}).then( + () => { + Service.serverURL = server.baseURI + "/"; + Service.clusterURL = server.baseURI + "/"; + } + ).then( + () => generateAndUploadKeys() + ); +} + +function generateAndUploadKeys() { + generateNewKeys(Service.collectionKeys); + let serverKeys = Service.collectionKeys.asWBO("crypto", "keys"); + serverKeys.encrypt(Service.identity.syncKeyBundle); + return serverKeys.upload(Service.resource(Service.cryptoKeysURL)).success; +} + +function clean() { + Service.startOver(); + Status.resetSync(); + Status.resetBackoff(); + errorHandler.didReportProlongedError = false; +} + +add_identity_test(this, function test_401_logout() { + let server = sync_httpd_setup(); + yield setUp(server); + + // By calling sync, we ensure we're logged in. + Service.sync(); + do_check_eq(Status.sync, SYNC_SUCCEEDED); + do_check_true(Service.isLoggedIn); + + let deferred = Promise.defer(); + Svc.Obs.add("weave:service:sync:error", onSyncError); + function onSyncError() { + _("Got weave:service:sync:error in first sync."); + Svc.Obs.remove("weave:service:sync:error", onSyncError); + + // Wait for the automatic next sync. + function onLoginError() { + _("Got weave:service:login:error in second sync."); + Svc.Obs.remove("weave:service:login:error", onLoginError); + + do_check_eq(Status.login, LOGIN_FAILED_LOGIN_REJECTED); + do_check_false(Service.isLoggedIn); + + // Clean up. + Utils.nextTick(function () { + Service.startOver(); + server.stop(deferred.resolve); + }); + } + Svc.Obs.add("weave:service:login:error", onLoginError); + } + + // Make sync fail due to login rejected. + yield configureIdentity({username: "janedoe"}); + Service._updateCachedURLs(); + + _("Starting first sync."); + Service.sync(); + _("First sync done."); + yield deferred.promise; +}); + +add_identity_test(this, function test_credentials_changed_logout() { + let server = sync_httpd_setup(); + yield setUp(server); + + // By calling sync, we ensure we're logged in. + Service.sync(); + do_check_eq(Status.sync, SYNC_SUCCEEDED); + do_check_true(Service.isLoggedIn); + + generateCredentialsChangedFailure(); + Service.sync(); + + do_check_eq(Status.sync, CREDENTIALS_CHANGED); + do_check_false(Service.isLoggedIn); + + // Clean up. + Service.startOver(); + let deferred = Promise.defer(); + server.stop(deferred.resolve); + yield deferred.promise; +}); + +add_identity_test(this, function test_no_lastSync_pref() { + // Test reported error. + Status.resetSync(); + errorHandler.dontIgnoreErrors = true; + Status.sync = CREDENTIALS_CHANGED; + do_check_true(errorHandler.shouldReportError()); + + // Test unreported error. + Status.resetSync(); + errorHandler.dontIgnoreErrors = true; + Status.login = LOGIN_FAILED_NETWORK_ERROR; + do_check_true(errorHandler.shouldReportError()); + +}); + +add_identity_test(this, function test_shouldReportError() { + Status.login = MASTER_PASSWORD_LOCKED; + do_check_false(errorHandler.shouldReportError()); + + // Give ourselves a clusterURL so that the temporary 401 no-error situation + // doesn't come into play. + Service.serverURL = FAKE_SERVER_URL; + Service.clusterURL = FAKE_SERVER_URL; + + // Test dontIgnoreErrors, non-network, non-prolonged, login error reported + Status.resetSync(); + setLastSync(NON_PROLONGED_ERROR_DURATION); + errorHandler.dontIgnoreErrors = true; + Status.login = LOGIN_FAILED_NO_PASSWORD; + do_check_true(errorHandler.shouldReportError()); + + // Test dontIgnoreErrors, non-network, non-prolonged, sync error reported + Status.resetSync(); + setLastSync(NON_PROLONGED_ERROR_DURATION); + errorHandler.dontIgnoreErrors = true; + Status.sync = CREDENTIALS_CHANGED; + do_check_true(errorHandler.shouldReportError()); + + // Test dontIgnoreErrors, non-network, prolonged, login error reported + Status.resetSync(); + setLastSync(PROLONGED_ERROR_DURATION); + errorHandler.dontIgnoreErrors = true; + Status.login = LOGIN_FAILED_NO_PASSWORD; + do_check_true(errorHandler.shouldReportError()); + + // Test dontIgnoreErrors, non-network, prolonged, sync error reported + Status.resetSync(); + setLastSync(PROLONGED_ERROR_DURATION); + errorHandler.dontIgnoreErrors = true; + Status.sync = CREDENTIALS_CHANGED; + do_check_true(errorHandler.shouldReportError()); + + // Test dontIgnoreErrors, network, non-prolonged, login error reported + Status.resetSync(); + setLastSync(NON_PROLONGED_ERROR_DURATION); + errorHandler.dontIgnoreErrors = true; + Status.login = LOGIN_FAILED_NETWORK_ERROR; + do_check_true(errorHandler.shouldReportError()); + + // Test dontIgnoreErrors, network, non-prolonged, sync error reported + Status.resetSync(); + setLastSync(NON_PROLONGED_ERROR_DURATION); + errorHandler.dontIgnoreErrors = true; + Status.sync = LOGIN_FAILED_NETWORK_ERROR; + do_check_true(errorHandler.shouldReportError()); + + // Test dontIgnoreErrors, network, prolonged, login error reported + Status.resetSync(); + setLastSync(PROLONGED_ERROR_DURATION); + errorHandler.dontIgnoreErrors = true; + Status.login = LOGIN_FAILED_NETWORK_ERROR; + do_check_true(errorHandler.shouldReportError()); + + // Test dontIgnoreErrors, network, prolonged, sync error reported + Status.resetSync(); + setLastSync(PROLONGED_ERROR_DURATION); + errorHandler.dontIgnoreErrors = true; + Status.sync = LOGIN_FAILED_NETWORK_ERROR; + do_check_true(errorHandler.shouldReportError()); + + // Test non-network, prolonged, login error reported + do_check_false(errorHandler.didReportProlongedError); + Status.resetSync(); + setLastSync(PROLONGED_ERROR_DURATION); + errorHandler.dontIgnoreErrors = false; + Status.login = LOGIN_FAILED_NO_PASSWORD; + do_check_true(errorHandler.shouldReportError()); + do_check_true(errorHandler.didReportProlongedError); + + // Second time with prolonged error and without resetting + // didReportProlongedError, sync error should not be reported. + Status.resetSync(); + setLastSync(PROLONGED_ERROR_DURATION); + errorHandler.dontIgnoreErrors = false; + Status.login = LOGIN_FAILED_NO_PASSWORD; + do_check_false(errorHandler.shouldReportError()); + do_check_true(errorHandler.didReportProlongedError); + + // Test non-network, prolonged, sync error reported + Status.resetSync(); + setLastSync(PROLONGED_ERROR_DURATION); + errorHandler.dontIgnoreErrors = false; + errorHandler.didReportProlongedError = false; + Status.sync = CREDENTIALS_CHANGED; + do_check_true(errorHandler.shouldReportError()); + do_check_true(errorHandler.didReportProlongedError); + errorHandler.didReportProlongedError = false; + + // Test network, prolonged, login error reported + Status.resetSync(); + setLastSync(PROLONGED_ERROR_DURATION); + errorHandler.dontIgnoreErrors = false; + Status.login = LOGIN_FAILED_NETWORK_ERROR; + do_check_true(errorHandler.shouldReportError()); + do_check_true(errorHandler.didReportProlongedError); + errorHandler.didReportProlongedError = false; + + // Test network, prolonged, sync error reported + Status.resetSync(); + setLastSync(PROLONGED_ERROR_DURATION); + errorHandler.dontIgnoreErrors = false; + Status.sync = LOGIN_FAILED_NETWORK_ERROR; + do_check_true(errorHandler.shouldReportError()); + do_check_true(errorHandler.didReportProlongedError); + errorHandler.didReportProlongedError = false; + + // Test non-network, non-prolonged, login error reported + Status.resetSync(); + setLastSync(NON_PROLONGED_ERROR_DURATION); + errorHandler.dontIgnoreErrors = false; + Status.login = LOGIN_FAILED_NO_PASSWORD; + do_check_true(errorHandler.shouldReportError()); + do_check_false(errorHandler.didReportProlongedError); + + // Test non-network, non-prolonged, sync error reported + Status.resetSync(); + setLastSync(NON_PROLONGED_ERROR_DURATION); + errorHandler.dontIgnoreErrors = false; + Status.sync = CREDENTIALS_CHANGED; + do_check_true(errorHandler.shouldReportError()); + do_check_false(errorHandler.didReportProlongedError); + + // Test network, non-prolonged, login error reported + Status.resetSync(); + setLastSync(NON_PROLONGED_ERROR_DURATION); + errorHandler.dontIgnoreErrors = false; + Status.login = LOGIN_FAILED_NETWORK_ERROR; + do_check_false(errorHandler.shouldReportError()); + do_check_false(errorHandler.didReportProlongedError); + + // Test network, non-prolonged, sync error reported + Status.resetSync(); + setLastSync(NON_PROLONGED_ERROR_DURATION); + errorHandler.dontIgnoreErrors = false; + Status.sync = LOGIN_FAILED_NETWORK_ERROR; + do_check_false(errorHandler.shouldReportError()); + do_check_false(errorHandler.didReportProlongedError); + + // Test server maintenance, sync errors are not reported + Status.resetSync(); + setLastSync(NON_PROLONGED_ERROR_DURATION); + errorHandler.dontIgnoreErrors = false; + Status.sync = SERVER_MAINTENANCE; + do_check_false(errorHandler.shouldReportError()); + do_check_false(errorHandler.didReportProlongedError); + + // Test server maintenance, login errors are not reported + Status.resetSync(); + setLastSync(NON_PROLONGED_ERROR_DURATION); + errorHandler.dontIgnoreErrors = false; + Status.login = SERVER_MAINTENANCE; + do_check_false(errorHandler.shouldReportError()); + do_check_false(errorHandler.didReportProlongedError); + + // Test prolonged, server maintenance, sync errors are reported + Status.resetSync(); + setLastSync(PROLONGED_ERROR_DURATION); + errorHandler.dontIgnoreErrors = false; + Status.sync = SERVER_MAINTENANCE; + do_check_true(errorHandler.shouldReportError()); + do_check_true(errorHandler.didReportProlongedError); + errorHandler.didReportProlongedError = false; + + // Test prolonged, server maintenance, login errors are reported + Status.resetSync(); + setLastSync(PROLONGED_ERROR_DURATION); + errorHandler.dontIgnoreErrors = false; + Status.login = SERVER_MAINTENANCE; + do_check_true(errorHandler.shouldReportError()); + do_check_true(errorHandler.didReportProlongedError); + errorHandler.didReportProlongedError = false; + + // Test dontIgnoreErrors, server maintenance, sync errors are reported + Status.resetSync(); + setLastSync(NON_PROLONGED_ERROR_DURATION); + errorHandler.dontIgnoreErrors = true; + Status.sync = SERVER_MAINTENANCE; + do_check_true(errorHandler.shouldReportError()); + // dontIgnoreErrors means we don't set didReportProlongedError + do_check_false(errorHandler.didReportProlongedError); + + // Test dontIgnoreErrors, server maintenance, login errors are reported + Status.resetSync(); + setLastSync(NON_PROLONGED_ERROR_DURATION); + errorHandler.dontIgnoreErrors = true; + Status.login = SERVER_MAINTENANCE; + do_check_true(errorHandler.shouldReportError()); + do_check_false(errorHandler.didReportProlongedError); + + // Test dontIgnoreErrors, prolonged, server maintenance, + // sync errors are reported + Status.resetSync(); + setLastSync(PROLONGED_ERROR_DURATION); + errorHandler.dontIgnoreErrors = true; + Status.sync = SERVER_MAINTENANCE; + do_check_true(errorHandler.shouldReportError()); + do_check_false(errorHandler.didReportProlongedError); + + // Test dontIgnoreErrors, prolonged, server maintenance, + // login errors are reported + Status.resetSync(); + setLastSync(PROLONGED_ERROR_DURATION); + errorHandler.dontIgnoreErrors = true; + Status.login = SERVER_MAINTENANCE; + do_check_true(errorHandler.shouldReportError()); + do_check_false(errorHandler.didReportProlongedError); +}); + +add_identity_test(this, function test_shouldReportError_master_password() { + _("Test error ignored due to locked master password"); + let server = sync_httpd_setup(); + yield setUp(server); + + // Monkey patch Service.verifyLogin to imitate + // master password being locked. + Service._verifyLogin = Service.verifyLogin; + Service.verifyLogin = function () { + Status.login = MASTER_PASSWORD_LOCKED; + return false; + }; + + setLastSync(NON_PROLONGED_ERROR_DURATION); + Service.sync(); + do_check_false(errorHandler.shouldReportError()); + + // Clean up. + Service.verifyLogin = Service._verifyLogin; + clean(); + let deferred = Promise.defer(); + server.stop(deferred.resolve); + yield deferred.promise; +}); + +// Test that even if we don't have a cluster URL, a login failure due to +// authentication errors is always reported. +add_identity_test(this, function test_shouldReportLoginFailureWithNoCluster() { + // Ensure no clusterURL - any error not specific to login should not be reported. + Service.serverURL = ""; + Service.clusterURL = ""; + + // Test explicit "login rejected" state. + Status.resetSync(); + // If we have a LOGIN_REJECTED state, we always report the error. + Status.login = LOGIN_FAILED_LOGIN_REJECTED; + do_check_true(errorHandler.shouldReportError()); + // But any other status with a missing clusterURL is treated as a mid-sync + // 401 (ie, should be treated as a node reassignment) + Status.login = LOGIN_SUCCEEDED; + do_check_false(errorHandler.shouldReportError()); +}); + +// XXX - how to arrange for 'Service.identity.basicPassword = null;' in +// an fxaccounts environment? +add_task(function test_login_syncAndReportErrors_non_network_error() { + // Test non-network errors are reported + // when calling syncAndReportErrors + let server = sync_httpd_setup(); + yield setUp(server); + Service.identity.basicPassword = null; + + let deferred = Promise.defer(); + Svc.Obs.add("weave:ui:login:error", function onSyncError() { + Svc.Obs.remove("weave:ui:login:error", onSyncError); + do_check_eq(Status.login, LOGIN_FAILED_NO_PASSWORD); + + clean(); + server.stop(deferred.resolve); + }); + + setLastSync(NON_PROLONGED_ERROR_DURATION); + errorHandler.syncAndReportErrors(); + yield deferred.promise; +}); + +add_identity_test(this, function test_sync_syncAndReportErrors_non_network_error() { + // Test non-network errors are reported + // when calling syncAndReportErrors + let server = sync_httpd_setup(); + yield setUp(server); + + // By calling sync, we ensure we're logged in. + Service.sync(); + do_check_eq(Status.sync, SYNC_SUCCEEDED); + do_check_true(Service.isLoggedIn); + + generateCredentialsChangedFailure(); + + let deferred = Promise.defer(); + Svc.Obs.add("weave:ui:sync:error", function onSyncError() { + Svc.Obs.remove("weave:ui:sync:error", onSyncError); + do_check_eq(Status.sync, CREDENTIALS_CHANGED); + + clean(); + server.stop(deferred.resolve); + }); + + setLastSync(NON_PROLONGED_ERROR_DURATION); + errorHandler.syncAndReportErrors(); + yield deferred.promise; +}); + +// XXX - how to arrange for 'Service.identity.basicPassword = null;' in +// an fxaccounts environment? +add_task(function test_login_syncAndReportErrors_prolonged_non_network_error() { + // Test prolonged, non-network errors are + // reported when calling syncAndReportErrors. + let server = sync_httpd_setup(); + yield setUp(server); + Service.identity.basicPassword = null; + + let deferred = Promise.defer(); + Svc.Obs.add("weave:ui:login:error", function onSyncError() { + Svc.Obs.remove("weave:ui:login:error", onSyncError); + do_check_eq(Status.login, LOGIN_FAILED_NO_PASSWORD); + + clean(); + server.stop(deferred.resolve); + }); + + setLastSync(PROLONGED_ERROR_DURATION); + errorHandler.syncAndReportErrors(); + yield deferred.promise; +}); + +add_identity_test(this, function test_sync_syncAndReportErrors_prolonged_non_network_error() { + // Test prolonged, non-network errors are + // reported when calling syncAndReportErrors. + let server = sync_httpd_setup(); + yield setUp(server); + + // By calling sync, we ensure we're logged in. + Service.sync(); + do_check_eq(Status.sync, SYNC_SUCCEEDED); + do_check_true(Service.isLoggedIn); + + generateCredentialsChangedFailure(); + + let deferred = Promise.defer(); + Svc.Obs.add("weave:ui:sync:error", function onSyncError() { + Svc.Obs.remove("weave:ui:sync:error", onSyncError); + do_check_eq(Status.sync, CREDENTIALS_CHANGED); + + clean(); + server.stop(deferred.resolve); + }); + + setLastSync(PROLONGED_ERROR_DURATION); + errorHandler.syncAndReportErrors(); + yield deferred.promise; +}); + +add_identity_test(this, function test_login_syncAndReportErrors_network_error() { + // Test network errors are reported when calling syncAndReportErrors. + yield configureIdentity({username: "broken.wipe"}); + Service.serverURL = FAKE_SERVER_URL; + Service.clusterURL = FAKE_SERVER_URL; + + let deferred = Promise.defer(); + Svc.Obs.add("weave:ui:login:error", function onSyncError() { + Svc.Obs.remove("weave:ui:login:error", onSyncError); + do_check_eq(Status.login, LOGIN_FAILED_NETWORK_ERROR); + + clean(); + deferred.resolve(); + }); + + setLastSync(NON_PROLONGED_ERROR_DURATION); + errorHandler.syncAndReportErrors(); + yield deferred.promise; +}); + + +add_test(function test_sync_syncAndReportErrors_network_error() { + // Test network errors are reported when calling syncAndReportErrors. + Services.io.offline = true; + + Svc.Obs.add("weave:ui:sync:error", function onSyncError() { + Svc.Obs.remove("weave:ui:sync:error", onSyncError); + do_check_eq(Status.sync, LOGIN_FAILED_NETWORK_ERROR); + + Services.io.offline = false; + clean(); + run_next_test(); + }); + + setLastSync(NON_PROLONGED_ERROR_DURATION); + errorHandler.syncAndReportErrors(); +}); + +add_identity_test(this, function test_login_syncAndReportErrors_prolonged_network_error() { + // Test prolonged, network errors are reported + // when calling syncAndReportErrors. + yield configureIdentity({username: "johndoe"}); + + Service.serverURL = FAKE_SERVER_URL; + Service.clusterURL = FAKE_SERVER_URL; + + let deferred = Promise.defer(); + Svc.Obs.add("weave:ui:login:error", function onSyncError() { + Svc.Obs.remove("weave:ui:login:error", onSyncError); + do_check_eq(Status.login, LOGIN_FAILED_NETWORK_ERROR); + + clean(); + deferred.resolve(); + }); + + setLastSync(PROLONGED_ERROR_DURATION); + errorHandler.syncAndReportErrors(); + yield deferred.promise; +}); + +add_test(function test_sync_syncAndReportErrors_prolonged_network_error() { + // Test prolonged, network errors are reported + // when calling syncAndReportErrors. + Services.io.offline = true; + + Svc.Obs.add("weave:ui:sync:error", function onSyncError() { + Svc.Obs.remove("weave:ui:sync:error", onSyncError); + do_check_eq(Status.sync, LOGIN_FAILED_NETWORK_ERROR); + + Services.io.offline = false; + clean(); + run_next_test(); + }); + + setLastSync(PROLONGED_ERROR_DURATION); + errorHandler.syncAndReportErrors(); +}); + +add_task(function test_login_prolonged_non_network_error() { + // Test prolonged, non-network errors are reported + let server = sync_httpd_setup(); + yield setUp(server); + Service.identity.basicPassword = null; + + let deferred = Promise.defer(); + Svc.Obs.add("weave:ui:login:error", function onSyncError() { + Svc.Obs.remove("weave:ui:login:error", onSyncError); + do_check_eq(Status.sync, PROLONGED_SYNC_FAILURE); + do_check_true(errorHandler.didReportProlongedError); + + clean(); + server.stop(deferred.resolve); + }); + + setLastSync(PROLONGED_ERROR_DURATION); + Service.sync(); + yield deferred.promise; +}); + +add_task(function test_sync_prolonged_non_network_error() { + // Test prolonged, non-network errors are reported + let server = sync_httpd_setup(); + yield setUp(server); + + // By calling sync, we ensure we're logged in. + Service.sync(); + do_check_eq(Status.sync, SYNC_SUCCEEDED); + do_check_true(Service.isLoggedIn); + + generateCredentialsChangedFailure(); + + let deferred = Promise.defer(); + Svc.Obs.add("weave:ui:sync:error", function onSyncError() { + Svc.Obs.remove("weave:ui:sync:error", onSyncError); + do_check_eq(Status.sync, PROLONGED_SYNC_FAILURE); + do_check_true(errorHandler.didReportProlongedError); + + clean(); + server.stop(deferred.resolve); + }); + + setLastSync(PROLONGED_ERROR_DURATION); + Service.sync(); + yield deferred.promise; +}); + +add_identity_test(this, function test_login_prolonged_network_error() { + // Test prolonged, network errors are reported + yield configureIdentity({username: "johndoe"}); + Service.serverURL = FAKE_SERVER_URL; + Service.clusterURL = FAKE_SERVER_URL; + + let deferred = Promise.defer(); + Svc.Obs.add("weave:ui:login:error", function onSyncError() { + Svc.Obs.remove("weave:ui:login:error", onSyncError); + do_check_eq(Status.sync, PROLONGED_SYNC_FAILURE); + do_check_true(errorHandler.didReportProlongedError); + + clean(); + deferred.resolve(); + }); + + setLastSync(PROLONGED_ERROR_DURATION); + Service.sync(); + yield deferred.promise; +}); + +add_test(function test_sync_prolonged_network_error() { + // Test prolonged, network errors are reported + Services.io.offline = true; + + Svc.Obs.add("weave:ui:sync:error", function onSyncError() { + Svc.Obs.remove("weave:ui:sync:error", onSyncError); + do_check_eq(Status.sync, PROLONGED_SYNC_FAILURE); + do_check_true(errorHandler.didReportProlongedError); + + Services.io.offline = false; + clean(); + run_next_test(); + }); + + setLastSync(PROLONGED_ERROR_DURATION); + Service.sync(); +}); + +add_task(function test_login_non_network_error() { + // Test non-network errors are reported + let server = sync_httpd_setup(); + yield setUp(server); + Service.identity.basicPassword = null; + + let deferred = Promise.defer(); + Svc.Obs.add("weave:ui:login:error", function onSyncError() { + Svc.Obs.remove("weave:ui:login:error", onSyncError); + do_check_eq(Status.login, LOGIN_FAILED_NO_PASSWORD); + do_check_false(errorHandler.didReportProlongedError); + + clean(); + server.stop(deferred.resolve); + }); + + setLastSync(NON_PROLONGED_ERROR_DURATION); + Service.sync(); + yield deferred.promise; +}); + +add_task(function test_sync_non_network_error() { + // Test non-network errors are reported + let server = sync_httpd_setup(); + yield setUp(server); + + // By calling sync, we ensure we're logged in. + Service.sync(); + do_check_eq(Status.sync, SYNC_SUCCEEDED); + do_check_true(Service.isLoggedIn); + + generateCredentialsChangedFailure(); + + let deferred = Promise.defer(); + Svc.Obs.add("weave:ui:sync:error", function onSyncError() { + Svc.Obs.remove("weave:ui:sync:error", onSyncError); + do_check_eq(Status.sync, CREDENTIALS_CHANGED); + do_check_false(errorHandler.didReportProlongedError); + + clean(); + server.stop(deferred.resolve); + }); + + setLastSync(NON_PROLONGED_ERROR_DURATION); + Service.sync(); + yield deferred.promise; +}); + +add_identity_test(this, function test_login_network_error() { + yield configureIdentity({username: "johndoe"}); + Service.serverURL = FAKE_SERVER_URL; + Service.clusterURL = FAKE_SERVER_URL; + + let deferred = Promise.defer(); + // Test network errors are not reported. + Svc.Obs.add("weave:ui:clear-error", function onClearError() { + Svc.Obs.remove("weave:ui:clear-error", onClearError); + + do_check_eq(Status.login, LOGIN_FAILED_NETWORK_ERROR); + do_check_false(errorHandler.didReportProlongedError); + + Services.io.offline = false; + clean(); + deferred.resolve() + }); + + setLastSync(NON_PROLONGED_ERROR_DURATION); + Service.sync(); + yield deferred.promise; +}); + +add_test(function test_sync_network_error() { + // Test network errors are not reported. + Services.io.offline = true; + + Svc.Obs.add("weave:ui:sync:finish", function onUIUpdate() { + Svc.Obs.remove("weave:ui:sync:finish", onUIUpdate); + do_check_eq(Status.sync, LOGIN_FAILED_NETWORK_ERROR); + do_check_false(errorHandler.didReportProlongedError); + + Services.io.offline = false; + clean(); + run_next_test(); + }); + + setLastSync(NON_PROLONGED_ERROR_DURATION); + Service.sync(); +}); + +add_identity_test(this, function test_sync_server_maintenance_error() { + // Test server maintenance errors are not reported. + let server = sync_httpd_setup(); + yield setUp(server); + + const BACKOFF = 42; + let engine = engineManager.get("catapult"); + engine.enabled = true; + engine.exception = {status: 503, + headers: {"retry-after": BACKOFF}}; + + function onSyncError() { + do_throw("Shouldn't get here!"); + } + Svc.Obs.add("weave:ui:sync:error", onSyncError); + + do_check_eq(Status.service, STATUS_OK); + + let deferred = Promise.defer(); + Svc.Obs.add("weave:ui:sync:finish", function onSyncFinish() { + Svc.Obs.remove("weave:ui:sync:finish", onSyncFinish); + + do_check_eq(Status.service, SYNC_FAILED_PARTIAL); + do_check_eq(Status.sync, SERVER_MAINTENANCE); + do_check_false(errorHandler.didReportProlongedError); + + Svc.Obs.remove("weave:ui:sync:error", onSyncError); + clean(); + server.stop(deferred.resolve); + }); + + setLastSync(NON_PROLONGED_ERROR_DURATION); + Service.sync(); + yield deferred.promise; +}); + +add_identity_test(this, function test_info_collections_login_server_maintenance_error() { + // Test info/collections server maintenance errors are not reported. + let server = sync_httpd_setup(); + yield setUp(server); + + Service.username = "broken.info"; + yield configureIdentity({username: "broken.info"}); + Service.serverURL = server.baseURI + "/maintenance/"; + Service.clusterURL = server.baseURI + "/maintenance/"; + + let backoffInterval; + Svc.Obs.add("weave:service:backoff:interval", function observe(subject, data) { + Svc.Obs.remove("weave:service:backoff:interval", observe); + backoffInterval = subject; + }); + + function onUIUpdate() { + do_throw("Shouldn't experience UI update!"); + } + Svc.Obs.add("weave:ui:login:error", onUIUpdate); + + do_check_false(Status.enforceBackoff); + do_check_eq(Status.service, STATUS_OK); + + let deferred = Promise.defer(); + Svc.Obs.add("weave:ui:clear-error", function onLoginFinish() { + Svc.Obs.remove("weave:ui:clear-error", onLoginFinish); + + do_check_true(Status.enforceBackoff); + do_check_eq(backoffInterval, 42); + do_check_eq(Status.service, LOGIN_FAILED); + do_check_eq(Status.login, SERVER_MAINTENANCE); + do_check_false(errorHandler.didReportProlongedError); + + Svc.Obs.remove("weave:ui:login:error", onUIUpdate); + clean(); + server.stop(deferred.resolve); + }); + + setLastSync(NON_PROLONGED_ERROR_DURATION); + Service.sync(); + yield deferred.promise; +}); + +add_identity_test(this, function test_meta_global_login_server_maintenance_error() { + // Test meta/global server maintenance errors are not reported. + let server = sync_httpd_setup(); + yield setUp(server); + + yield configureIdentity({username: "broken.meta"}); + Service.serverURL = server.baseURI + "/maintenance/"; + Service.clusterURL = server.baseURI + "/maintenance/"; + + let backoffInterval; + Svc.Obs.add("weave:service:backoff:interval", function observe(subject, data) { + Svc.Obs.remove("weave:service:backoff:interval", observe); + backoffInterval = subject; + }); + + function onUIUpdate() { + do_throw("Shouldn't get here!"); + } + Svc.Obs.add("weave:ui:login:error", onUIUpdate); + + do_check_false(Status.enforceBackoff); + do_check_eq(Status.service, STATUS_OK); + + let deferred = Promise.defer(); + Svc.Obs.add("weave:ui:clear-error", function onLoginFinish() { + Svc.Obs.remove("weave:ui:clear-error", onLoginFinish); + + do_check_true(Status.enforceBackoff); + do_check_eq(backoffInterval, 42); + do_check_eq(Status.service, LOGIN_FAILED); + do_check_eq(Status.login, SERVER_MAINTENANCE); + do_check_false(errorHandler.didReportProlongedError); + + Svc.Obs.remove("weave:ui:login:error", onUIUpdate); + clean(); + server.stop(deferred.resolve); + }); + + setLastSync(NON_PROLONGED_ERROR_DURATION); + Service.sync(); + yield deferred.promise; +}); + +add_identity_test(this, function test_crypto_keys_login_server_maintenance_error() { + // Test crypto/keys server maintenance errors are not reported. + let server = sync_httpd_setup(); + yield setUp(server); + + yield configureIdentity({username: "broken.keys"}); + Service.serverURL = server.baseURI + "/maintenance/"; + Service.clusterURL = server.baseURI + "/maintenance/"; + + // Force re-download of keys + Service.collectionKeys.clear(); + + let backoffInterval; + Svc.Obs.add("weave:service:backoff:interval", function observe(subject, data) { + Svc.Obs.remove("weave:service:backoff:interval", observe); + backoffInterval = subject; + }); + + function onUIUpdate() { + do_throw("Shouldn't get here!"); + } + Svc.Obs.add("weave:ui:login:error", onUIUpdate); + + do_check_false(Status.enforceBackoff); + do_check_eq(Status.service, STATUS_OK); + + let deferred = Promise.defer(); + Svc.Obs.add("weave:ui:clear-error", function onLoginFinish() { + Svc.Obs.remove("weave:ui:clear-error", onLoginFinish); + + do_check_true(Status.enforceBackoff); + do_check_eq(backoffInterval, 42); + do_check_eq(Status.service, LOGIN_FAILED); + do_check_eq(Status.login, SERVER_MAINTENANCE); + do_check_false(errorHandler.didReportProlongedError); + + Svc.Obs.remove("weave:ui:login:error", onUIUpdate); + clean(); + server.stop(deferred.resolve); + }); + + setLastSync(NON_PROLONGED_ERROR_DURATION); + Service.sync(); + yield deferred.promise; +}); + +add_task(function test_sync_prolonged_server_maintenance_error() { + // Test prolonged server maintenance errors are reported. + let server = sync_httpd_setup(); + yield setUp(server); + + const BACKOFF = 42; + let engine = engineManager.get("catapult"); + engine.enabled = true; + engine.exception = {status: 503, + headers: {"retry-after": BACKOFF}}; + + let deferred = Promise.defer(); + Svc.Obs.add("weave:ui:sync:error", function onUIUpdate() { + Svc.Obs.remove("weave:ui:sync:error", onUIUpdate); + do_check_eq(Status.service, SYNC_FAILED); + do_check_eq(Status.sync, PROLONGED_SYNC_FAILURE); + do_check_true(errorHandler.didReportProlongedError); + + clean(); + server.stop(deferred.resolve); + }); + + do_check_eq(Status.service, STATUS_OK); + + setLastSync(PROLONGED_ERROR_DURATION); + Service.sync(); + yield deferred.promise; +}); + +add_identity_test(this, function test_info_collections_login_prolonged_server_maintenance_error(){ + // Test info/collections prolonged server maintenance errors are reported. + let server = sync_httpd_setup(); + yield setUp(server); + + yield configureIdentity({username: "broken.info"}); + Service.serverURL = server.baseURI + "/maintenance/"; + Service.clusterURL = server.baseURI + "/maintenance/"; + + let backoffInterval; + Svc.Obs.add("weave:service:backoff:interval", function observe(subject, data) { + Svc.Obs.remove("weave:service:backoff:interval", observe); + backoffInterval = subject; + }); + + let deferred = Promise.defer(); + Svc.Obs.add("weave:ui:login:error", function onUIUpdate() { + Svc.Obs.remove("weave:ui:login:error", onUIUpdate); + do_check_true(Status.enforceBackoff); + do_check_eq(backoffInterval, 42); + do_check_eq(Status.service, SYNC_FAILED); + do_check_eq(Status.sync, PROLONGED_SYNC_FAILURE); + do_check_true(errorHandler.didReportProlongedError); + + clean(); + server.stop(deferred.resolve); + }); + + do_check_false(Status.enforceBackoff); + do_check_eq(Status.service, STATUS_OK); + + setLastSync(PROLONGED_ERROR_DURATION); + Service.sync(); + yield deferred.promise; +}); + +add_identity_test(this, function test_meta_global_login_prolonged_server_maintenance_error(){ + // Test meta/global prolonged server maintenance errors are reported. + let server = sync_httpd_setup(); + yield setUp(server); + + yield configureIdentity({username: "broken.meta"}); + Service.serverURL = server.baseURI + "/maintenance/"; + Service.clusterURL = server.baseURI + "/maintenance/"; + + let backoffInterval; + Svc.Obs.add("weave:service:backoff:interval", function observe(subject, data) { + Svc.Obs.remove("weave:service:backoff:interval", observe); + backoffInterval = subject; + }); + + let deferred = Promise.defer(); + Svc.Obs.add("weave:ui:login:error", function onUIUpdate() { + Svc.Obs.remove("weave:ui:login:error", onUIUpdate); + do_check_true(Status.enforceBackoff); + do_check_eq(backoffInterval, 42); + do_check_eq(Status.service, SYNC_FAILED); + do_check_eq(Status.sync, PROLONGED_SYNC_FAILURE); + do_check_true(errorHandler.didReportProlongedError); + + clean(); + server.stop(deferred.resolve); + }); + + do_check_false(Status.enforceBackoff); + do_check_eq(Status.service, STATUS_OK); + + setLastSync(PROLONGED_ERROR_DURATION); + Service.sync(); + yield deferred.promise; +}); + +add_identity_test(this, function test_download_crypto_keys_login_prolonged_server_maintenance_error(){ + // Test crypto/keys prolonged server maintenance errors are reported. + let server = sync_httpd_setup(); + yield setUp(server); + + yield configureIdentity({username: "broken.keys"}); + Service.serverURL = server.baseURI + "/maintenance/"; + Service.clusterURL = server.baseURI + "/maintenance/"; + // Force re-download of keys + Service.collectionKeys.clear(); + + let backoffInterval; + Svc.Obs.add("weave:service:backoff:interval", function observe(subject, data) { + Svc.Obs.remove("weave:service:backoff:interval", observe); + backoffInterval = subject; + }); + + let deferred = Promise.defer(); + Svc.Obs.add("weave:ui:login:error", function onUIUpdate() { + Svc.Obs.remove("weave:ui:login:error", onUIUpdate); + do_check_true(Status.enforceBackoff); + do_check_eq(backoffInterval, 42); + do_check_eq(Status.service, SYNC_FAILED); + do_check_eq(Status.sync, PROLONGED_SYNC_FAILURE); + do_check_true(errorHandler.didReportProlongedError); + + clean(); + server.stop(deferred.resolve); + }); + + do_check_false(Status.enforceBackoff); + do_check_eq(Status.service, STATUS_OK); + + setLastSync(PROLONGED_ERROR_DURATION); + Service.sync(); + yield deferred.promise; +}); + +add_identity_test(this, function test_upload_crypto_keys_login_prolonged_server_maintenance_error(){ + // Test crypto/keys prolonged server maintenance errors are reported. + let server = sync_httpd_setup(); + + // Start off with an empty account, do not upload a key. + yield configureIdentity({username: "broken.keys"}); + Service.serverURL = server.baseURI + "/maintenance/"; + Service.clusterURL = server.baseURI + "/maintenance/"; + + let backoffInterval; + Svc.Obs.add("weave:service:backoff:interval", function observe(subject, data) { + Svc.Obs.remove("weave:service:backoff:interval", observe); + backoffInterval = subject; + }); + + let deferred = Promise.defer(); + Svc.Obs.add("weave:ui:login:error", function onUIUpdate() { + Svc.Obs.remove("weave:ui:login:error", onUIUpdate); + do_check_true(Status.enforceBackoff); + do_check_eq(backoffInterval, 42); + do_check_eq(Status.service, SYNC_FAILED); + do_check_eq(Status.sync, PROLONGED_SYNC_FAILURE); + do_check_true(errorHandler.didReportProlongedError); + + clean(); + server.stop(deferred.resolve); + }); + + do_check_false(Status.enforceBackoff); + do_check_eq(Status.service, STATUS_OK); + + setLastSync(PROLONGED_ERROR_DURATION); + Service.sync(); + yield deferred.promise; +}); + +add_identity_test(this, function test_wipeServer_login_prolonged_server_maintenance_error(){ + // Test that we report prolonged server maintenance errors that occur whilst + // wiping the server. + let server = sync_httpd_setup(); + + // Start off with an empty account, do not upload a key. + yield configureIdentity({username: "broken.wipe"}); + Service.serverURL = server.baseURI + "/maintenance/"; + Service.clusterURL = server.baseURI + "/maintenance/"; + + let backoffInterval; + Svc.Obs.add("weave:service:backoff:interval", function observe(subject, data) { + Svc.Obs.remove("weave:service:backoff:interval", observe); + backoffInterval = subject; + }); + + let deferred = Promise.defer(); + Svc.Obs.add("weave:ui:login:error", function onUIUpdate() { + Svc.Obs.remove("weave:ui:login:error", onUIUpdate); + do_check_true(Status.enforceBackoff); + do_check_eq(backoffInterval, 42); + do_check_eq(Status.service, SYNC_FAILED); + do_check_eq(Status.sync, PROLONGED_SYNC_FAILURE); + do_check_true(errorHandler.didReportProlongedError); + + clean(); + server.stop(deferred.resolve); + }); + + do_check_false(Status.enforceBackoff); + do_check_eq(Status.service, STATUS_OK); + + setLastSync(PROLONGED_ERROR_DURATION); + Service.sync(); + yield deferred.promise; +}); + +add_identity_test(this, function test_wipeRemote_prolonged_server_maintenance_error(){ + // Test that we report prolonged server maintenance errors that occur whilst + // wiping all remote devices. + let server = sync_httpd_setup(); + + server.registerPathHandler("/1.1/broken.wipe/storage/catapult", service_unavailable); + yield configureIdentity({username: "broken.wipe"}); + Service.serverURL = server.baseURI + "/maintenance/"; + Service.clusterURL = server.baseURI + "/maintenance/"; + generateAndUploadKeys(); + + let engine = engineManager.get("catapult"); + engine.exception = null; + engine.enabled = true; + + let backoffInterval; + Svc.Obs.add("weave:service:backoff:interval", function observe(subject, data) { + Svc.Obs.remove("weave:service:backoff:interval", observe); + backoffInterval = subject; + }); + + let deferred = Promise.defer(); + Svc.Obs.add("weave:ui:sync:error", function onUIUpdate() { + Svc.Obs.remove("weave:ui:sync:error", onUIUpdate); + do_check_true(Status.enforceBackoff); + do_check_eq(backoffInterval, 42); + do_check_eq(Status.service, SYNC_FAILED); + do_check_eq(Status.sync, PROLONGED_SYNC_FAILURE); + do_check_eq(Svc.Prefs.get("firstSync"), "wipeRemote"); + do_check_true(errorHandler.didReportProlongedError); + + clean(); + server.stop(deferred.resolve); + }); + + do_check_false(Status.enforceBackoff); + do_check_eq(Status.service, STATUS_OK); + + Svc.Prefs.set("firstSync", "wipeRemote"); + setLastSync(PROLONGED_ERROR_DURATION); + Service.sync(); + yield deferred.promise; +}); + +add_task(function test_sync_syncAndReportErrors_server_maintenance_error() { + // Test server maintenance errors are reported + // when calling syncAndReportErrors. + let server = sync_httpd_setup(); + yield setUp(server); + + const BACKOFF = 42; + let engine = engineManager.get("catapult"); + engine.enabled = true; + engine.exception = {status: 503, + headers: {"retry-after": BACKOFF}}; + + let deferred = Promise.defer(); + Svc.Obs.add("weave:ui:sync:error", function onUIUpdate() { + Svc.Obs.remove("weave:ui:sync:error", onUIUpdate); + do_check_eq(Status.service, SYNC_FAILED_PARTIAL); + do_check_eq(Status.sync, SERVER_MAINTENANCE); + do_check_false(errorHandler.didReportProlongedError); + + clean(); + server.stop(deferred.resolve); + }); + + do_check_eq(Status.service, STATUS_OK); + + setLastSync(NON_PROLONGED_ERROR_DURATION); + errorHandler.syncAndReportErrors(); + yield deferred.promise; +}); + +add_identity_test(this, function test_info_collections_login_syncAndReportErrors_server_maintenance_error() { + // Test info/collections server maintenance errors are reported + // when calling syncAndReportErrors. + let server = sync_httpd_setup(); + yield setUp(server); + + yield configureIdentity({username: "broken.info"}); + Service.serverURL = server.baseURI + "/maintenance/"; + Service.clusterURL = server.baseURI + "/maintenance/"; + + let backoffInterval; + Svc.Obs.add("weave:service:backoff:interval", function observe(subject, data) { + Svc.Obs.remove("weave:service:backoff:interval", observe); + backoffInterval = subject; + }); + + let deferred = Promise.defer(); + Svc.Obs.add("weave:ui:login:error", function onUIUpdate() { + Svc.Obs.remove("weave:ui:login:error", onUIUpdate); + do_check_true(Status.enforceBackoff); + do_check_eq(backoffInterval, 42); + do_check_eq(Status.service, LOGIN_FAILED); + do_check_eq(Status.login, SERVER_MAINTENANCE); + do_check_false(errorHandler.didReportProlongedError); + + clean(); + server.stop(deferred.resolve); + }); + + do_check_false(Status.enforceBackoff); + do_check_eq(Status.service, STATUS_OK); + + setLastSync(NON_PROLONGED_ERROR_DURATION); + errorHandler.syncAndReportErrors(); + yield deferred.promise; +}); + +add_identity_test(this, function test_meta_global_login_syncAndReportErrors_server_maintenance_error() { + // Test meta/global server maintenance errors are reported + // when calling syncAndReportErrors. + let server = sync_httpd_setup(); + yield setUp(server); + + yield configureIdentity({username: "broken.meta"}); + Service.serverURL = server.baseURI + "/maintenance/"; + Service.clusterURL = server.baseURI + "/maintenance/"; + + let backoffInterval; + Svc.Obs.add("weave:service:backoff:interval", function observe(subject, data) { + Svc.Obs.remove("weave:service:backoff:interval", observe); + backoffInterval = subject; + }); + + let deferred = Promise.defer(); + Svc.Obs.add("weave:ui:login:error", function onUIUpdate() { + Svc.Obs.remove("weave:ui:login:error", onUIUpdate); + do_check_true(Status.enforceBackoff); + do_check_eq(backoffInterval, 42); + do_check_eq(Status.service, LOGIN_FAILED); + do_check_eq(Status.login, SERVER_MAINTENANCE); + do_check_false(errorHandler.didReportProlongedError); + + clean(); + server.stop(deferred.resolve); + }); + + do_check_false(Status.enforceBackoff); + do_check_eq(Status.service, STATUS_OK); + + setLastSync(NON_PROLONGED_ERROR_DURATION); + errorHandler.syncAndReportErrors(); + yield deferred.promise; +}); + +add_identity_test(this, function test_download_crypto_keys_login_syncAndReportErrors_server_maintenance_error() { + // Test crypto/keys server maintenance errors are reported + // when calling syncAndReportErrors. + let server = sync_httpd_setup(); + yield setUp(server); + + yield configureIdentity({username: "broken.keys"}); + Service.serverURL = server.baseURI + "/maintenance/"; + Service.clusterURL = server.baseURI + "/maintenance/"; + // Force re-download of keys + Service.collectionKeys.clear(); + + let backoffInterval; + Svc.Obs.add("weave:service:backoff:interval", function observe(subject, data) { + Svc.Obs.remove("weave:service:backoff:interval", observe); + backoffInterval = subject; + }); + + let deferred = Promise.defer(); + Svc.Obs.add("weave:ui:login:error", function onUIUpdate() { + Svc.Obs.remove("weave:ui:login:error", onUIUpdate); + do_check_true(Status.enforceBackoff); + do_check_eq(backoffInterval, 42); + do_check_eq(Status.service, LOGIN_FAILED); + do_check_eq(Status.login, SERVER_MAINTENANCE); + do_check_false(errorHandler.didReportProlongedError); + + clean(); + server.stop(deferred.resolve); + }); + + do_check_false(Status.enforceBackoff); + do_check_eq(Status.service, STATUS_OK); + + setLastSync(NON_PROLONGED_ERROR_DURATION); + errorHandler.syncAndReportErrors(); + yield deferred.promise; +}); + +add_identity_test(this, function test_upload_crypto_keys_login_syncAndReportErrors_server_maintenance_error() { + // Test crypto/keys server maintenance errors are reported + // when calling syncAndReportErrors. + let server = sync_httpd_setup(); + + // Start off with an empty account, do not upload a key. + yield configureIdentity({username: "broken.keys"}); + Service.serverURL = server.baseURI + "/maintenance/"; + Service.clusterURL = server.baseURI + "/maintenance/"; + + let backoffInterval; + Svc.Obs.add("weave:service:backoff:interval", function observe(subject, data) { + Svc.Obs.remove("weave:service:backoff:interval", observe); + backoffInterval = subject; + }); + + let deferred = Promise.defer(); + Svc.Obs.add("weave:ui:login:error", function onUIUpdate() { + Svc.Obs.remove("weave:ui:login:error", onUIUpdate); + do_check_true(Status.enforceBackoff); + do_check_eq(backoffInterval, 42); + do_check_eq(Status.service, LOGIN_FAILED); + do_check_eq(Status.login, SERVER_MAINTENANCE); + do_check_false(errorHandler.didReportProlongedError); + + clean(); + server.stop(deferred.resolve); + }); + + do_check_false(Status.enforceBackoff); + do_check_eq(Status.service, STATUS_OK); + + setLastSync(NON_PROLONGED_ERROR_DURATION); + errorHandler.syncAndReportErrors(); + yield deferred.promise; +}); + +add_identity_test(this, function test_wipeServer_login_syncAndReportErrors_server_maintenance_error() { + // Test crypto/keys server maintenance errors are reported + // when calling syncAndReportErrors. + let server = sync_httpd_setup(); + + // Start off with an empty account, do not upload a key. + yield configureIdentity({username: "broken.wipe"}); + Service.serverURL = server.baseURI + "/maintenance/"; + Service.clusterURL = server.baseURI + "/maintenance/"; + + let backoffInterval; + Svc.Obs.add("weave:service:backoff:interval", function observe(subject, data) { + Svc.Obs.remove("weave:service:backoff:interval", observe); + backoffInterval = subject; + }); + + let deferred = Promise.defer(); + Svc.Obs.add("weave:ui:login:error", function onUIUpdate() { + Svc.Obs.remove("weave:ui:login:error", onUIUpdate); + do_check_true(Status.enforceBackoff); + do_check_eq(backoffInterval, 42); + do_check_eq(Status.service, LOGIN_FAILED); + do_check_eq(Status.login, SERVER_MAINTENANCE); + do_check_false(errorHandler.didReportProlongedError); + + clean(); + server.stop(deferred.resolve); + }); + + do_check_false(Status.enforceBackoff); + do_check_eq(Status.service, STATUS_OK); + + setLastSync(NON_PROLONGED_ERROR_DURATION); + errorHandler.syncAndReportErrors(); + yield deferred.promise; +}); + +add_identity_test(this, function test_wipeRemote_syncAndReportErrors_server_maintenance_error(){ + // Test that we report prolonged server maintenance errors that occur whilst + // wiping all remote devices. + let server = sync_httpd_setup(); + + yield configureIdentity({username: "broken.wipe"}); + Service.serverURL = server.baseURI + "/maintenance/"; + Service.clusterURL = server.baseURI + "/maintenance/"; + generateAndUploadKeys(); + + let engine = engineManager.get("catapult"); + engine.exception = null; + engine.enabled = true; + + let backoffInterval; + Svc.Obs.add("weave:service:backoff:interval", function observe(subject, data) { + Svc.Obs.remove("weave:service:backoff:interval", observe); + backoffInterval = subject; + }); + + let deferred = Promise.defer(); + Svc.Obs.add("weave:ui:sync:error", function onUIUpdate() { + Svc.Obs.remove("weave:ui:sync:error", onUIUpdate); + do_check_true(Status.enforceBackoff); + do_check_eq(backoffInterval, 42); + do_check_eq(Status.service, SYNC_FAILED); + do_check_eq(Status.sync, SERVER_MAINTENANCE); + do_check_eq(Svc.Prefs.get("firstSync"), "wipeRemote"); + do_check_false(errorHandler.didReportProlongedError); + + clean(); + server.stop(deferred.resolve); + }); + + do_check_false(Status.enforceBackoff); + do_check_eq(Status.service, STATUS_OK); + + Svc.Prefs.set("firstSync", "wipeRemote"); + setLastSync(NON_PROLONGED_ERROR_DURATION); + errorHandler.syncAndReportErrors(); + yield deferred.promise; +}); + +add_task(function test_sync_syncAndReportErrors_prolonged_server_maintenance_error() { + // Test prolonged server maintenance errors are + // reported when calling syncAndReportErrors. + let server = sync_httpd_setup(); + yield setUp(server); + + const BACKOFF = 42; + let engine = engineManager.get("catapult"); + engine.enabled = true; + engine.exception = {status: 503, + headers: {"retry-after": BACKOFF}}; + + let deferred = Promise.defer(); + Svc.Obs.add("weave:ui:sync:error", function onUIUpdate() { + Svc.Obs.remove("weave:ui:sync:error", onUIUpdate); + do_check_eq(Status.service, SYNC_FAILED_PARTIAL); + do_check_eq(Status.sync, SERVER_MAINTENANCE); + // syncAndReportErrors means dontIgnoreErrors, which means + // didReportProlongedError not touched. + do_check_false(errorHandler.didReportProlongedError); + + clean(); + server.stop(deferred.resolve); + }); + + do_check_eq(Status.service, STATUS_OK); + + setLastSync(PROLONGED_ERROR_DURATION); + errorHandler.syncAndReportErrors(); + yield deferred.promise; +}); + +add_identity_test(this, function test_info_collections_login_syncAndReportErrors_prolonged_server_maintenance_error() { + // Test info/collections server maintenance errors are reported + // when calling syncAndReportErrors. + let server = sync_httpd_setup(); + yield setUp(server); + + yield configureIdentity({username: "broken.info"}); + Service.serverURL = server.baseURI + "/maintenance/"; + Service.clusterURL = server.baseURI + "/maintenance/"; + + let backoffInterval; + Svc.Obs.add("weave:service:backoff:interval", function observe(subject, data) { + Svc.Obs.remove("weave:service:backoff:interval", observe); + backoffInterval = subject; + }); + + let deferred = Promise.defer(); + Svc.Obs.add("weave:ui:login:error", function onUIUpdate() { + Svc.Obs.remove("weave:ui:login:error", onUIUpdate); + do_check_true(Status.enforceBackoff); + do_check_eq(backoffInterval, 42); + do_check_eq(Status.service, LOGIN_FAILED); + do_check_eq(Status.login, SERVER_MAINTENANCE); + // syncAndReportErrors means dontIgnoreErrors, which means + // didReportProlongedError not touched. + do_check_false(errorHandler.didReportProlongedError); + + clean(); + server.stop(deferred.resolve); + }); + + do_check_false(Status.enforceBackoff); + do_check_eq(Status.service, STATUS_OK); + + setLastSync(PROLONGED_ERROR_DURATION); + errorHandler.syncAndReportErrors(); + yield deferred.promise; +}); + +add_identity_test(this, function test_meta_global_login_syncAndReportErrors_prolonged_server_maintenance_error() { + // Test meta/global server maintenance errors are reported + // when calling syncAndReportErrors. + let server = sync_httpd_setup(); + yield setUp(server); + + yield configureIdentity({username: "broken.meta"}); + Service.serverURL = server.baseURI + "/maintenance/"; + Service.clusterURL = server.baseURI + "/maintenance/"; + + let backoffInterval; + Svc.Obs.add("weave:service:backoff:interval", function observe(subject, data) { + Svc.Obs.remove("weave:service:backoff:interval", observe); + backoffInterval = subject; + }); + + let deferred = Promise.defer(); + Svc.Obs.add("weave:ui:login:error", function onUIUpdate() { + Svc.Obs.remove("weave:ui:login:error", onUIUpdate); + do_check_true(Status.enforceBackoff); + do_check_eq(backoffInterval, 42); + do_check_eq(Status.service, LOGIN_FAILED); + do_check_eq(Status.login, SERVER_MAINTENANCE); + // syncAndReportErrors means dontIgnoreErrors, which means + // didReportProlongedError not touched. + do_check_false(errorHandler.didReportProlongedError); + + clean(); + server.stop(deferred.resolve); + }); + + do_check_false(Status.enforceBackoff); + do_check_eq(Status.service, STATUS_OK); + + setLastSync(PROLONGED_ERROR_DURATION); + errorHandler.syncAndReportErrors(); + yield deferred.promise; +}); + +add_identity_test(this, function test_download_crypto_keys_login_syncAndReportErrors_prolonged_server_maintenance_error() { + // Test crypto/keys server maintenance errors are reported + // when calling syncAndReportErrors. + let server = sync_httpd_setup(); + yield setUp(server); + + yield configureIdentity({username: "broken.keys"}); + Service.serverURL = server.baseURI + "/maintenance/"; + Service.clusterURL = server.baseURI + "/maintenance/"; + // Force re-download of keys + Service.collectionKeys.clear(); + + let backoffInterval; + Svc.Obs.add("weave:service:backoff:interval", function observe(subject, data) { + Svc.Obs.remove("weave:service:backoff:interval", observe); + backoffInterval = subject; + }); + + let deferred = Promise.defer(); + Svc.Obs.add("weave:ui:login:error", function onUIUpdate() { + Svc.Obs.remove("weave:ui:login:error", onUIUpdate); + do_check_true(Status.enforceBackoff); + do_check_eq(backoffInterval, 42); + do_check_eq(Status.service, LOGIN_FAILED); + do_check_eq(Status.login, SERVER_MAINTENANCE); + // syncAndReportErrors means dontIgnoreErrors, which means + // didReportProlongedError not touched. + do_check_false(errorHandler.didReportProlongedError); + + clean(); + server.stop(deferred.resolve); + }); + + do_check_false(Status.enforceBackoff); + do_check_eq(Status.service, STATUS_OK); + + setLastSync(PROLONGED_ERROR_DURATION); + errorHandler.syncAndReportErrors(); + yield deferred.promise; +}); + +add_identity_test(this, function test_upload_crypto_keys_login_syncAndReportErrors_prolonged_server_maintenance_error() { + // Test crypto/keys server maintenance errors are reported + // when calling syncAndReportErrors. + let server = sync_httpd_setup(); + + // Start off with an empty account, do not upload a key. + yield configureIdentity({username: "broken.keys"}); + Service.serverURL = server.baseURI + "/maintenance/"; + Service.clusterURL = server.baseURI + "/maintenance/"; + + let backoffInterval; + Svc.Obs.add("weave:service:backoff:interval", function observe(subject, data) { + Svc.Obs.remove("weave:service:backoff:interval", observe); + backoffInterval = subject; + }); + + let deferred = Promise.defer(); + Svc.Obs.add("weave:ui:login:error", function onUIUpdate() { + Svc.Obs.remove("weave:ui:login:error", onUIUpdate); + do_check_true(Status.enforceBackoff); + do_check_eq(backoffInterval, 42); + do_check_eq(Status.service, LOGIN_FAILED); + do_check_eq(Status.login, SERVER_MAINTENANCE); + // syncAndReportErrors means dontIgnoreErrors, which means + // didReportProlongedError not touched. + do_check_false(errorHandler.didReportProlongedError); + + clean(); + server.stop(deferred.resolve); + }); + + do_check_false(Status.enforceBackoff); + do_check_eq(Status.service, STATUS_OK); + + setLastSync(PROLONGED_ERROR_DURATION); + errorHandler.syncAndReportErrors(); + yield deferred.promise; +}); + +add_identity_test(this, function test_wipeServer_login_syncAndReportErrors_prolonged_server_maintenance_error() { + // Test crypto/keys server maintenance errors are reported + // when calling syncAndReportErrors. + let server = sync_httpd_setup(); + + // Start off with an empty account, do not upload a key. + yield configureIdentity({username: "broken.wipe"}); + Service.serverURL = server.baseURI + "/maintenance/"; + Service.clusterURL = server.baseURI + "/maintenance/"; + + let backoffInterval; + Svc.Obs.add("weave:service:backoff:interval", function observe(subject, data) { + Svc.Obs.remove("weave:service:backoff:interval", observe); + backoffInterval = subject; + }); + + let deferred = Promise.defer(); + Svc.Obs.add("weave:ui:login:error", function onUIUpdate() { + Svc.Obs.remove("weave:ui:login:error", onUIUpdate); + do_check_true(Status.enforceBackoff); + do_check_eq(backoffInterval, 42); + do_check_eq(Status.service, LOGIN_FAILED); + do_check_eq(Status.login, SERVER_MAINTENANCE); + // syncAndReportErrors means dontIgnoreErrors, which means + // didReportProlongedError not touched. + do_check_false(errorHandler.didReportProlongedError); + + clean(); + server.stop(deferred.resolve); + }); + + do_check_false(Status.enforceBackoff); + do_check_eq(Status.service, STATUS_OK); + + setLastSync(PROLONGED_ERROR_DURATION); + errorHandler.syncAndReportErrors(); + yield deferred.promise; +}); + +add_task(function test_sync_engine_generic_fail() { + let server = sync_httpd_setup(); + + let engine = engineManager.get("catapult"); + engine.enabled = true; + engine.sync = function sync() { + Svc.Obs.notify("weave:engine:sync:error", "", "catapult"); + }; + + let log = Log.repository.getLogger("Sync.ErrorHandler"); + Svc.Prefs.set("log.appender.file.logOnError", true); + + do_check_eq(Status.engines["catapult"], undefined); + + let deferred = Promise.defer(); + // Don't wait for reset-file-log until the sync is underway. + // This avoids us catching a delayed notification from an earlier test. + Svc.Obs.add("weave:engine:sync:finish", function onEngineFinish() { + Svc.Obs.remove("weave:engine:sync:finish", onEngineFinish); + + log.info("Adding reset-file-log observer."); + Svc.Obs.add("weave:service:reset-file-log", function onResetFileLog() { + Svc.Obs.remove("weave:service:reset-file-log", onResetFileLog); + + // Put these checks here, not after sync(), so that we aren't racing the + // log handler... which resets everything just a few lines below! + _("Status.engines: " + JSON.stringify(Status.engines)); + do_check_eq(Status.engines["catapult"], ENGINE_UNKNOWN_FAIL); + do_check_eq(Status.service, SYNC_FAILED_PARTIAL); + + // Test Error log was written on SYNC_FAILED_PARTIAL. + let entries = logsdir.directoryEntries; + do_check_true(entries.hasMoreElements()); + let logfile = entries.getNext().QueryInterface(Ci.nsILocalFile); + do_check_true(logfile.leafName.startsWith("error-sync-"), logfile.leafName); + + clean(); + server.stop(deferred.resolve); + }); + }); + + do_check_true(yield setUp(server)); + Service.sync(); + yield deferred.promise; +}); + +add_test(function test_logs_on_sync_error_despite_shouldReportError() { + _("Ensure that an error is still logged when weave:service:sync:error " + + "is notified, despite shouldReportError returning false."); + + let log = Log.repository.getLogger("Sync.ErrorHandler"); + Svc.Prefs.set("log.appender.file.logOnError", true); + log.info("TESTING"); + + // Ensure that we report no error. + Status.login = MASTER_PASSWORD_LOCKED; + do_check_false(errorHandler.shouldReportError()); + + Svc.Obs.add("weave:service:reset-file-log", function onResetFileLog() { + Svc.Obs.remove("weave:service:reset-file-log", onResetFileLog); + + // Test that error log was written. + let entries = logsdir.directoryEntries; + do_check_true(entries.hasMoreElements()); + let logfile = entries.getNext().QueryInterface(Ci.nsILocalFile); + do_check_true(logfile.leafName.startsWith("error-sync-"), logfile.leafName); + + clean(); + run_next_test(); + }); + Svc.Obs.notify("weave:service:sync:error", {}); +}); + +add_test(function test_logs_on_login_error_despite_shouldReportError() { + _("Ensure that an error is still logged when weave:service:login:error " + + "is notified, despite shouldReportError returning false."); + + let log = Log.repository.getLogger("Sync.ErrorHandler"); + Svc.Prefs.set("log.appender.file.logOnError", true); + log.info("TESTING"); + + // Ensure that we report no error. + Status.login = MASTER_PASSWORD_LOCKED; + do_check_false(errorHandler.shouldReportError()); + + Svc.Obs.add("weave:service:reset-file-log", function onResetFileLog() { + Svc.Obs.remove("weave:service:reset-file-log", onResetFileLog); + + // Test that error log was written. + let entries = logsdir.directoryEntries; + do_check_true(entries.hasMoreElements()); + let logfile = entries.getNext().QueryInterface(Ci.nsILocalFile); + do_check_true(logfile.leafName.startsWith("error-sync-"), logfile.leafName); + + clean(); + run_next_test(); + }); + Svc.Obs.notify("weave:service:login:error", {}); +}); + +// This test should be the last one since it monkeypatches the engine object +// and we should only have one engine object throughout the file (bug 629664). +add_task(function test_engine_applyFailed() { + let server = sync_httpd_setup(); + + let engine = engineManager.get("catapult"); + engine.enabled = true; + delete engine.exception; + engine.sync = function sync() { + Svc.Obs.notify("weave:engine:sync:applied", {newFailed:1}, "catapult"); + }; + + let log = Log.repository.getLogger("Sync.ErrorHandler"); + Svc.Prefs.set("log.appender.file.logOnError", true); + + let deferred = Promise.defer(); + Svc.Obs.add("weave:service:reset-file-log", function onResetFileLog() { + Svc.Obs.remove("weave:service:reset-file-log", onResetFileLog); + + do_check_eq(Status.engines["catapult"], ENGINE_APPLY_FAIL); + do_check_eq(Status.service, SYNC_FAILED_PARTIAL); + + // Test Error log was written on SYNC_FAILED_PARTIAL. + let entries = logsdir.directoryEntries; + do_check_true(entries.hasMoreElements()); + let logfile = entries.getNext().QueryInterface(Ci.nsILocalFile); + do_check_true(logfile.leafName.startsWith("error-sync-"), logfile.leafName); + + clean(); + server.stop(deferred.resolve); + }); + + do_check_eq(Status.engines["catapult"], undefined); + do_check_true(yield setUp(server)); + Service.sync(); + yield deferred.promise; +}); diff --git a/services/sync/tests/unit/test_errorhandler_1.js b/services/sync/tests/unit/test_errorhandler_1.js deleted file mode 100644 index ea2070b48..000000000 --- a/services/sync/tests/unit/test_errorhandler_1.js +++ /dev/null @@ -1,913 +0,0 @@ -/* Any copyright is dedicated to the Public Domain. - http://creativecommons.org/publicdomain/zero/1.0/ */ - -Cu.import("resource://services-sync/engines/clients.js"); -Cu.import("resource://services-sync/constants.js"); -Cu.import("resource://services-sync/engines.js"); -Cu.import("resource://services-sync/keys.js"); -Cu.import("resource://services-sync/policies.js"); -Cu.import("resource://services-sync/service.js"); -Cu.import("resource://services-sync/status.js"); -Cu.import("resource://services-sync/util.js"); -Cu.import("resource://testing-common/services/sync/utils.js"); -Cu.import("resource://gre/modules/FileUtils.jsm"); - -var fakeServer = new SyncServer(); -fakeServer.start(); - -do_register_cleanup(function() { - return new Promise(resolve => { - fakeServer.stop(resolve); - }); -}); - -var fakeServerUrl = "http://localhost:" + fakeServer.port; - -const logsdir = FileUtils.getDir("ProfD", ["weave", "logs"], true); - -const PROLONGED_ERROR_DURATION = - (Svc.Prefs.get('errorhandler.networkFailureReportTimeout') * 2) * 1000; - -const NON_PROLONGED_ERROR_DURATION = - (Svc.Prefs.get('errorhandler.networkFailureReportTimeout') / 2) * 1000; - -Service.engineManager.clear(); - -function setLastSync(lastSyncValue) { - Svc.Prefs.set("lastSync", (new Date(Date.now() - lastSyncValue)).toString()); -} - -var engineManager = Service.engineManager; -engineManager.register(EHTestsCommon.CatapultEngine); - -// This relies on Service/ErrorHandler being a singleton. Fixing this will take -// a lot of work. -var errorHandler = Service.errorHandler; - -function run_test() { - initTestLogging("Trace"); - - Log.repository.getLogger("Sync.Service").level = Log.Level.Trace; - Log.repository.getLogger("Sync.SyncScheduler").level = Log.Level.Trace; - Log.repository.getLogger("Sync.ErrorHandler").level = Log.Level.Trace; - - ensureLegacyIdentityManager(); - - run_next_test(); -} - - -function clean() { - Service.startOver(); - Status.resetSync(); - Status.resetBackoff(); - errorHandler.didReportProlongedError = false; -} - -add_identity_test(this, function* test_401_logout() { - let server = EHTestsCommon.sync_httpd_setup(); - yield EHTestsCommon.setUp(server); - - // By calling sync, we ensure we're logged in. - yield sync_and_validate_telem(); - do_check_eq(Status.sync, SYNC_SUCCEEDED); - do_check_true(Service.isLoggedIn); - - let deferred = Promise.defer(); - Svc.Obs.add("weave:service:sync:error", onSyncError); - function onSyncError() { - _("Got weave:service:sync:error in first sync."); - Svc.Obs.remove("weave:service:sync:error", onSyncError); - - // Wait for the automatic next sync. - function onLoginError() { - _("Got weave:service:login:error in second sync."); - Svc.Obs.remove("weave:service:login:error", onLoginError); - - let expected = isConfiguredWithLegacyIdentity() ? - LOGIN_FAILED_LOGIN_REJECTED : LOGIN_FAILED_NETWORK_ERROR; - - do_check_eq(Status.login, expected); - do_check_false(Service.isLoggedIn); - - // Clean up. - Utils.nextTick(function () { - Service.startOver(); - server.stop(deferred.resolve); - }); - } - Svc.Obs.add("weave:service:login:error", onLoginError); - } - - // Make sync fail due to login rejected. - yield configureIdentity({username: "janedoe"}); - Service._updateCachedURLs(); - - _("Starting first sync."); - let ping = yield sync_and_validate_telem(true); - deepEqual(ping.failureReason, { name: "httperror", code: 401 }); - _("First sync done."); - yield deferred.promise; -}); - -add_identity_test(this, function* test_credentials_changed_logout() { - let server = EHTestsCommon.sync_httpd_setup(); - yield EHTestsCommon.setUp(server); - - // By calling sync, we ensure we're logged in. - yield sync_and_validate_telem(); - do_check_eq(Status.sync, SYNC_SUCCEEDED); - do_check_true(Service.isLoggedIn); - - EHTestsCommon.generateCredentialsChangedFailure(); - - let ping = yield sync_and_validate_telem(true); - equal(ping.status.sync, CREDENTIALS_CHANGED); - deepEqual(ping.failureReason, { - name: "unexpectederror", - error: "Error: Aborting sync, remote setup failed" - }); - - do_check_eq(Status.sync, CREDENTIALS_CHANGED); - do_check_false(Service.isLoggedIn); - - // Clean up. - Service.startOver(); - let deferred = Promise.defer(); - server.stop(deferred.resolve); - yield deferred.promise; -}); - -add_identity_test(this, function test_no_lastSync_pref() { - // Test reported error. - Status.resetSync(); - errorHandler.dontIgnoreErrors = true; - Status.sync = CREDENTIALS_CHANGED; - do_check_true(errorHandler.shouldReportError()); - - // Test unreported error. - Status.resetSync(); - errorHandler.dontIgnoreErrors = true; - Status.login = LOGIN_FAILED_NETWORK_ERROR; - do_check_true(errorHandler.shouldReportError()); - -}); - -add_identity_test(this, function test_shouldReportError() { - Status.login = MASTER_PASSWORD_LOCKED; - do_check_false(errorHandler.shouldReportError()); - - // Give ourselves a clusterURL so that the temporary 401 no-error situation - // doesn't come into play. - Service.serverURL = fakeServerUrl; - Service.clusterURL = fakeServerUrl; - - // Test dontIgnoreErrors, non-network, non-prolonged, login error reported - Status.resetSync(); - setLastSync(NON_PROLONGED_ERROR_DURATION); - errorHandler.dontIgnoreErrors = true; - Status.login = LOGIN_FAILED_NO_PASSWORD; - do_check_true(errorHandler.shouldReportError()); - - // Test dontIgnoreErrors, non-network, non-prolonged, sync error reported - Status.resetSync(); - setLastSync(NON_PROLONGED_ERROR_DURATION); - errorHandler.dontIgnoreErrors = true; - Status.sync = CREDENTIALS_CHANGED; - do_check_true(errorHandler.shouldReportError()); - - // Test dontIgnoreErrors, non-network, prolonged, login error reported - Status.resetSync(); - setLastSync(PROLONGED_ERROR_DURATION); - errorHandler.dontIgnoreErrors = true; - Status.login = LOGIN_FAILED_NO_PASSWORD; - do_check_true(errorHandler.shouldReportError()); - - // Test dontIgnoreErrors, non-network, prolonged, sync error reported - Status.resetSync(); - setLastSync(PROLONGED_ERROR_DURATION); - errorHandler.dontIgnoreErrors = true; - Status.sync = CREDENTIALS_CHANGED; - do_check_true(errorHandler.shouldReportError()); - - // Test dontIgnoreErrors, network, non-prolonged, login error reported - Status.resetSync(); - setLastSync(NON_PROLONGED_ERROR_DURATION); - errorHandler.dontIgnoreErrors = true; - Status.login = LOGIN_FAILED_NETWORK_ERROR; - do_check_true(errorHandler.shouldReportError()); - - // Test dontIgnoreErrors, network, non-prolonged, sync error reported - Status.resetSync(); - setLastSync(NON_PROLONGED_ERROR_DURATION); - errorHandler.dontIgnoreErrors = true; - Status.sync = LOGIN_FAILED_NETWORK_ERROR; - do_check_true(errorHandler.shouldReportError()); - - // Test dontIgnoreErrors, network, prolonged, login error reported - Status.resetSync(); - setLastSync(PROLONGED_ERROR_DURATION); - errorHandler.dontIgnoreErrors = true; - Status.login = LOGIN_FAILED_NETWORK_ERROR; - do_check_true(errorHandler.shouldReportError()); - - // Test dontIgnoreErrors, network, prolonged, sync error reported - Status.resetSync(); - setLastSync(PROLONGED_ERROR_DURATION); - errorHandler.dontIgnoreErrors = true; - Status.sync = LOGIN_FAILED_NETWORK_ERROR; - do_check_true(errorHandler.shouldReportError()); - - // Test non-network, prolonged, login error reported - do_check_false(errorHandler.didReportProlongedError); - Status.resetSync(); - setLastSync(PROLONGED_ERROR_DURATION); - errorHandler.dontIgnoreErrors = false; - Status.login = LOGIN_FAILED_NO_PASSWORD; - do_check_true(errorHandler.shouldReportError()); - do_check_true(errorHandler.didReportProlongedError); - - // Second time with prolonged error and without resetting - // didReportProlongedError, sync error should not be reported. - Status.resetSync(); - setLastSync(PROLONGED_ERROR_DURATION); - errorHandler.dontIgnoreErrors = false; - Status.login = LOGIN_FAILED_NO_PASSWORD; - do_check_false(errorHandler.shouldReportError()); - do_check_true(errorHandler.didReportProlongedError); - - // Test non-network, prolonged, sync error reported - Status.resetSync(); - setLastSync(PROLONGED_ERROR_DURATION); - errorHandler.dontIgnoreErrors = false; - errorHandler.didReportProlongedError = false; - Status.sync = CREDENTIALS_CHANGED; - do_check_true(errorHandler.shouldReportError()); - do_check_true(errorHandler.didReportProlongedError); - errorHandler.didReportProlongedError = false; - - // Test network, prolonged, login error reported - Status.resetSync(); - setLastSync(PROLONGED_ERROR_DURATION); - errorHandler.dontIgnoreErrors = false; - Status.login = LOGIN_FAILED_NETWORK_ERROR; - do_check_true(errorHandler.shouldReportError()); - do_check_true(errorHandler.didReportProlongedError); - errorHandler.didReportProlongedError = false; - - // Test network, prolonged, sync error reported - Status.resetSync(); - setLastSync(PROLONGED_ERROR_DURATION); - errorHandler.dontIgnoreErrors = false; - Status.sync = LOGIN_FAILED_NETWORK_ERROR; - do_check_true(errorHandler.shouldReportError()); - do_check_true(errorHandler.didReportProlongedError); - errorHandler.didReportProlongedError = false; - - // Test non-network, non-prolonged, login error reported - Status.resetSync(); - setLastSync(NON_PROLONGED_ERROR_DURATION); - errorHandler.dontIgnoreErrors = false; - Status.login = LOGIN_FAILED_NO_PASSWORD; - do_check_true(errorHandler.shouldReportError()); - do_check_false(errorHandler.didReportProlongedError); - - // Test non-network, non-prolonged, sync error reported - Status.resetSync(); - setLastSync(NON_PROLONGED_ERROR_DURATION); - errorHandler.dontIgnoreErrors = false; - Status.sync = CREDENTIALS_CHANGED; - do_check_true(errorHandler.shouldReportError()); - do_check_false(errorHandler.didReportProlongedError); - - // Test network, non-prolonged, login error reported - Status.resetSync(); - setLastSync(NON_PROLONGED_ERROR_DURATION); - errorHandler.dontIgnoreErrors = false; - Status.login = LOGIN_FAILED_NETWORK_ERROR; - do_check_false(errorHandler.shouldReportError()); - do_check_false(errorHandler.didReportProlongedError); - - // Test network, non-prolonged, sync error reported - Status.resetSync(); - setLastSync(NON_PROLONGED_ERROR_DURATION); - errorHandler.dontIgnoreErrors = false; - Status.sync = LOGIN_FAILED_NETWORK_ERROR; - do_check_false(errorHandler.shouldReportError()); - do_check_false(errorHandler.didReportProlongedError); - - // Test server maintenance, sync errors are not reported - Status.resetSync(); - setLastSync(NON_PROLONGED_ERROR_DURATION); - errorHandler.dontIgnoreErrors = false; - Status.sync = SERVER_MAINTENANCE; - do_check_false(errorHandler.shouldReportError()); - do_check_false(errorHandler.didReportProlongedError); - - // Test server maintenance, login errors are not reported - Status.resetSync(); - setLastSync(NON_PROLONGED_ERROR_DURATION); - errorHandler.dontIgnoreErrors = false; - Status.login = SERVER_MAINTENANCE; - do_check_false(errorHandler.shouldReportError()); - do_check_false(errorHandler.didReportProlongedError); - - // Test prolonged, server maintenance, sync errors are reported - Status.resetSync(); - setLastSync(PROLONGED_ERROR_DURATION); - errorHandler.dontIgnoreErrors = false; - Status.sync = SERVER_MAINTENANCE; - do_check_true(errorHandler.shouldReportError()); - do_check_true(errorHandler.didReportProlongedError); - errorHandler.didReportProlongedError = false; - - // Test prolonged, server maintenance, login errors are reported - Status.resetSync(); - setLastSync(PROLONGED_ERROR_DURATION); - errorHandler.dontIgnoreErrors = false; - Status.login = SERVER_MAINTENANCE; - do_check_true(errorHandler.shouldReportError()); - do_check_true(errorHandler.didReportProlongedError); - errorHandler.didReportProlongedError = false; - - // Test dontIgnoreErrors, server maintenance, sync errors are reported - Status.resetSync(); - setLastSync(NON_PROLONGED_ERROR_DURATION); - errorHandler.dontIgnoreErrors = true; - Status.sync = SERVER_MAINTENANCE; - do_check_true(errorHandler.shouldReportError()); - // dontIgnoreErrors means we don't set didReportProlongedError - do_check_false(errorHandler.didReportProlongedError); - - // Test dontIgnoreErrors, server maintenance, login errors are reported - Status.resetSync(); - setLastSync(NON_PROLONGED_ERROR_DURATION); - errorHandler.dontIgnoreErrors = true; - Status.login = SERVER_MAINTENANCE; - do_check_true(errorHandler.shouldReportError()); - do_check_false(errorHandler.didReportProlongedError); - - // Test dontIgnoreErrors, prolonged, server maintenance, - // sync errors are reported - Status.resetSync(); - setLastSync(PROLONGED_ERROR_DURATION); - errorHandler.dontIgnoreErrors = true; - Status.sync = SERVER_MAINTENANCE; - do_check_true(errorHandler.shouldReportError()); - do_check_false(errorHandler.didReportProlongedError); - - // Test dontIgnoreErrors, prolonged, server maintenance, - // login errors are reported - Status.resetSync(); - setLastSync(PROLONGED_ERROR_DURATION); - errorHandler.dontIgnoreErrors = true; - Status.login = SERVER_MAINTENANCE; - do_check_true(errorHandler.shouldReportError()); - do_check_false(errorHandler.didReportProlongedError); -}); - -add_identity_test(this, function* test_shouldReportError_master_password() { - _("Test error ignored due to locked master password"); - let server = EHTestsCommon.sync_httpd_setup(); - yield EHTestsCommon.setUp(server); - - // Monkey patch Service.verifyLogin to imitate - // master password being locked. - Service._verifyLogin = Service.verifyLogin; - Service.verifyLogin = function () { - Status.login = MASTER_PASSWORD_LOCKED; - return false; - }; - - setLastSync(NON_PROLONGED_ERROR_DURATION); - Service.sync(); - do_check_false(errorHandler.shouldReportError()); - - // Clean up. - Service.verifyLogin = Service._verifyLogin; - clean(); - let deferred = Promise.defer(); - server.stop(deferred.resolve); - yield deferred.promise; -}); - -// Test that even if we don't have a cluster URL, a login failure due to -// authentication errors is always reported. -add_identity_test(this, function test_shouldReportLoginFailureWithNoCluster() { - // Ensure no clusterURL - any error not specific to login should not be reported. - Service.serverURL = ""; - Service.clusterURL = ""; - - // Test explicit "login rejected" state. - Status.resetSync(); - // If we have a LOGIN_REJECTED state, we always report the error. - Status.login = LOGIN_FAILED_LOGIN_REJECTED; - do_check_true(errorHandler.shouldReportError()); - // But any other status with a missing clusterURL is treated as a mid-sync - // 401 (ie, should be treated as a node reassignment) - Status.login = LOGIN_SUCCEEDED; - do_check_false(errorHandler.shouldReportError()); -}); - -// XXX - how to arrange for 'Service.identity.basicPassword = null;' in -// an fxaccounts environment? -add_task(function* test_login_syncAndReportErrors_non_network_error() { - // Test non-network errors are reported - // when calling syncAndReportErrors - let server = EHTestsCommon.sync_httpd_setup(); - yield EHTestsCommon.setUp(server); - Service.identity.basicPassword = null; - - let deferred = Promise.defer(); - Svc.Obs.add("weave:ui:login:error", function onSyncError() { - Svc.Obs.remove("weave:ui:login:error", onSyncError); - do_check_eq(Status.login, LOGIN_FAILED_NO_PASSWORD); - - clean(); - server.stop(deferred.resolve); - }); - - setLastSync(NON_PROLONGED_ERROR_DURATION); - errorHandler.syncAndReportErrors(); - yield deferred.promise; -}); - -add_identity_test(this, function* test_sync_syncAndReportErrors_non_network_error() { - // Test non-network errors are reported - // when calling syncAndReportErrors - let server = EHTestsCommon.sync_httpd_setup(); - yield EHTestsCommon.setUp(server); - - // By calling sync, we ensure we're logged in. - Service.sync(); - do_check_eq(Status.sync, SYNC_SUCCEEDED); - do_check_true(Service.isLoggedIn); - - EHTestsCommon.generateCredentialsChangedFailure(); - - let deferred = Promise.defer(); - Svc.Obs.add("weave:ui:sync:error", function onSyncError() { - Svc.Obs.remove("weave:ui:sync:error", onSyncError); - do_check_eq(Status.sync, CREDENTIALS_CHANGED); - // If we clean this tick, telemetry won't get the right error - server.stop(() => { - clean(); - deferred.resolve(); - }); - }); - - setLastSync(NON_PROLONGED_ERROR_DURATION); - let ping = yield wait_for_ping(() => errorHandler.syncAndReportErrors(), true); - equal(ping.status.sync, CREDENTIALS_CHANGED); - deepEqual(ping.failureReason, { - name: "unexpectederror", - error: "Error: Aborting sync, remote setup failed" - }); - yield deferred.promise; -}); - -// XXX - how to arrange for 'Service.identity.basicPassword = null;' in -// an fxaccounts environment? -add_task(function* test_login_syncAndReportErrors_prolonged_non_network_error() { - // Test prolonged, non-network errors are - // reported when calling syncAndReportErrors. - let server = EHTestsCommon.sync_httpd_setup(); - yield EHTestsCommon.setUp(server); - Service.identity.basicPassword = null; - - let deferred = Promise.defer(); - Svc.Obs.add("weave:ui:login:error", function onSyncError() { - Svc.Obs.remove("weave:ui:login:error", onSyncError); - do_check_eq(Status.login, LOGIN_FAILED_NO_PASSWORD); - - clean(); - server.stop(deferred.resolve); - }); - - setLastSync(PROLONGED_ERROR_DURATION); - errorHandler.syncAndReportErrors(); - yield deferred.promise; -}); - -add_identity_test(this, function* test_sync_syncAndReportErrors_prolonged_non_network_error() { - // Test prolonged, non-network errors are - // reported when calling syncAndReportErrors. - let server = EHTestsCommon.sync_httpd_setup(); - yield EHTestsCommon.setUp(server); - - // By calling sync, we ensure we're logged in. - Service.sync(); - do_check_eq(Status.sync, SYNC_SUCCEEDED); - do_check_true(Service.isLoggedIn); - - EHTestsCommon.generateCredentialsChangedFailure(); - - let deferred = Promise.defer(); - Svc.Obs.add("weave:ui:sync:error", function onSyncError() { - Svc.Obs.remove("weave:ui:sync:error", onSyncError); - do_check_eq(Status.sync, CREDENTIALS_CHANGED); - // If we clean this tick, telemetry won't get the right error - server.stop(() => { - clean(); - deferred.resolve(); - }); - }); - - setLastSync(PROLONGED_ERROR_DURATION); - let ping = yield wait_for_ping(() => errorHandler.syncAndReportErrors(), true); - equal(ping.status.sync, CREDENTIALS_CHANGED); - deepEqual(ping.failureReason, { - name: "unexpectederror", - error: "Error: Aborting sync, remote setup failed" - }); - yield deferred.promise; -}); - -add_identity_test(this, function* test_login_syncAndReportErrors_network_error() { - // Test network errors are reported when calling syncAndReportErrors. - yield configureIdentity({username: "broken.wipe"}); - Service.serverURL = fakeServerUrl; - Service.clusterURL = fakeServerUrl; - - let deferred = Promise.defer(); - Svc.Obs.add("weave:ui:login:error", function onSyncError() { - Svc.Obs.remove("weave:ui:login:error", onSyncError); - do_check_eq(Status.login, LOGIN_FAILED_NETWORK_ERROR); - - clean(); - deferred.resolve(); - }); - - setLastSync(NON_PROLONGED_ERROR_DURATION); - errorHandler.syncAndReportErrors(); - yield deferred.promise; -}); - - -add_test(function test_sync_syncAndReportErrors_network_error() { - // Test network errors are reported when calling syncAndReportErrors. - Services.io.offline = true; - - Svc.Obs.add("weave:ui:sync:error", function onSyncError() { - Svc.Obs.remove("weave:ui:sync:error", onSyncError); - do_check_eq(Status.sync, LOGIN_FAILED_NETWORK_ERROR); - - Services.io.offline = false; - clean(); - run_next_test(); - }); - - setLastSync(NON_PROLONGED_ERROR_DURATION); - errorHandler.syncAndReportErrors(); -}); - -add_identity_test(this, function* test_login_syncAndReportErrors_prolonged_network_error() { - // Test prolonged, network errors are reported - // when calling syncAndReportErrors. - yield configureIdentity({username: "johndoe"}); - - Service.serverURL = fakeServerUrl; - Service.clusterURL = fakeServerUrl; - - let deferred = Promise.defer(); - Svc.Obs.add("weave:ui:login:error", function onSyncError() { - Svc.Obs.remove("weave:ui:login:error", onSyncError); - do_check_eq(Status.login, LOGIN_FAILED_NETWORK_ERROR); - - clean(); - deferred.resolve(); - }); - - setLastSync(PROLONGED_ERROR_DURATION); - errorHandler.syncAndReportErrors(); - yield deferred.promise; -}); - -add_test(function test_sync_syncAndReportErrors_prolonged_network_error() { - // Test prolonged, network errors are reported - // when calling syncAndReportErrors. - Services.io.offline = true; - - Svc.Obs.add("weave:ui:sync:error", function onSyncError() { - Svc.Obs.remove("weave:ui:sync:error", onSyncError); - do_check_eq(Status.sync, LOGIN_FAILED_NETWORK_ERROR); - - Services.io.offline = false; - clean(); - run_next_test(); - }); - - setLastSync(PROLONGED_ERROR_DURATION); - errorHandler.syncAndReportErrors(); -}); - -add_task(function* test_login_prolonged_non_network_error() { - // Test prolonged, non-network errors are reported - let server = EHTestsCommon.sync_httpd_setup(); - yield EHTestsCommon.setUp(server); - Service.identity.basicPassword = null; - - let deferred = Promise.defer(); - Svc.Obs.add("weave:ui:login:error", function onSyncError() { - Svc.Obs.remove("weave:ui:login:error", onSyncError); - do_check_eq(Status.sync, PROLONGED_SYNC_FAILURE); - do_check_true(errorHandler.didReportProlongedError); - - clean(); - server.stop(deferred.resolve); - }); - - setLastSync(PROLONGED_ERROR_DURATION); - Service.sync(); - yield deferred.promise; -}); - -add_task(function* test_sync_prolonged_non_network_error() { - // Test prolonged, non-network errors are reported - let server = EHTestsCommon.sync_httpd_setup(); - yield EHTestsCommon.setUp(server); - - // By calling sync, we ensure we're logged in. - Service.sync(); - do_check_eq(Status.sync, SYNC_SUCCEEDED); - do_check_true(Service.isLoggedIn); - - EHTestsCommon.generateCredentialsChangedFailure(); - - let deferred = Promise.defer(); - Svc.Obs.add("weave:ui:sync:error", function onSyncError() { - Svc.Obs.remove("weave:ui:sync:error", onSyncError); - do_check_eq(Status.sync, PROLONGED_SYNC_FAILURE); - do_check_true(errorHandler.didReportProlongedError); - server.stop(() => { - clean(); - deferred.resolve(); - }); - }); - - setLastSync(PROLONGED_ERROR_DURATION); - - let ping = yield sync_and_validate_telem(true); - equal(ping.status.sync, PROLONGED_SYNC_FAILURE); - deepEqual(ping.failureReason, { - name: "unexpectederror", - error: "Error: Aborting sync, remote setup failed" - }); - yield deferred.promise; -}); - -add_identity_test(this, function* test_login_prolonged_network_error() { - // Test prolonged, network errors are reported - yield configureIdentity({username: "johndoe"}); - Service.serverURL = fakeServerUrl; - Service.clusterURL = fakeServerUrl; - - let deferred = Promise.defer(); - Svc.Obs.add("weave:ui:login:error", function onSyncError() { - Svc.Obs.remove("weave:ui:login:error", onSyncError); - do_check_eq(Status.sync, PROLONGED_SYNC_FAILURE); - do_check_true(errorHandler.didReportProlongedError); - - clean(); - deferred.resolve(); - }); - - setLastSync(PROLONGED_ERROR_DURATION); - Service.sync(); - yield deferred.promise; -}); - -add_test(function test_sync_prolonged_network_error() { - // Test prolonged, network errors are reported - Services.io.offline = true; - - Svc.Obs.add("weave:ui:sync:error", function onSyncError() { - Svc.Obs.remove("weave:ui:sync:error", onSyncError); - do_check_eq(Status.sync, PROLONGED_SYNC_FAILURE); - do_check_true(errorHandler.didReportProlongedError); - - Services.io.offline = false; - clean(); - run_next_test(); - }); - - setLastSync(PROLONGED_ERROR_DURATION); - Service.sync(); -}); - -add_task(function* test_login_non_network_error() { - // Test non-network errors are reported - let server = EHTestsCommon.sync_httpd_setup(); - yield EHTestsCommon.setUp(server); - Service.identity.basicPassword = null; - - let deferred = Promise.defer(); - Svc.Obs.add("weave:ui:login:error", function onSyncError() { - Svc.Obs.remove("weave:ui:login:error", onSyncError); - do_check_eq(Status.login, LOGIN_FAILED_NO_PASSWORD); - do_check_false(errorHandler.didReportProlongedError); - - clean(); - server.stop(deferred.resolve); - }); - - setLastSync(NON_PROLONGED_ERROR_DURATION); - Service.sync(); - yield deferred.promise; -}); - -add_task(function* test_sync_non_network_error() { - // Test non-network errors are reported - let server = EHTestsCommon.sync_httpd_setup(); - yield EHTestsCommon.setUp(server); - - // By calling sync, we ensure we're logged in. - Service.sync(); - do_check_eq(Status.sync, SYNC_SUCCEEDED); - do_check_true(Service.isLoggedIn); - - EHTestsCommon.generateCredentialsChangedFailure(); - - let deferred = Promise.defer(); - Svc.Obs.add("weave:ui:sync:error", function onSyncError() { - Svc.Obs.remove("weave:ui:sync:error", onSyncError); - do_check_eq(Status.sync, CREDENTIALS_CHANGED); - do_check_false(errorHandler.didReportProlongedError); - - clean(); - server.stop(deferred.resolve); - }); - - setLastSync(NON_PROLONGED_ERROR_DURATION); - Service.sync(); - yield deferred.promise; -}); - -add_identity_test(this, function* test_login_network_error() { - yield configureIdentity({username: "johndoe"}); - Service.serverURL = fakeServerUrl; - Service.clusterURL = fakeServerUrl; - - let deferred = Promise.defer(); - // Test network errors are not reported. - Svc.Obs.add("weave:ui:clear-error", function onClearError() { - Svc.Obs.remove("weave:ui:clear-error", onClearError); - - do_check_eq(Status.login, LOGIN_FAILED_NETWORK_ERROR); - do_check_false(errorHandler.didReportProlongedError); - - Services.io.offline = false; - clean(); - deferred.resolve() - }); - - setLastSync(NON_PROLONGED_ERROR_DURATION); - Service.sync(); - yield deferred.promise; -}); - -add_test(function test_sync_network_error() { - // Test network errors are not reported. - Services.io.offline = true; - - Svc.Obs.add("weave:ui:sync:finish", function onUIUpdate() { - Svc.Obs.remove("weave:ui:sync:finish", onUIUpdate); - do_check_eq(Status.sync, LOGIN_FAILED_NETWORK_ERROR); - do_check_false(errorHandler.didReportProlongedError); - - Services.io.offline = false; - clean(); - run_next_test(); - }); - - setLastSync(NON_PROLONGED_ERROR_DURATION); - Service.sync(); -}); - -add_identity_test(this, function* test_sync_server_maintenance_error() { - // Test server maintenance errors are not reported. - let server = EHTestsCommon.sync_httpd_setup(); - yield EHTestsCommon.setUp(server); - - const BACKOFF = 42; - let engine = engineManager.get("catapult"); - engine.enabled = true; - engine.exception = {status: 503, - headers: {"retry-after": BACKOFF}}; - - function onSyncError() { - do_throw("Shouldn't get here!"); - } - Svc.Obs.add("weave:ui:sync:error", onSyncError); - - do_check_eq(Status.service, STATUS_OK); - - let deferred = Promise.defer(); - Svc.Obs.add("weave:ui:sync:finish", function onSyncFinish() { - Svc.Obs.remove("weave:ui:sync:finish", onSyncFinish); - - do_check_eq(Status.service, SYNC_FAILED_PARTIAL); - do_check_eq(Status.sync, SERVER_MAINTENANCE); - do_check_false(errorHandler.didReportProlongedError); - - Svc.Obs.remove("weave:ui:sync:error", onSyncError); - server.stop(() => { - clean(); - deferred.resolve(); - }) - }); - - setLastSync(NON_PROLONGED_ERROR_DURATION); - let ping = yield sync_and_validate_telem(true); - equal(ping.status.sync, SERVER_MAINTENANCE); - deepEqual(ping.engines.find(e => e.failureReason).failureReason, { name: "httperror", code: 503 }) - - yield deferred.promise; -}); - -add_identity_test(this, function* test_info_collections_login_server_maintenance_error() { - // Test info/collections server maintenance errors are not reported. - let server = EHTestsCommon.sync_httpd_setup(); - yield EHTestsCommon.setUp(server); - - Service.username = "broken.info"; - yield configureIdentity({username: "broken.info"}); - Service.serverURL = server.baseURI + "/maintenance/"; - Service.clusterURL = server.baseURI + "/maintenance/"; - - let backoffInterval; - Svc.Obs.add("weave:service:backoff:interval", function observe(subject, data) { - Svc.Obs.remove("weave:service:backoff:interval", observe); - backoffInterval = subject; - }); - - function onUIUpdate() { - do_throw("Shouldn't experience UI update!"); - } - Svc.Obs.add("weave:ui:login:error", onUIUpdate); - - do_check_false(Status.enforceBackoff); - do_check_eq(Status.service, STATUS_OK); - - let deferred = Promise.defer(); - Svc.Obs.add("weave:ui:clear-error", function onLoginFinish() { - Svc.Obs.remove("weave:ui:clear-error", onLoginFinish); - - do_check_true(Status.enforceBackoff); - do_check_eq(backoffInterval, 42); - do_check_eq(Status.service, LOGIN_FAILED); - do_check_eq(Status.login, SERVER_MAINTENANCE); - do_check_false(errorHandler.didReportProlongedError); - - Svc.Obs.remove("weave:ui:login:error", onUIUpdate); - clean(); - server.stop(deferred.resolve); - }); - - setLastSync(NON_PROLONGED_ERROR_DURATION); - Service.sync(); - yield deferred.promise; -}); - -add_identity_test(this, function* test_meta_global_login_server_maintenance_error() { - // Test meta/global server maintenance errors are not reported. - let server = EHTestsCommon.sync_httpd_setup(); - yield EHTestsCommon.setUp(server); - - yield configureIdentity({username: "broken.meta"}); - Service.serverURL = server.baseURI + "/maintenance/"; - Service.clusterURL = server.baseURI + "/maintenance/"; - - let backoffInterval; - Svc.Obs.add("weave:service:backoff:interval", function observe(subject, data) { - Svc.Obs.remove("weave:service:backoff:interval", observe); - backoffInterval = subject; - }); - - function onUIUpdate() { - do_throw("Shouldn't get here!"); - } - Svc.Obs.add("weave:ui:login:error", onUIUpdate); - - do_check_false(Status.enforceBackoff); - do_check_eq(Status.service, STATUS_OK); - - let deferred = Promise.defer(); - Svc.Obs.add("weave:ui:clear-error", function onLoginFinish() { - Svc.Obs.remove("weave:ui:clear-error", onLoginFinish); - - do_check_true(Status.enforceBackoff); - do_check_eq(backoffInterval, 42); - do_check_eq(Status.service, LOGIN_FAILED); - do_check_eq(Status.login, SERVER_MAINTENANCE); - do_check_false(errorHandler.didReportProlongedError); - - Svc.Obs.remove("weave:ui:login:error", onUIUpdate); - clean(); - server.stop(deferred.resolve); - }); - - setLastSync(NON_PROLONGED_ERROR_DURATION); - Service.sync(); - yield deferred.promise; -}); diff --git a/services/sync/tests/unit/test_errorhandler_2.js b/services/sync/tests/unit/test_errorhandler_2.js deleted file mode 100644 index 41f8ee727..000000000 --- a/services/sync/tests/unit/test_errorhandler_2.js +++ /dev/null @@ -1,1012 +0,0 @@ -/* Any copyright is dedicated to the Public Domain. - http://creativecommons.org/publicdomain/zero/1.0/ */ - -Cu.import("resource://services-sync/engines/clients.js"); -Cu.import("resource://services-sync/constants.js"); -Cu.import("resource://services-sync/engines.js"); -Cu.import("resource://services-sync/keys.js"); -Cu.import("resource://services-sync/policies.js"); -Cu.import("resource://services-sync/service.js"); -Cu.import("resource://services-sync/status.js"); -Cu.import("resource://services-sync/util.js"); -Cu.import("resource://testing-common/services/sync/utils.js"); -Cu.import("resource://gre/modules/FileUtils.jsm"); - -var fakeServer = new SyncServer(); -fakeServer.start(); - -do_register_cleanup(function() { - return new Promise(resolve => { - fakeServer.stop(resolve); - }); -}); - -var fakeServerUrl = "http://localhost:" + fakeServer.port; - -const logsdir = FileUtils.getDir("ProfD", ["weave", "logs"], true); - -const PROLONGED_ERROR_DURATION = - (Svc.Prefs.get('errorhandler.networkFailureReportTimeout') * 2) * 1000; - -const NON_PROLONGED_ERROR_DURATION = - (Svc.Prefs.get('errorhandler.networkFailureReportTimeout') / 2) * 1000; - -Service.engineManager.clear(); - -function setLastSync(lastSyncValue) { - Svc.Prefs.set("lastSync", (new Date(Date.now() - lastSyncValue)).toString()); -} - -var engineManager = Service.engineManager; -engineManager.register(EHTestsCommon.CatapultEngine); - -// This relies on Service/ErrorHandler being a singleton. Fixing this will take -// a lot of work. -var errorHandler = Service.errorHandler; - -function run_test() { - initTestLogging("Trace"); - - Log.repository.getLogger("Sync.Service").level = Log.Level.Trace; - Log.repository.getLogger("Sync.SyncScheduler").level = Log.Level.Trace; - Log.repository.getLogger("Sync.ErrorHandler").level = Log.Level.Trace; - - ensureLegacyIdentityManager(); - - run_next_test(); -} - - -function clean() { - Service.startOver(); - Status.resetSync(); - Status.resetBackoff(); - errorHandler.didReportProlongedError = false; -} - -add_identity_test(this, function* test_crypto_keys_login_server_maintenance_error() { - Status.resetSync(); - // Test crypto/keys server maintenance errors are not reported. - let server = EHTestsCommon.sync_httpd_setup(); - yield EHTestsCommon.setUp(server); - - yield configureIdentity({username: "broken.keys"}); - Service.serverURL = server.baseURI + "/maintenance/"; - Service.clusterURL = server.baseURI + "/maintenance/"; - - // Force re-download of keys - Service.collectionKeys.clear(); - - let backoffInterval; - Svc.Obs.add("weave:service:backoff:interval", function observe(subject, data) { - Svc.Obs.remove("weave:service:backoff:interval", observe); - backoffInterval = subject; - }); - - function onUIUpdate() { - do_throw("Shouldn't get here!"); - } - Svc.Obs.add("weave:ui:login:error", onUIUpdate); - - do_check_false(Status.enforceBackoff); - do_check_eq(Status.service, STATUS_OK); - - let deferred = Promise.defer(); - Svc.Obs.add("weave:ui:clear-error", function onLoginFinish() { - Svc.Obs.remove("weave:ui:clear-error", onLoginFinish); - - do_check_true(Status.enforceBackoff); - do_check_eq(backoffInterval, 42); - do_check_eq(Status.service, LOGIN_FAILED); - do_check_eq(Status.login, SERVER_MAINTENANCE); - do_check_false(errorHandler.didReportProlongedError); - - Svc.Obs.remove("weave:ui:login:error", onUIUpdate); - clean(); - server.stop(deferred.resolve); - }); - - setLastSync(NON_PROLONGED_ERROR_DURATION); - Service.sync(); - yield deferred.promise; -}); - -add_task(function* test_sync_prolonged_server_maintenance_error() { - // Test prolonged server maintenance errors are reported. - let server = EHTestsCommon.sync_httpd_setup(); - yield EHTestsCommon.setUp(server); - - const BACKOFF = 42; - let engine = engineManager.get("catapult"); - engine.enabled = true; - engine.exception = {status: 503, - headers: {"retry-after": BACKOFF}}; - - let deferred = Promise.defer(); - Svc.Obs.add("weave:ui:sync:error", function onUIUpdate() { - Svc.Obs.remove("weave:ui:sync:error", onUIUpdate); - do_check_eq(Status.service, SYNC_FAILED); - do_check_eq(Status.sync, PROLONGED_SYNC_FAILURE); - do_check_true(errorHandler.didReportProlongedError); - - server.stop(() => { - clean(); - deferred.resolve(); - }); - }); - - do_check_eq(Status.service, STATUS_OK); - - setLastSync(PROLONGED_ERROR_DURATION); - let ping = yield sync_and_validate_telem(true); - deepEqual(ping.status.sync, PROLONGED_SYNC_FAILURE); - deepEqual(ping.engines.find(e => e.failureReason).failureReason, - { name: "httperror", code: 503 }); - yield deferred.promise; -}); - -add_identity_test(this, function* test_info_collections_login_prolonged_server_maintenance_error(){ - // Test info/collections prolonged server maintenance errors are reported. - let server = EHTestsCommon.sync_httpd_setup(); - yield EHTestsCommon.setUp(server); - - yield configureIdentity({username: "broken.info"}); - Service.serverURL = server.baseURI + "/maintenance/"; - Service.clusterURL = server.baseURI + "/maintenance/"; - - let backoffInterval; - Svc.Obs.add("weave:service:backoff:interval", function observe(subject, data) { - Svc.Obs.remove("weave:service:backoff:interval", observe); - backoffInterval = subject; - }); - - let deferred = Promise.defer(); - Svc.Obs.add("weave:ui:login:error", function onUIUpdate() { - Svc.Obs.remove("weave:ui:login:error", onUIUpdate); - do_check_true(Status.enforceBackoff); - do_check_eq(backoffInterval, 42); - do_check_eq(Status.service, SYNC_FAILED); - do_check_eq(Status.sync, PROLONGED_SYNC_FAILURE); - do_check_true(errorHandler.didReportProlongedError); - - clean(); - server.stop(deferred.resolve); - }); - - do_check_false(Status.enforceBackoff); - do_check_eq(Status.service, STATUS_OK); - - setLastSync(PROLONGED_ERROR_DURATION); - Service.sync(); - yield deferred.promise; -}); - -add_identity_test(this, function* test_meta_global_login_prolonged_server_maintenance_error(){ - // Test meta/global prolonged server maintenance errors are reported. - let server = EHTestsCommon.sync_httpd_setup(); - yield EHTestsCommon.setUp(server); - - yield configureIdentity({username: "broken.meta"}); - Service.serverURL = server.baseURI + "/maintenance/"; - Service.clusterURL = server.baseURI + "/maintenance/"; - - let backoffInterval; - Svc.Obs.add("weave:service:backoff:interval", function observe(subject, data) { - Svc.Obs.remove("weave:service:backoff:interval", observe); - backoffInterval = subject; - }); - - let deferred = Promise.defer(); - Svc.Obs.add("weave:ui:login:error", function onUIUpdate() { - Svc.Obs.remove("weave:ui:login:error", onUIUpdate); - do_check_true(Status.enforceBackoff); - do_check_eq(backoffInterval, 42); - do_check_eq(Status.service, SYNC_FAILED); - do_check_eq(Status.sync, PROLONGED_SYNC_FAILURE); - do_check_true(errorHandler.didReportProlongedError); - - clean(); - server.stop(deferred.resolve); - }); - - do_check_false(Status.enforceBackoff); - do_check_eq(Status.service, STATUS_OK); - - setLastSync(PROLONGED_ERROR_DURATION); - Service.sync(); - yield deferred.promise; -}); - -add_identity_test(this, function* test_download_crypto_keys_login_prolonged_server_maintenance_error(){ - // Test crypto/keys prolonged server maintenance errors are reported. - let server = EHTestsCommon.sync_httpd_setup(); - yield EHTestsCommon.setUp(server); - - yield configureIdentity({username: "broken.keys"}); - Service.serverURL = server.baseURI + "/maintenance/"; - Service.clusterURL = server.baseURI + "/maintenance/"; - // Force re-download of keys - Service.collectionKeys.clear(); - - let backoffInterval; - Svc.Obs.add("weave:service:backoff:interval", function observe(subject, data) { - Svc.Obs.remove("weave:service:backoff:interval", observe); - backoffInterval = subject; - }); - - let deferred = Promise.defer(); - Svc.Obs.add("weave:ui:login:error", function onUIUpdate() { - Svc.Obs.remove("weave:ui:login:error", onUIUpdate); - do_check_true(Status.enforceBackoff); - do_check_eq(backoffInterval, 42); - do_check_eq(Status.service, SYNC_FAILED); - do_check_eq(Status.sync, PROLONGED_SYNC_FAILURE); - do_check_true(errorHandler.didReportProlongedError); - - clean(); - server.stop(deferred.resolve); - }); - - do_check_false(Status.enforceBackoff); - do_check_eq(Status.service, STATUS_OK); - - setLastSync(PROLONGED_ERROR_DURATION); - Service.sync(); - yield deferred.promise; -}); - -add_identity_test(this, function* test_upload_crypto_keys_login_prolonged_server_maintenance_error(){ - // Test crypto/keys prolonged server maintenance errors are reported. - let server = EHTestsCommon.sync_httpd_setup(); - - // Start off with an empty account, do not upload a key. - yield configureIdentity({username: "broken.keys"}); - Service.serverURL = server.baseURI + "/maintenance/"; - Service.clusterURL = server.baseURI + "/maintenance/"; - - let backoffInterval; - Svc.Obs.add("weave:service:backoff:interval", function observe(subject, data) { - Svc.Obs.remove("weave:service:backoff:interval", observe); - backoffInterval = subject; - }); - - let deferred = Promise.defer(); - Svc.Obs.add("weave:ui:login:error", function onUIUpdate() { - Svc.Obs.remove("weave:ui:login:error", onUIUpdate); - do_check_true(Status.enforceBackoff); - do_check_eq(backoffInterval, 42); - do_check_eq(Status.service, SYNC_FAILED); - do_check_eq(Status.sync, PROLONGED_SYNC_FAILURE); - do_check_true(errorHandler.didReportProlongedError); - - clean(); - server.stop(deferred.resolve); - }); - - do_check_false(Status.enforceBackoff); - do_check_eq(Status.service, STATUS_OK); - - setLastSync(PROLONGED_ERROR_DURATION); - Service.sync(); - yield deferred.promise; -}); - -add_identity_test(this, function* test_wipeServer_login_prolonged_server_maintenance_error(){ - // Test that we report prolonged server maintenance errors that occur whilst - // wiping the server. - let server = EHTestsCommon.sync_httpd_setup(); - - // Start off with an empty account, do not upload a key. - yield configureIdentity({username: "broken.wipe"}); - Service.serverURL = server.baseURI + "/maintenance/"; - Service.clusterURL = server.baseURI + "/maintenance/"; - - let backoffInterval; - Svc.Obs.add("weave:service:backoff:interval", function observe(subject, data) { - Svc.Obs.remove("weave:service:backoff:interval", observe); - backoffInterval = subject; - }); - - let deferred = Promise.defer(); - Svc.Obs.add("weave:ui:login:error", function onUIUpdate() { - Svc.Obs.remove("weave:ui:login:error", onUIUpdate); - do_check_true(Status.enforceBackoff); - do_check_eq(backoffInterval, 42); - do_check_eq(Status.service, SYNC_FAILED); - do_check_eq(Status.sync, PROLONGED_SYNC_FAILURE); - do_check_true(errorHandler.didReportProlongedError); - - clean(); - server.stop(deferred.resolve); - }); - - do_check_false(Status.enforceBackoff); - do_check_eq(Status.service, STATUS_OK); - - setLastSync(PROLONGED_ERROR_DURATION); - Service.sync(); - yield deferred.promise; -}); - -add_identity_test(this, function* test_wipeRemote_prolonged_server_maintenance_error(){ - // Test that we report prolonged server maintenance errors that occur whilst - // wiping all remote devices. - let server = EHTestsCommon.sync_httpd_setup(); - - server.registerPathHandler("/1.1/broken.wipe/storage/catapult", EHTestsCommon.service_unavailable); - yield configureIdentity({username: "broken.wipe"}); - Service.serverURL = server.baseURI + "/maintenance/"; - Service.clusterURL = server.baseURI + "/maintenance/"; - EHTestsCommon.generateAndUploadKeys(); - - let engine = engineManager.get("catapult"); - engine.exception = null; - engine.enabled = true; - - let backoffInterval; - Svc.Obs.add("weave:service:backoff:interval", function observe(subject, data) { - Svc.Obs.remove("weave:service:backoff:interval", observe); - backoffInterval = subject; - }); - - let deferred = Promise.defer(); - Svc.Obs.add("weave:ui:sync:error", function onUIUpdate() { - Svc.Obs.remove("weave:ui:sync:error", onUIUpdate); - do_check_true(Status.enforceBackoff); - do_check_eq(backoffInterval, 42); - do_check_eq(Status.service, SYNC_FAILED); - do_check_eq(Status.sync, PROLONGED_SYNC_FAILURE); - do_check_eq(Svc.Prefs.get("firstSync"), "wipeRemote"); - do_check_true(errorHandler.didReportProlongedError); - server.stop(() => { - clean(); - deferred.resolve(); - }); - }); - - do_check_false(Status.enforceBackoff); - do_check_eq(Status.service, STATUS_OK); - - Svc.Prefs.set("firstSync", "wipeRemote"); - setLastSync(PROLONGED_ERROR_DURATION); - let ping = yield sync_and_validate_telem(true); - deepEqual(ping.failureReason, { name: "httperror", code: 503 }); - yield deferred.promise; -}); - -add_task(function* test_sync_syncAndReportErrors_server_maintenance_error() { - // Test server maintenance errors are reported - // when calling syncAndReportErrors. - let server = EHTestsCommon.sync_httpd_setup(); - yield EHTestsCommon.setUp(server); - - const BACKOFF = 42; - let engine = engineManager.get("catapult"); - engine.enabled = true; - engine.exception = {status: 503, - headers: {"retry-after": BACKOFF}}; - - let deferred = Promise.defer(); - Svc.Obs.add("weave:ui:sync:error", function onUIUpdate() { - Svc.Obs.remove("weave:ui:sync:error", onUIUpdate); - do_check_eq(Status.service, SYNC_FAILED_PARTIAL); - do_check_eq(Status.sync, SERVER_MAINTENANCE); - do_check_false(errorHandler.didReportProlongedError); - - clean(); - server.stop(deferred.resolve); - }); - - do_check_eq(Status.service, STATUS_OK); - - setLastSync(NON_PROLONGED_ERROR_DURATION); - errorHandler.syncAndReportErrors(); - yield deferred.promise; -}); - -add_identity_test(this, function* test_info_collections_login_syncAndReportErrors_server_maintenance_error() { - // Test info/collections server maintenance errors are reported - // when calling syncAndReportErrors. - let server = EHTestsCommon.sync_httpd_setup(); - yield EHTestsCommon.setUp(server); - - yield configureIdentity({username: "broken.info"}); - Service.serverURL = server.baseURI + "/maintenance/"; - Service.clusterURL = server.baseURI + "/maintenance/"; - - let backoffInterval; - Svc.Obs.add("weave:service:backoff:interval", function observe(subject, data) { - Svc.Obs.remove("weave:service:backoff:interval", observe); - backoffInterval = subject; - }); - - let deferred = Promise.defer(); - Svc.Obs.add("weave:ui:login:error", function onUIUpdate() { - Svc.Obs.remove("weave:ui:login:error", onUIUpdate); - do_check_true(Status.enforceBackoff); - do_check_eq(backoffInterval, 42); - do_check_eq(Status.service, LOGIN_FAILED); - do_check_eq(Status.login, SERVER_MAINTENANCE); - do_check_false(errorHandler.didReportProlongedError); - - clean(); - server.stop(deferred.resolve); - }); - - do_check_false(Status.enforceBackoff); - do_check_eq(Status.service, STATUS_OK); - - setLastSync(NON_PROLONGED_ERROR_DURATION); - errorHandler.syncAndReportErrors(); - yield deferred.promise; -}); - -add_identity_test(this, function* test_meta_global_login_syncAndReportErrors_server_maintenance_error() { - // Test meta/global server maintenance errors are reported - // when calling syncAndReportErrors. - let server = EHTestsCommon.sync_httpd_setup(); - yield EHTestsCommon.setUp(server); - - yield configureIdentity({username: "broken.meta"}); - Service.serverURL = server.baseURI + "/maintenance/"; - Service.clusterURL = server.baseURI + "/maintenance/"; - - let backoffInterval; - Svc.Obs.add("weave:service:backoff:interval", function observe(subject, data) { - Svc.Obs.remove("weave:service:backoff:interval", observe); - backoffInterval = subject; - }); - - let deferred = Promise.defer(); - Svc.Obs.add("weave:ui:login:error", function onUIUpdate() { - Svc.Obs.remove("weave:ui:login:error", onUIUpdate); - do_check_true(Status.enforceBackoff); - do_check_eq(backoffInterval, 42); - do_check_eq(Status.service, LOGIN_FAILED); - do_check_eq(Status.login, SERVER_MAINTENANCE); - do_check_false(errorHandler.didReportProlongedError); - - clean(); - server.stop(deferred.resolve); - }); - - do_check_false(Status.enforceBackoff); - do_check_eq(Status.service, STATUS_OK); - - setLastSync(NON_PROLONGED_ERROR_DURATION); - errorHandler.syncAndReportErrors(); - yield deferred.promise; -}); - -add_identity_test(this, function* test_download_crypto_keys_login_syncAndReportErrors_server_maintenance_error() { - // Test crypto/keys server maintenance errors are reported - // when calling syncAndReportErrors. - let server = EHTestsCommon.sync_httpd_setup(); - yield EHTestsCommon.setUp(server); - - yield configureIdentity({username: "broken.keys"}); - Service.serverURL = server.baseURI + "/maintenance/"; - Service.clusterURL = server.baseURI + "/maintenance/"; - // Force re-download of keys - Service.collectionKeys.clear(); - - let backoffInterval; - Svc.Obs.add("weave:service:backoff:interval", function observe(subject, data) { - Svc.Obs.remove("weave:service:backoff:interval", observe); - backoffInterval = subject; - }); - - let deferred = Promise.defer(); - Svc.Obs.add("weave:ui:login:error", function onUIUpdate() { - Svc.Obs.remove("weave:ui:login:error", onUIUpdate); - do_check_true(Status.enforceBackoff); - do_check_eq(backoffInterval, 42); - do_check_eq(Status.service, LOGIN_FAILED); - do_check_eq(Status.login, SERVER_MAINTENANCE); - do_check_false(errorHandler.didReportProlongedError); - - clean(); - server.stop(deferred.resolve); - }); - - do_check_false(Status.enforceBackoff); - do_check_eq(Status.service, STATUS_OK); - - setLastSync(NON_PROLONGED_ERROR_DURATION); - errorHandler.syncAndReportErrors(); - yield deferred.promise; -}); - -add_identity_test(this, function* test_upload_crypto_keys_login_syncAndReportErrors_server_maintenance_error() { - // Test crypto/keys server maintenance errors are reported - // when calling syncAndReportErrors. - let server = EHTestsCommon.sync_httpd_setup(); - - // Start off with an empty account, do not upload a key. - yield configureIdentity({username: "broken.keys"}); - Service.serverURL = server.baseURI + "/maintenance/"; - Service.clusterURL = server.baseURI + "/maintenance/"; - - let backoffInterval; - Svc.Obs.add("weave:service:backoff:interval", function observe(subject, data) { - Svc.Obs.remove("weave:service:backoff:interval", observe); - backoffInterval = subject; - }); - - let deferred = Promise.defer(); - Svc.Obs.add("weave:ui:login:error", function onUIUpdate() { - Svc.Obs.remove("weave:ui:login:error", onUIUpdate); - do_check_true(Status.enforceBackoff); - do_check_eq(backoffInterval, 42); - do_check_eq(Status.service, LOGIN_FAILED); - do_check_eq(Status.login, SERVER_MAINTENANCE); - do_check_false(errorHandler.didReportProlongedError); - - clean(); - server.stop(deferred.resolve); - }); - - do_check_false(Status.enforceBackoff); - do_check_eq(Status.service, STATUS_OK); - - setLastSync(NON_PROLONGED_ERROR_DURATION); - errorHandler.syncAndReportErrors(); - yield deferred.promise; -}); - -add_identity_test(this, function* test_wipeServer_login_syncAndReportErrors_server_maintenance_error() { - // Test crypto/keys server maintenance errors are reported - // when calling syncAndReportErrors. - let server = EHTestsCommon.sync_httpd_setup(); - - // Start off with an empty account, do not upload a key. - yield configureIdentity({username: "broken.wipe"}); - Service.serverURL = server.baseURI + "/maintenance/"; - Service.clusterURL = server.baseURI + "/maintenance/"; - - let backoffInterval; - Svc.Obs.add("weave:service:backoff:interval", function observe(subject, data) { - Svc.Obs.remove("weave:service:backoff:interval", observe); - backoffInterval = subject; - }); - - let deferred = Promise.defer(); - Svc.Obs.add("weave:ui:login:error", function onUIUpdate() { - Svc.Obs.remove("weave:ui:login:error", onUIUpdate); - do_check_true(Status.enforceBackoff); - do_check_eq(backoffInterval, 42); - do_check_eq(Status.service, LOGIN_FAILED); - do_check_eq(Status.login, SERVER_MAINTENANCE); - do_check_false(errorHandler.didReportProlongedError); - - clean(); - server.stop(deferred.resolve); - }); - - do_check_false(Status.enforceBackoff); - do_check_eq(Status.service, STATUS_OK); - - setLastSync(NON_PROLONGED_ERROR_DURATION); - errorHandler.syncAndReportErrors(); - yield deferred.promise; -}); - -add_identity_test(this, function* test_wipeRemote_syncAndReportErrors_server_maintenance_error(){ - // Test that we report prolonged server maintenance errors that occur whilst - // wiping all remote devices. - let server = EHTestsCommon.sync_httpd_setup(); - - yield configureIdentity({username: "broken.wipe"}); - Service.serverURL = server.baseURI + "/maintenance/"; - Service.clusterURL = server.baseURI + "/maintenance/"; - EHTestsCommon.generateAndUploadKeys(); - - let engine = engineManager.get("catapult"); - engine.exception = null; - engine.enabled = true; - - let backoffInterval; - Svc.Obs.add("weave:service:backoff:interval", function observe(subject, data) { - Svc.Obs.remove("weave:service:backoff:interval", observe); - backoffInterval = subject; - }); - - let deferred = Promise.defer(); - Svc.Obs.add("weave:ui:sync:error", function onUIUpdate() { - Svc.Obs.remove("weave:ui:sync:error", onUIUpdate); - do_check_true(Status.enforceBackoff); - do_check_eq(backoffInterval, 42); - do_check_eq(Status.service, SYNC_FAILED); - do_check_eq(Status.sync, SERVER_MAINTENANCE); - do_check_eq(Svc.Prefs.get("firstSync"), "wipeRemote"); - do_check_false(errorHandler.didReportProlongedError); - - clean(); - server.stop(deferred.resolve); - }); - - do_check_false(Status.enforceBackoff); - do_check_eq(Status.service, STATUS_OK); - - Svc.Prefs.set("firstSync", "wipeRemote"); - setLastSync(NON_PROLONGED_ERROR_DURATION); - errorHandler.syncAndReportErrors(); - yield deferred.promise; -}); - -add_task(function* test_sync_syncAndReportErrors_prolonged_server_maintenance_error() { - // Test prolonged server maintenance errors are - // reported when calling syncAndReportErrors. - let server = EHTestsCommon.sync_httpd_setup(); - yield EHTestsCommon.setUp(server); - - const BACKOFF = 42; - let engine = engineManager.get("catapult"); - engine.enabled = true; - engine.exception = {status: 503, - headers: {"retry-after": BACKOFF}}; - - let deferred = Promise.defer(); - Svc.Obs.add("weave:ui:sync:error", function onUIUpdate() { - Svc.Obs.remove("weave:ui:sync:error", onUIUpdate); - do_check_eq(Status.service, SYNC_FAILED_PARTIAL); - do_check_eq(Status.sync, SERVER_MAINTENANCE); - // syncAndReportErrors means dontIgnoreErrors, which means - // didReportProlongedError not touched. - do_check_false(errorHandler.didReportProlongedError); - - clean(); - server.stop(deferred.resolve); - }); - - do_check_eq(Status.service, STATUS_OK); - - setLastSync(PROLONGED_ERROR_DURATION); - errorHandler.syncAndReportErrors(); - yield deferred.promise; -}); - -add_identity_test(this, function* test_info_collections_login_syncAndReportErrors_prolonged_server_maintenance_error() { - // Test info/collections server maintenance errors are reported - // when calling syncAndReportErrors. - let server = EHTestsCommon.sync_httpd_setup(); - yield EHTestsCommon.setUp(server); - - yield configureIdentity({username: "broken.info"}); - Service.serverURL = server.baseURI + "/maintenance/"; - Service.clusterURL = server.baseURI + "/maintenance/"; - - let backoffInterval; - Svc.Obs.add("weave:service:backoff:interval", function observe(subject, data) { - Svc.Obs.remove("weave:service:backoff:interval", observe); - backoffInterval = subject; - }); - - let deferred = Promise.defer(); - Svc.Obs.add("weave:ui:login:error", function onUIUpdate() { - Svc.Obs.remove("weave:ui:login:error", onUIUpdate); - do_check_true(Status.enforceBackoff); - do_check_eq(backoffInterval, 42); - do_check_eq(Status.service, LOGIN_FAILED); - do_check_eq(Status.login, SERVER_MAINTENANCE); - // syncAndReportErrors means dontIgnoreErrors, which means - // didReportProlongedError not touched. - do_check_false(errorHandler.didReportProlongedError); - - clean(); - server.stop(deferred.resolve); - }); - - do_check_false(Status.enforceBackoff); - do_check_eq(Status.service, STATUS_OK); - - setLastSync(PROLONGED_ERROR_DURATION); - errorHandler.syncAndReportErrors(); - yield deferred.promise; -}); - -add_identity_test(this, function* test_meta_global_login_syncAndReportErrors_prolonged_server_maintenance_error() { - // Test meta/global server maintenance errors are reported - // when calling syncAndReportErrors. - let server = EHTestsCommon.sync_httpd_setup(); - yield EHTestsCommon.setUp(server); - - yield configureIdentity({username: "broken.meta"}); - Service.serverURL = server.baseURI + "/maintenance/"; - Service.clusterURL = server.baseURI + "/maintenance/"; - - let backoffInterval; - Svc.Obs.add("weave:service:backoff:interval", function observe(subject, data) { - Svc.Obs.remove("weave:service:backoff:interval", observe); - backoffInterval = subject; - }); - - let deferred = Promise.defer(); - Svc.Obs.add("weave:ui:login:error", function onUIUpdate() { - Svc.Obs.remove("weave:ui:login:error", onUIUpdate); - do_check_true(Status.enforceBackoff); - do_check_eq(backoffInterval, 42); - do_check_eq(Status.service, LOGIN_FAILED); - do_check_eq(Status.login, SERVER_MAINTENANCE); - // syncAndReportErrors means dontIgnoreErrors, which means - // didReportProlongedError not touched. - do_check_false(errorHandler.didReportProlongedError); - - clean(); - server.stop(deferred.resolve); - }); - - do_check_false(Status.enforceBackoff); - do_check_eq(Status.service, STATUS_OK); - - setLastSync(PROLONGED_ERROR_DURATION); - errorHandler.syncAndReportErrors(); - yield deferred.promise; -}); - -add_identity_test(this, function* test_download_crypto_keys_login_syncAndReportErrors_prolonged_server_maintenance_error() { - // Test crypto/keys server maintenance errors are reported - // when calling syncAndReportErrors. - let server = EHTestsCommon.sync_httpd_setup(); - yield EHTestsCommon.setUp(server); - - yield configureIdentity({username: "broken.keys"}); - Service.serverURL = server.baseURI + "/maintenance/"; - Service.clusterURL = server.baseURI + "/maintenance/"; - // Force re-download of keys - Service.collectionKeys.clear(); - - let backoffInterval; - Svc.Obs.add("weave:service:backoff:interval", function observe(subject, data) { - Svc.Obs.remove("weave:service:backoff:interval", observe); - backoffInterval = subject; - }); - - let deferred = Promise.defer(); - Svc.Obs.add("weave:ui:login:error", function onUIUpdate() { - Svc.Obs.remove("weave:ui:login:error", onUIUpdate); - do_check_true(Status.enforceBackoff); - do_check_eq(backoffInterval, 42); - do_check_eq(Status.service, LOGIN_FAILED); - do_check_eq(Status.login, SERVER_MAINTENANCE); - // syncAndReportErrors means dontIgnoreErrors, which means - // didReportProlongedError not touched. - do_check_false(errorHandler.didReportProlongedError); - - clean(); - server.stop(deferred.resolve); - }); - - do_check_false(Status.enforceBackoff); - do_check_eq(Status.service, STATUS_OK); - - setLastSync(PROLONGED_ERROR_DURATION); - errorHandler.syncAndReportErrors(); - yield deferred.promise; -}); - -add_identity_test(this, function* test_upload_crypto_keys_login_syncAndReportErrors_prolonged_server_maintenance_error() { - // Test crypto/keys server maintenance errors are reported - // when calling syncAndReportErrors. - let server = EHTestsCommon.sync_httpd_setup(); - - // Start off with an empty account, do not upload a key. - yield configureIdentity({username: "broken.keys"}); - Service.serverURL = server.baseURI + "/maintenance/"; - Service.clusterURL = server.baseURI + "/maintenance/"; - - let backoffInterval; - Svc.Obs.add("weave:service:backoff:interval", function observe(subject, data) { - Svc.Obs.remove("weave:service:backoff:interval", observe); - backoffInterval = subject; - }); - - let deferred = Promise.defer(); - Svc.Obs.add("weave:ui:login:error", function onUIUpdate() { - Svc.Obs.remove("weave:ui:login:error", onUIUpdate); - do_check_true(Status.enforceBackoff); - do_check_eq(backoffInterval, 42); - do_check_eq(Status.service, LOGIN_FAILED); - do_check_eq(Status.login, SERVER_MAINTENANCE); - // syncAndReportErrors means dontIgnoreErrors, which means - // didReportProlongedError not touched. - do_check_false(errorHandler.didReportProlongedError); - - clean(); - server.stop(deferred.resolve); - }); - - do_check_false(Status.enforceBackoff); - do_check_eq(Status.service, STATUS_OK); - - setLastSync(PROLONGED_ERROR_DURATION); - errorHandler.syncAndReportErrors(); - yield deferred.promise; -}); - -add_identity_test(this, function* test_wipeServer_login_syncAndReportErrors_prolonged_server_maintenance_error() { - // Test crypto/keys server maintenance errors are reported - // when calling syncAndReportErrors. - let server = EHTestsCommon.sync_httpd_setup(); - - // Start off with an empty account, do not upload a key. - yield configureIdentity({username: "broken.wipe"}); - Service.serverURL = server.baseURI + "/maintenance/"; - Service.clusterURL = server.baseURI + "/maintenance/"; - - let backoffInterval; - Svc.Obs.add("weave:service:backoff:interval", function observe(subject, data) { - Svc.Obs.remove("weave:service:backoff:interval", observe); - backoffInterval = subject; - }); - - let deferred = Promise.defer(); - Svc.Obs.add("weave:ui:login:error", function onUIUpdate() { - Svc.Obs.remove("weave:ui:login:error", onUIUpdate); - do_check_true(Status.enforceBackoff); - do_check_eq(backoffInterval, 42); - do_check_eq(Status.service, LOGIN_FAILED); - do_check_eq(Status.login, SERVER_MAINTENANCE); - // syncAndReportErrors means dontIgnoreErrors, which means - // didReportProlongedError not touched. - do_check_false(errorHandler.didReportProlongedError); - - clean(); - server.stop(deferred.resolve); - }); - - do_check_false(Status.enforceBackoff); - do_check_eq(Status.service, STATUS_OK); - - setLastSync(PROLONGED_ERROR_DURATION); - errorHandler.syncAndReportErrors(); - yield deferred.promise; -}); - -add_task(function* test_sync_engine_generic_fail() { - let server = EHTestsCommon.sync_httpd_setup(); - -let engine = engineManager.get("catapult"); - engine.enabled = true; - engine.sync = function sync() { - Svc.Obs.notify("weave:engine:sync:error", ENGINE_UNKNOWN_FAIL, "catapult"); - }; - - let log = Log.repository.getLogger("Sync.ErrorHandler"); - Svc.Prefs.set("log.appender.file.logOnError", true); - - do_check_eq(Status.engines["catapult"], undefined); - - let deferred = Promise.defer(); - // Don't wait for reset-file-log until the sync is underway. - // This avoids us catching a delayed notification from an earlier test. - Svc.Obs.add("weave:engine:sync:finish", function onEngineFinish() { - Svc.Obs.remove("weave:engine:sync:finish", onEngineFinish); - - log.info("Adding reset-file-log observer."); - Svc.Obs.add("weave:service:reset-file-log", function onResetFileLog() { - Svc.Obs.remove("weave:service:reset-file-log", onResetFileLog); - - // Put these checks here, not after sync(), so that we aren't racing the - // log handler... which resets everything just a few lines below! - _("Status.engines: " + JSON.stringify(Status.engines)); - do_check_eq(Status.engines["catapult"], ENGINE_UNKNOWN_FAIL); - do_check_eq(Status.service, SYNC_FAILED_PARTIAL); - - // Test Error log was written on SYNC_FAILED_PARTIAL. - let entries = logsdir.directoryEntries; - do_check_true(entries.hasMoreElements()); - let logfile = entries.getNext().QueryInterface(Ci.nsILocalFile); - do_check_true(logfile.leafName.startsWith("error-sync-"), logfile.leafName); - - clean(); - - let syncErrors = sumHistogram("WEAVE_ENGINE_SYNC_ERRORS", { key: "catapult" }); - do_check_true(syncErrors, 1); - - server.stop(() => { - clean(); - deferred.resolve(); - }); - }); - }); - - do_check_true(yield EHTestsCommon.setUp(server)); - let ping = yield sync_and_validate_telem(true); - deepEqual(ping.status.service, SYNC_FAILED_PARTIAL); - deepEqual(ping.engines.find(e => e.status).status, ENGINE_UNKNOWN_FAIL); - - yield deferred.promise; -}); - -add_test(function test_logs_on_sync_error_despite_shouldReportError() { - _("Ensure that an error is still logged when weave:service:sync:error " + - "is notified, despite shouldReportError returning false."); - - let log = Log.repository.getLogger("Sync.ErrorHandler"); - Svc.Prefs.set("log.appender.file.logOnError", true); - log.info("TESTING"); - - // Ensure that we report no error. - Status.login = MASTER_PASSWORD_LOCKED; - do_check_false(errorHandler.shouldReportError()); - - Svc.Obs.add("weave:service:reset-file-log", function onResetFileLog() { - Svc.Obs.remove("weave:service:reset-file-log", onResetFileLog); - - // Test that error log was written. - let entries = logsdir.directoryEntries; - do_check_true(entries.hasMoreElements()); - let logfile = entries.getNext().QueryInterface(Ci.nsILocalFile); - do_check_true(logfile.leafName.startsWith("error-sync-"), logfile.leafName); - - clean(); - run_next_test(); - }); - Svc.Obs.notify("weave:service:sync:error", {}); -}); - -add_test(function test_logs_on_login_error_despite_shouldReportError() { - _("Ensure that an error is still logged when weave:service:login:error " + - "is notified, despite shouldReportError returning false."); - - let log = Log.repository.getLogger("Sync.ErrorHandler"); - Svc.Prefs.set("log.appender.file.logOnError", true); - log.info("TESTING"); - - // Ensure that we report no error. - Status.login = MASTER_PASSWORD_LOCKED; - do_check_false(errorHandler.shouldReportError()); - - Svc.Obs.add("weave:service:reset-file-log", function onResetFileLog() { - Svc.Obs.remove("weave:service:reset-file-log", onResetFileLog); - - // Test that error log was written. - let entries = logsdir.directoryEntries; - do_check_true(entries.hasMoreElements()); - let logfile = entries.getNext().QueryInterface(Ci.nsILocalFile); - do_check_true(logfile.leafName.startsWith("error-sync-"), logfile.leafName); - - clean(); - run_next_test(); - }); - Svc.Obs.notify("weave:service:login:error", {}); -}); - -// This test should be the last one since it monkeypatches the engine object -// and we should only have one engine object throughout the file (bug 629664). -add_task(function* test_engine_applyFailed() { - let server = EHTestsCommon.sync_httpd_setup(); - - let engine = engineManager.get("catapult"); - engine.enabled = true; - delete engine.exception; - engine.sync = function sync() { - Svc.Obs.notify("weave:engine:sync:applied", {newFailed:1}, "catapult"); - }; - - let log = Log.repository.getLogger("Sync.ErrorHandler"); - Svc.Prefs.set("log.appender.file.logOnError", true); - - let deferred = Promise.defer(); - Svc.Obs.add("weave:service:reset-file-log", function onResetFileLog() { - Svc.Obs.remove("weave:service:reset-file-log", onResetFileLog); - - do_check_eq(Status.engines["catapult"], ENGINE_APPLY_FAIL); - do_check_eq(Status.service, SYNC_FAILED_PARTIAL); - - // Test Error log was written on SYNC_FAILED_PARTIAL. - let entries = logsdir.directoryEntries; - do_check_true(entries.hasMoreElements()); - let logfile = entries.getNext().QueryInterface(Ci.nsILocalFile); - do_check_true(logfile.leafName.startsWith("error-sync-"), logfile.leafName); - - clean(); - server.stop(deferred.resolve); - }); - - do_check_eq(Status.engines["catapult"], undefined); - do_check_true(yield EHTestsCommon.setUp(server)); - Service.sync(); - yield deferred.promise; -}); diff --git a/services/sync/tests/unit/test_errorhandler_eol.js b/services/sync/tests/unit/test_errorhandler_eol.js index c8d2ff4be..381bc7268 100644 --- a/services/sync/tests/unit/test_errorhandler_eol.js +++ b/services/sync/tests/unit/test_errorhandler_eol.js @@ -43,7 +43,7 @@ function sync_httpd_setup(infoHandler) { return httpd_setup(handlers); } -function* setUp(server) { +function setUp(server) { yield configureIdentity({username: "johndoe"}); Service.serverURL = server.baseURI + "/"; Service.clusterURL = server.baseURI + "/"; @@ -66,7 +66,7 @@ function do_check_hard_eol(eh, start) { do_check_true(Status.eol); } -add_identity_test(this, function* test_200_hard() { +add_identity_test(this, function test_200_hard() { let eh = Service.errorHandler; let start = Date.now(); let server = sync_httpd_setup(handler200("hard-eol")); @@ -88,7 +88,7 @@ add_identity_test(this, function* test_200_hard() { yield deferred.promise; }); -add_identity_test(this, function* test_513_hard() { +add_identity_test(this, function test_513_hard() { let eh = Service.errorHandler; let start = Date.now(); let server = sync_httpd_setup(handler513); @@ -114,7 +114,7 @@ add_identity_test(this, function* test_513_hard() { yield deferred.promise; }); -add_identity_test(this, function* test_200_soft() { +add_identity_test(this, function test_200_soft() { let eh = Service.errorHandler; let start = Date.now(); let server = sync_httpd_setup(handler200("soft-eol")); diff --git a/services/sync/tests/unit/test_errorhandler_filelog.js b/services/sync/tests/unit/test_errorhandler_filelog.js index 993a478fd..0ce82b170 100644 --- a/services/sync/tests/unit/test_errorhandler_filelog.js +++ b/services/sync/tests/unit/test_errorhandler_filelog.js @@ -21,7 +21,7 @@ const DELAY_BUFFER = 500; // Buffer for timers on different OS platforms. const PROLONGED_ERROR_DURATION = (Svc.Prefs.get('errorhandler.networkFailureReportTimeout') * 2) * 1000; -var errorHandler = Service.errorHandler; +let errorHandler = Service.errorHandler; function setLastSync(lastSyncValue) { Svc.Prefs.set("lastSync", (new Date(Date.now() - lastSyncValue)).toString()); @@ -35,8 +35,6 @@ function run_test() { Log.repository.getLogger("Sync.SyncScheduler").level = Log.Level.Trace; Log.repository.getLogger("Sync.ErrorHandler").level = Log.Level.Trace; - validate_all_future_pings(); - run_next_test(); } @@ -47,22 +45,20 @@ add_test(function test_noOutput() { // Clear log output from startup. Svc.Prefs.set("log.appender.file.logOnSuccess", false); Svc.Obs.notify("weave:service:sync:finish"); - Svc.Obs.add("weave:service:reset-file-log", function onResetFileLogOuter() { - Svc.Obs.remove("weave:service:reset-file-log", onResetFileLogOuter); - // Clear again without having issued any output. - Svc.Prefs.set("log.appender.file.logOnSuccess", true); - Svc.Obs.add("weave:service:reset-file-log", function onResetFileLogInner() { - Svc.Obs.remove("weave:service:reset-file-log", onResetFileLogInner); + // Clear again without having issued any output. + Svc.Prefs.set("log.appender.file.logOnSuccess", true); - errorHandler._logManager._fileAppender.level = Log.Level.Trace; - Svc.Prefs.resetBranch(""); - run_next_test(); - }); + Svc.Obs.add("weave:service:reset-file-log", function onResetFileLog() { + Svc.Obs.remove("weave:service:reset-file-log", onResetFileLog); - // Fake a successful sync. - Svc.Obs.notify("weave:service:sync:finish"); + errorHandler._logManager._fileAppender.level = Log.Level.Trace; + Svc.Prefs.resetBranch(""); + run_next_test(); }); + + // Fake a successful sync. + Svc.Obs.notify("weave:service:sync:finish"); }); add_test(function test_logOnSuccess_false() { @@ -85,14 +81,16 @@ add_test(function test_logOnSuccess_false() { }); function readFile(file, callback) { - NetUtil.asyncFetch({ - uri: NetUtil.newURI(file), - loadUsingSystemPrincipal: true - }, function (inputStream, statusCode, request) { + NetUtil.asyncFetch2(file, function (inputStream, statusCode, request) { let data = NetUtil.readInputStreamToString(inputStream, inputStream.available()); callback(statusCode, data); - }); + }, + null, // aLoadingNode + Services.scriptSecurityManager.getSystemPrincipal(), + null, // aTriggeringPrincipal + Ci.nsILoadInfo.SEC_NORMAL, + Ci.nsIContentPolicy.TYPE_OTHER); } add_test(function test_logOnSuccess_true() { @@ -269,51 +267,6 @@ add_test(function test_login_error_logOnError_true() { Svc.Obs.notify("weave:service:login:error"); }); - -add_test(function test_errorLog_dumpAddons() { - Svc.Prefs.set("log.appender.file.logOnError", true); - - let log = Log.repository.getLogger("Sync.Test.FileLog"); - - // We need to wait until the log cleanup started by this test is complete - // or the next test will fail as it is ongoing. - Svc.Obs.add("services-tests:common:log-manager:cleanup-logs", function onCleanupLogs() { - Svc.Obs.remove("services-tests:common:log-manager:cleanup-logs", onCleanupLogs); - run_next_test(); - }); - - Svc.Obs.add("weave:service:reset-file-log", function onResetFileLog() { - Svc.Obs.remove("weave:service:reset-file-log", onResetFileLog); - - let entries = logsdir.directoryEntries; - do_check_true(entries.hasMoreElements()); - let logfile = entries.getNext().QueryInterface(Ci.nsILocalFile); - do_check_eq(logfile.leafName.slice(-4), ".txt"); - do_check_true(logfile.leafName.startsWith("error-sync-"), logfile.leafName); - do_check_false(entries.hasMoreElements()); - - // Ensure we logged some addon list (which is probably empty) - readFile(logfile, function (error, data) { - do_check_true(Components.isSuccessCode(error)); - do_check_neq(data.indexOf("Addons installed"), -1); - - // Clean up. - try { - logfile.remove(false); - } catch(ex) { - dump("Couldn't delete file: " + ex + "\n"); - // Stupid Windows box. - } - - Svc.Prefs.resetBranch(""); - }); - }); - - // Fake an unsuccessful sync due to prolonged failure. - setLastSync(PROLONGED_ERROR_DURATION); - Svc.Obs.notify("weave:service:sync:error"); -}); - // Check that error log files are deleted above an age threshold. add_test(function test_logErrorCleanup_age() { _("Beginning test_logErrorCleanup_age."); diff --git a/services/sync/tests/unit/test_errorhandler_sync_checkServerError.js b/services/sync/tests/unit/test_errorhandler_sync_checkServerError.js index 953f59fcb..18cea2cce 100644 --- a/services/sync/tests/unit/test_errorhandler_sync_checkServerError.js +++ b/services/sync/tests/unit/test_errorhandler_sync_checkServerError.js @@ -13,7 +13,7 @@ Cu.import("resource://testing-common/services/sync/utils.js"); initTestLogging("Trace"); -var engineManager = Service.engineManager; +let engineManager = Service.engineManager; engineManager.clear(); function promiseStopServer(server) { @@ -59,7 +59,7 @@ function sync_httpd_setup() { return httpd_setup(handlers); } -function* setUp(server) { +function setUp(server) { yield configureIdentity({username: "johndoe"}); Service.serverURL = server.baseURI + "/"; Service.clusterURL = server.baseURI + "/"; @@ -75,7 +75,7 @@ function generateAndUploadKeys(server) { } -add_identity_test(this, function* test_backoff500() { +add_identity_test(this, function test_backoff500() { _("Test: HTTP 500 sets backoff status."); let server = sync_httpd_setup(); yield setUp(server); @@ -102,7 +102,7 @@ add_identity_test(this, function* test_backoff500() { yield promiseStopServer(server); }); -add_identity_test(this, function* test_backoff503() { +add_identity_test(this, function test_backoff503() { _("Test: HTTP 503 with Retry-After header leads to backoff notification and sets backoff status."); let server = sync_httpd_setup(); yield setUp(server); @@ -138,7 +138,7 @@ add_identity_test(this, function* test_backoff503() { yield promiseStopServer(server); }); -add_identity_test(this, function* test_overQuota() { +add_identity_test(this, function test_overQuota() { _("Test: HTTP 400 with body error code 14 means over quota."); let server = sync_httpd_setup(); yield setUp(server); @@ -167,7 +167,7 @@ add_identity_test(this, function* test_overQuota() { yield promiseStopServer(server); }); -add_identity_test(this, function* test_service_networkError() { +add_identity_test(this, function test_service_networkError() { _("Test: Connection refused error from Service.sync() leads to the right status code."); let server = sync_httpd_setup(); yield setUp(server); @@ -193,14 +193,13 @@ add_identity_test(this, function* test_service_networkError() { yield deferred.promise; }); -add_identity_test(this, function* test_service_offline() { +add_identity_test(this, function test_service_offline() { _("Test: Wanting to sync in offline mode leads to the right status code but does not increment the ignorable error count."); let server = sync_httpd_setup(); yield setUp(server); let deferred = Promise.defer(); server.stop(() => { Services.io.offline = true; - Services.prefs.setBoolPref("network.dns.offline-localhost", false); try { do_check_eq(Status.sync, SYNC_SUCCEEDED); @@ -215,13 +214,12 @@ add_identity_test(this, function* test_service_offline() { Service.startOver(); } Services.io.offline = false; - Services.prefs.clearUserPref("network.dns.offline-localhost"); deferred.resolve(); }); yield deferred.promise; }); -add_identity_test(this, function* test_engine_networkError() { +add_identity_test(this, function test_engine_networkError() { _("Test: Network related exceptions from engine.sync() lead to the right status code."); let server = sync_httpd_setup(); yield setUp(server); @@ -248,7 +246,7 @@ add_identity_test(this, function* test_engine_networkError() { yield promiseStopServer(server); }); -add_identity_test(this, function* test_resource_timeout() { +add_identity_test(this, function test_resource_timeout() { let server = sync_httpd_setup(); yield setUp(server); @@ -276,7 +274,6 @@ add_identity_test(this, function* test_resource_timeout() { }); function run_test() { - validate_all_future_pings(); engineManager.register(CatapultEngine); run_next_test(); } diff --git a/services/sync/tests/unit/test_extension_storage_crypto.js b/services/sync/tests/unit/test_extension_storage_crypto.js deleted file mode 100644 index f93e4970d..000000000 --- a/services/sync/tests/unit/test_extension_storage_crypto.js +++ /dev/null @@ -1,93 +0,0 @@ -/* Any copyright is dedicated to the Public Domain. - * http://creativecommons.org/publicdomain/zero/1.0/ */ - -"use strict"; - -Cu.import("resource://services-crypto/utils.js"); -Cu.import("resource://services-sync/engines/extension-storage.js"); -Cu.import("resource://services-sync/util.js"); - -/** - * Like Assert.throws, but for generators. - * - * @param {string | Object | function} constraint - * What to use to check the exception. - * @param {function} f - * The function to call. - */ -function* throwsGen(constraint, f) { - let threw = false; - let exception; - try { - yield* f(); - } - catch (e) { - threw = true; - exception = e; - } - - ok(threw, "did not throw an exception"); - - const debuggingMessage = `got ${exception}, expected ${constraint}`; - let message = exception; - if (typeof exception === "object") { - message = exception.message; - } - - if (typeof constraint === "function") { - ok(constraint(message), debuggingMessage); - } else { - ok(constraint === message, debuggingMessage); - } - -} - -/** - * An EncryptionRemoteTransformer that uses a fixed key bundle, - * suitable for testing. - */ -class StaticKeyEncryptionRemoteTransformer extends EncryptionRemoteTransformer { - constructor(keyBundle) { - super(); - this.keyBundle = keyBundle; - } - - getKeys() { - return Promise.resolve(this.keyBundle); - } -} -const BORING_KB = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"; -const STRETCHED_KEY = CryptoUtils.hkdf(BORING_KB, undefined, `testing storage.sync encryption`, 2*32); -const KEY_BUNDLE = { - sha256HMACHasher: Utils.makeHMACHasher(Ci.nsICryptoHMAC.SHA256, Utils.makeHMACKey(STRETCHED_KEY.slice(0, 32))), - encryptionKeyB64: btoa(STRETCHED_KEY.slice(32, 64)), -}; -const transformer = new StaticKeyEncryptionRemoteTransformer(KEY_BUNDLE); - -add_task(function* test_encryption_transformer_roundtrip() { - const POSSIBLE_DATAS = [ - "string", - 2, // number - [1, 2, 3], // array - {key: "value"}, // object - ]; - - for (let data of POSSIBLE_DATAS) { - const record = {data: data, id: "key-some_2D_key", key: "some-key"}; - - deepEqual(record, yield transformer.decode(yield transformer.encode(record))); - } -}); - -add_task(function* test_refuses_to_decrypt_tampered() { - const encryptedRecord = yield transformer.encode({data: [1, 2, 3], id: "key-some_2D_key", key: "some-key"}); - const tamperedHMAC = Object.assign({}, encryptedRecord, {hmac: "0000000000000000000000000000000000000000000000000000000000000001"}); - yield* throwsGen(Utils.isHMACMismatch, function*() { - yield transformer.decode(tamperedHMAC); - }); - - const tamperedIV = Object.assign({}, encryptedRecord, {IV: "aaaaaaaaaaaaaaaaaaaaaa=="}); - yield* throwsGen(Utils.isHMACMismatch, function*() { - yield transformer.decode(tamperedIV); - }); -}); diff --git a/services/sync/tests/unit/test_extension_storage_engine.js b/services/sync/tests/unit/test_extension_storage_engine.js deleted file mode 100644 index 1b2792703..000000000 --- a/services/sync/tests/unit/test_extension_storage_engine.js +++ /dev/null @@ -1,62 +0,0 @@ -/* Any copyright is dedicated to the Public Domain. - * http://creativecommons.org/publicdomain/zero/1.0/ */ - -"use strict"; - -Cu.import("resource://services-sync/engines.js"); -Cu.import("resource://services-sync/engines/extension-storage.js"); -Cu.import("resource://services-sync/service.js"); -Cu.import("resource://services-sync/util.js"); -Cu.import("resource://testing-common/services/sync/utils.js"); -Cu.import("resource://gre/modules/ExtensionStorageSync.jsm"); - -Service.engineManager.register(ExtensionStorageEngine); -const engine = Service.engineManager.get("extension-storage"); -do_get_profile(); // so we can use FxAccounts -loadWebExtensionTestFunctions(); - -function mock(options) { - let calls = []; - let ret = function() { - calls.push(arguments); - return options.returns; - } - Object.setPrototypeOf(ret, { - __proto__: Function.prototype, - get calls() { - return calls; - } - }); - return ret; -} - -add_task(function* test_calling_sync_calls__sync() { - let oldSync = ExtensionStorageEngine.prototype._sync; - let syncMock = ExtensionStorageEngine.prototype._sync = mock({returns: true}); - try { - // I wanted to call the main sync entry point for the entire - // package, but that fails because it tries to sync ClientEngine - // first, which fails. - yield engine.sync(); - } finally { - ExtensionStorageEngine.prototype._sync = oldSync; - } - equal(syncMock.calls.length, 1); -}); - -add_task(function* test_calling_sync_calls_ext_storage_sync() { - const extension = {id: "my-extension"}; - let oldSync = ExtensionStorageSync.syncAll; - let syncMock = ExtensionStorageSync.syncAll = mock({returns: Promise.resolve()}); - try { - yield* withSyncContext(function* (context) { - // Set something so that everyone knows that we're using storage.sync - yield ExtensionStorageSync.set(extension, {"a": "b"}, context); - - yield engine._sync(); - }); - } finally { - ExtensionStorageSync.syncAll = oldSync; - } - do_check_true(syncMock.calls.length >= 1); -}); diff --git a/services/sync/tests/unit/test_extension_storage_tracker.js b/services/sync/tests/unit/test_extension_storage_tracker.js deleted file mode 100644 index fac51a897..000000000 --- a/services/sync/tests/unit/test_extension_storage_tracker.js +++ /dev/null @@ -1,38 +0,0 @@ -/* Any copyright is dedicated to the Public Domain. - * http://creativecommons.org/publicdomain/zero/1.0/ */ - -"use strict"; - -Cu.import("resource://services-sync/constants.js"); -Cu.import("resource://services-sync/engines.js"); -Cu.import("resource://services-sync/engines/extension-storage.js"); -Cu.import("resource://services-sync/service.js"); -Cu.import("resource://services-sync/util.js"); -Cu.import("resource://gre/modules/ExtensionStorageSync.jsm"); - -Service.engineManager.register(ExtensionStorageEngine); -const engine = Service.engineManager.get("extension-storage"); -do_get_profile(); // so we can use FxAccounts -loadWebExtensionTestFunctions(); - -add_task(function* test_changing_extension_storage_changes_score() { - const tracker = engine._tracker; - const extension = {id: "my-extension-id"}; - Svc.Obs.notify("weave:engine:start-tracking"); - yield* withSyncContext(function*(context) { - yield ExtensionStorageSync.set(extension, {"a": "b"}, context); - }); - do_check_eq(tracker.score, SCORE_INCREMENT_MEDIUM); - - tracker.resetScore(); - yield* withSyncContext(function*(context) { - yield ExtensionStorageSync.remove(extension, "a", context); - }); - do_check_eq(tracker.score, SCORE_INCREMENT_MEDIUM); - - Svc.Obs.notify("weave:engine:stop-tracking"); -}); - -function run_test() { - run_next_test(); -} diff --git a/services/sync/tests/unit/test_forms_tracker.js b/services/sync/tests/unit/test_forms_tracker.js index f14e208b3..5f7aaa648 100644 --- a/services/sync/tests/unit/test_forms_tracker.js +++ b/services/sync/tests/unit/test_forms_tracker.js @@ -40,18 +40,6 @@ function run_test() { addEntry("address", "Memory Lane"); do_check_attribute_count(tracker.changedIDs, 3); - - _("Check that ignoreAll is respected"); - tracker.clearChangedIDs(); - tracker.score = 0; - tracker.ignoreAll = true; - addEntry("username", "johndoe123"); - addEntry("favoritecolor", "green"); - removeEntry("name", "John Doe"); - tracker.ignoreAll = false; - do_check_empty(tracker.changedIDs); - equal(tracker.score, 0); - _("Let's stop tracking again."); tracker.clearChangedIDs(); Svc.Obs.notify("weave:engine:stop-tracking"); @@ -63,8 +51,6 @@ function run_test() { removeEntry("email", "john@doe.com"); do_check_empty(tracker.changedIDs); - - } finally { _("Clean up."); engine._store.wipe(); diff --git a/services/sync/tests/unit/test_fxa_migration.js b/services/sync/tests/unit/test_fxa_migration.js new file mode 100644 index 000000000..7c65d5996 --- /dev/null +++ b/services/sync/tests/unit/test_fxa_migration.js @@ -0,0 +1,279 @@ +// Test the FxAMigration module +Cu.import("resource://services-sync/FxaMigrator.jsm"); +Cu.import("resource://gre/modules/Promise.jsm"); +Cu.import("resource://gre/modules/FxAccounts.jsm"); +Cu.import("resource://gre/modules/FxAccountsCommon.js"); +Cu.import("resource://services-sync/browserid_identity.js"); + +// Set our username pref early so sync initializes with the legacy provider. +Services.prefs.setCharPref("services.sync.username", "foo"); +// And ensure all debug messages end up being printed. +Services.prefs.setCharPref("services.sync.log.appender.dump", "Debug"); + +// Now import sync +Cu.import("resource://services-sync/service.js"); +Cu.import("resource://services-sync/record.js"); +Cu.import("resource://services-sync/util.js"); + +// And reset the username. +Services.prefs.clearUserPref("services.sync.username"); + +Cu.import("resource://testing-common/services/sync/utils.js"); +Cu.import("resource://testing-common/services/common/logging.js"); +Cu.import("resource://testing-common/services/sync/rotaryengine.js"); + +const FXA_USERNAME = "someone@somewhere"; + +// Utilities +function promiseOneObserver(topic) { + return new Promise((resolve, reject) => { + let observer = function(subject, topic, data) { + Services.obs.removeObserver(observer, topic); + resolve({ subject: subject, data: data }); + } + Services.obs.addObserver(observer, topic, false); + }); +} + +function promiseStopServer(server) { + return new Promise((resolve, reject) => { + server.stop(resolve); + }); +} + + +// Helpers +function configureLegacySync() { + let engine = new RotaryEngine(Service); + engine.enabled = true; + Svc.Prefs.set("registerEngines", engine.name); + Svc.Prefs.set("log.logger.engine.rotary", "Trace"); + + let contents = { + meta: {global: {engines: {rotary: {version: engine.version, + syncID: engine.syncID}}}}, + crypto: {}, + rotary: {} + }; + + const USER = "foo"; + const PASSPHRASE = "abcdeabcdeabcdeabcdeabcdea"; + + setBasicCredentials(USER, "password", PASSPHRASE); + + let onRequest = function(request, response) { + // ideally we'd only do this while a legacy user is configured, but WTH. + response.setHeader("x-weave-alert", JSON.stringify({code: "soft-eol"})); + } + let server = new SyncServer({onRequest: onRequest}); + server.registerUser(USER, "password"); + server.createContents(USER, contents); + server.start(); + + Service.serverURL = server.baseURI; + Service.clusterURL = server.baseURI; + Service.identity.username = USER; + Service._updateCachedURLs(); + + Service.engineManager._engines[engine.name] = engine; + + return [engine, server]; +} + +function configureFxa() { + Services.prefs.setCharPref("identity.fxaccounts.auth.uri", "http://localhost"); +} + +add_task(function *testMigration() { + configureFxa(); + + // when we do a .startOver we want the new provider. + let oldValue = Services.prefs.getBoolPref("services.sync-testing.startOverKeepIdentity"); + Services.prefs.setBoolPref("services.sync-testing.startOverKeepIdentity", false); + + // disable the addons engine - this engine choice is arbitrary, but we + // want to check it remains disabled after migration. + Services.prefs.setBoolPref("services.sync.engine.addons", false); + + do_register_cleanup(() => { + Services.prefs.setBoolPref("services.sync-testing.startOverKeepIdentity", oldValue) + Services.prefs.setBoolPref("services.sync.engine.addons", true); + }); + + // No sync user - that should report no user-action necessary. + Assert.deepEqual((yield fxaMigrator._queueCurrentUserState()), null, + "no user state when complete"); + + // Arrange for a legacy sync user and manually bump the migrator + let [engine, server] = configureLegacySync(); + + // Check our disabling of the "addons" engine worked, and for good measure, + // that the "passwords" engine is enabled. + Assert.ok(!Service.engineManager.get("addons").enabled, "addons is disabled"); + Assert.ok(Service.engineManager.get("passwords").enabled, "passwords is enabled"); + + // monkey-patch the migration sentinel code so we know it was called. + let haveStartedSentinel = false; + let origSetFxAMigrationSentinel = Service.setFxAMigrationSentinel; + let promiseSentinelWritten = new Promise((resolve, reject) => { + Service.setFxAMigrationSentinel = function(arg) { + haveStartedSentinel = true; + return origSetFxAMigrationSentinel.call(Service, arg).then(result => { + Service.setFxAMigrationSentinel = origSetFxAMigrationSentinel; + resolve(result); + return result; + }); + } + }); + + // We are now configured for legacy sync, but we aren't in an EOL state yet, + // so should still be not waiting for a user. + Assert.deepEqual((yield fxaMigrator._queueCurrentUserState()), null, + "no user state before server EOL"); + + // Start a sync - this will cause an EOL notification which the migrator's + // observer will notice. + let promise = promiseOneObserver("fxa-migration:state-changed"); + _("Starting sync"); + Service.sync(); + _("Finished sync"); + + // We should have seen the observer, so be waiting for an FxA user. + Assert.equal((yield promise).data, fxaMigrator.STATE_USER_FXA, "now waiting for FxA.") + + // Re-calling our user-state promise should also reflect the same state. + Assert.equal((yield fxaMigrator._queueCurrentUserState()), + fxaMigrator.STATE_USER_FXA, + "still waiting for FxA."); + + // arrange for an unverified FxA user. + let config = makeIdentityConfig({username: FXA_USERNAME}); + let fxa = new FxAccounts({}); + config.fxaccount.user.email = config.username; + delete config.fxaccount.user.verified; + // *sob* - shouldn't need this boilerplate + fxa.internal.currentAccountState.getCertificate = function(data, keyPair, mustBeValidUntil) { + this.cert = { + validUntil: fxa.internal.now() + CERT_LIFETIME, + cert: "certificate", + }; + return Promise.resolve(this.cert.cert); + }; + + // As soon as we set the FxA user the observers should fire and magically + // transition. + promise = promiseOneObserver("fxa-migration:state-changed"); + fxAccounts.setSignedInUser(config.fxaccount.user); + + let observerInfo = yield promise; + Assert.equal(observerInfo.data, + fxaMigrator.STATE_USER_FXA_VERIFIED, + "now waiting for verification"); + Assert.ok(observerInfo.subject instanceof Ci.nsISupportsString, + "email was passed to observer"); + Assert.equal(observerInfo.subject.data, + FXA_USERNAME, + "email passed to observer is correct"); + + // should have seen the user set, so state should automatically update. + Assert.equal((yield fxaMigrator._queueCurrentUserState()), + fxaMigrator.STATE_USER_FXA_VERIFIED, + "now waiting for verification"); + + // Before we verify the user, fire off a sync that calls us back during + // the sync and before it completes - this way we can ensure we do the right + // thing in terms of blocking sync and waiting for it to complete. + + let wasWaiting = false; + // This is a PITA as sync is pseudo-blocking. + engine._syncFinish = function () { + // We aren't in a generator here, so use a helper to block on promises. + function getState() { + let cb = Async.makeSpinningCallback(); + fxaMigrator._queueCurrentUserState().then(state => cb(null, state)); + return cb.wait(); + } + // should still be waiting for verification. + Assert.equal(getState(), fxaMigrator.STATE_USER_FXA_VERIFIED, + "still waiting for verification"); + + // arrange for the user to be verified. The fxAccount's mock story is + // broken, so go behind its back. + config.fxaccount.user.verified = true; + fxAccounts.setSignedInUser(config.fxaccount.user); + Services.obs.notifyObservers(null, ONVERIFIED_NOTIFICATION, null); + + // spinningly wait for the migrator to catch up - sync is running so + // we should be in a 'null' user-state as there is no user-action + // necessary. + let cb = Async.makeSpinningCallback(); + promiseOneObserver("fxa-migration:state-changed").then(({ data: state }) => cb(null, state)); + Assert.equal(cb.wait(), null, "no user action necessary while sync completes."); + + // We must not have started writing the sentinel yet. + Assert.ok(!haveStartedSentinel, "haven't written a sentinel yet"); + + // sync should be blocked from continuing + Assert.ok(Service.scheduler.isBlocked, "sync is blocked.") + + wasWaiting = true; + throw ex; + }; + + _("Starting sync"); + Service.sync(); + _("Finished sync"); + + // mock sync so we can ensure the final sync is scheduled with the FxA user. + // (letting a "normal" sync complete is a PITA without mocking huge amounts + // of FxA infra) + let promiseFinalSync = new Promise((resolve, reject) => { + let oldSync = Service.sync; + Service.sync = function() { + Service.sync = oldSync; + resolve(); + } + }); + + Assert.ok(wasWaiting, "everything was good while sync was running.") + + // The migration is now going to run to completion. + // sync should still be "blocked" + Assert.ok(Service.scheduler.isBlocked, "sync is blocked."); + + // We should see the migration sentinel written and it should return true. + Assert.ok((yield promiseSentinelWritten), "wrote the sentinel"); + + // And we should see a new sync start + yield promiseFinalSync; + + // and we should be configured for FxA + let WeaveService = Cc["@mozilla.org/weave/service;1"] + .getService(Components.interfaces.nsISupports) + .wrappedJSObject; + Assert.ok(WeaveService.fxAccountsEnabled, "FxA is enabled"); + Assert.ok(Service.identity instanceof BrowserIDManager, + "sync is configured with the browserid_identity provider."); + Assert.equal(Service.identity.username, config.username, "correct user configured") + Assert.ok(!Service.scheduler.isBlocked, "sync is not blocked.") + // and the user state should remain null. + Assert.deepEqual((yield fxaMigrator._queueCurrentUserState()), + null, + "still no user action necessary"); + // and our engines should be in the same enabled/disabled state as before. + Assert.ok(!Service.engineManager.get("addons").enabled, "addons is still disabled"); + Assert.ok(Service.engineManager.get("passwords").enabled, "passwords is still enabled"); + + // aaaand, we are done - clean up. + yield promiseStopServer(server); +}); + + +function run_test() { + initTestLogging(); + do_register_cleanup(() => { + fxaMigrator.finalize(); + Svc.Prefs.resetBranch(""); + }); + run_next_test(); +} diff --git a/services/sync/tests/unit/test_fxa_migration_sentinel.js b/services/sync/tests/unit/test_fxa_migration_sentinel.js new file mode 100644 index 000000000..bed2dd756 --- /dev/null +++ b/services/sync/tests/unit/test_fxa_migration_sentinel.js @@ -0,0 +1,150 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +// Test the reading and writing of the sync migration sentinel. +Cu.import("resource://gre/modules/Promise.jsm"); +Cu.import("resource://gre/modules/FxAccounts.jsm"); +Cu.import("resource://gre/modules/FxAccountsCommon.js"); + +Cu.import("resource://testing-common/services/sync/utils.js"); +Cu.import("resource://testing-common/services/common/logging.js"); + +Cu.import("resource://services-sync/record.js"); + +// Set our username pref early so sync initializes with the legacy provider. +Services.prefs.setCharPref("services.sync.username", "foo"); + +// Now import sync +Cu.import("resource://services-sync/service.js"); + +const USER = "foo"; +const PASSPHRASE = "abcdeabcdeabcdeabcdeabcdea"; + +function promiseStopServer(server) { + return new Promise((resolve, reject) => { + server.stop(resolve); + }); +} + +let numServerRequests = 0; + +// Helpers +function configureLegacySync() { + let contents = { + meta: {global: {}}, + crypto: {}, + }; + + setBasicCredentials(USER, "password", PASSPHRASE); + + numServerRequests = 0; + let server = new SyncServer({ + onRequest: () => { + ++numServerRequests + } + }); + server.registerUser(USER, "password"); + server.createContents(USER, contents); + server.start(); + + Service.serverURL = server.baseURI; + Service.clusterURL = server.baseURI; + Service.identity.username = USER; + Service._updateCachedURLs(); + + return server; +} + +// Test a simple round-trip of the get/set functions. +add_task(function *() { + // Arrange for a legacy sync user. + let server = configureLegacySync(); + + Assert.equal((yield Service.getFxAMigrationSentinel()), null, "no sentinel to start"); + + let sentinel = {foo: "bar"}; + yield Service.setFxAMigrationSentinel(sentinel); + + Assert.deepEqual((yield Service.getFxAMigrationSentinel()), sentinel, "got the sentinel back"); + + yield promiseStopServer(server); +}); + +// Test the records are cached by the record manager. +add_task(function *() { + // Arrange for a legacy sync user. + let server = configureLegacySync(); + Service.login(); + + // Reset the request count here as the login would have made some. + numServerRequests = 0; + + Assert.equal((yield Service.getFxAMigrationSentinel()), null, "no sentinel to start"); + Assert.equal(numServerRequests, 1, "first fetch should hit the server"); + + let sentinel = {foo: "bar"}; + yield Service.setFxAMigrationSentinel(sentinel); + Assert.equal(numServerRequests, 2, "setting sentinel should hit the server"); + + Assert.deepEqual((yield Service.getFxAMigrationSentinel()), sentinel, "got the sentinel back"); + Assert.equal(numServerRequests, 2, "second fetch should not should hit the server"); + + // Clobber the caches and ensure we still get the correct value back when we + // do hit the server. + Service.recordManager.clearCache(); + Assert.deepEqual((yield Service.getFxAMigrationSentinel()), sentinel, "got the sentinel back"); + Assert.equal(numServerRequests, 3, "should have re-hit the server with empty caches"); + + yield promiseStopServer(server); +}); + +// Test the records are cached by a sync. +add_task(function* () { + let server = configureLegacySync(); + + // A first sync clobbers meta/global due to it being empty, so we first + // do a sync which forces a good set of data on the server. + Service.sync(); + + // Now create a sentinel exists on the server. It's encrypted, so we need to + // put an encrypted version. + let cryptoWrapper = new CryptoWrapper("meta", "fxa_credentials"); + let sentinel = {foo: "bar"}; + cryptoWrapper.cleartext = { + id: "fxa_credentials", + sentinel: sentinel, + deleted: false, + } + cryptoWrapper.encrypt(Service.identity.syncKeyBundle); + let payload = { + ciphertext: cryptoWrapper.ciphertext, + IV: cryptoWrapper.IV, + hmac: cryptoWrapper.hmac, + }; + + server.createContents(USER, { + meta: {fxa_credentials: payload}, + crypto: {}, + }); + + // Another sync - this will cause the encrypted record to be fetched. + Service.sync(); + // Reset the request count here as the sync will have made many! + numServerRequests = 0; + + // Asking for the sentinel should use the copy cached in the record manager. + Assert.deepEqual((yield Service.getFxAMigrationSentinel()), sentinel, "got it"); + Assert.equal(numServerRequests, 0, "should not have hit the server"); + + // And asking for it again should work (we have to work around the fact the + // ciphertext is clobbered on first decrypt...) + Assert.deepEqual((yield Service.getFxAMigrationSentinel()), sentinel, "got it again"); + Assert.equal(numServerRequests, 0, "should not have hit the server"); + + yield promiseStopServer(server); +}); + +function run_test() { + initTestLogging(); + run_next_test(); +} diff --git a/services/sync/tests/unit/test_fxa_node_reassignment.js b/services/sync/tests/unit/test_fxa_node_reassignment.js index 3e4cefd53..2f61afd6f 100644 --- a/services/sync/tests/unit/test_fxa_node_reassignment.js +++ b/services/sync/tests/unit/test_fxa_node_reassignment.js @@ -1,368 +1,321 @@ -/* Any copyright is dedicated to the Public Domain. - http://creativecommons.org/publicdomain/zero/1.0/ */ - -_("Test that node reassignment happens correctly using the FxA identity mgr."); -// The node-reassignment logic is quite different for FxA than for the legacy -// provider. In particular, there's no special request necessary for -// reassignment - it comes from the token server - so we need to ensure the -// Fxa cluster manager grabs a new token. - -Cu.import("resource://gre/modules/Log.jsm"); -Cu.import("resource://services-common/rest.js"); -Cu.import("resource://services-sync/constants.js"); -Cu.import("resource://services-sync/service.js"); -Cu.import("resource://services-sync/status.js"); -Cu.import("resource://services-sync/util.js"); -Cu.import("resource://testing-common/services/sync/rotaryengine.js"); -Cu.import("resource://services-sync/browserid_identity.js"); -Cu.import("resource://testing-common/services/sync/utils.js"); - -Service.engineManager.clear(); - -function run_test() { - Log.repository.getLogger("Sync.AsyncResource").level = Log.Level.Trace; - Log.repository.getLogger("Sync.ErrorHandler").level = Log.Level.Trace; - Log.repository.getLogger("Sync.Resource").level = Log.Level.Trace; - Log.repository.getLogger("Sync.RESTRequest").level = Log.Level.Trace; - Log.repository.getLogger("Sync.Service").level = Log.Level.Trace; - Log.repository.getLogger("Sync.SyncScheduler").level = Log.Level.Trace; - initTestLogging(); - - Service.engineManager.register(RotaryEngine); - - // Setup the FxA identity manager and cluster manager. - Status.__authManager = Service.identity = new BrowserIDManager(); - Service._clusterManager = Service.identity.createClusterManager(Service); - - // None of the failures in this file should result in a UI error. - function onUIError() { - do_throw("Errors should not be presented in the UI."); - } - Svc.Obs.add("weave:ui:login:error", onUIError); - Svc.Obs.add("weave:ui:sync:error", onUIError); - - run_next_test(); -} - - -// API-compatible with SyncServer handler. Bind `handler` to something to use -// as a ServerCollection handler. -function handleReassign(handler, req, resp) { - resp.setStatusLine(req.httpVersion, 401, "Node reassignment"); - resp.setHeader("Content-Type", "application/json"); - let reassignBody = JSON.stringify({error: "401inator in place"}); - resp.bodyOutputStream.write(reassignBody, reassignBody.length); -} - -var numTokenRequests = 0; - -function prepareServer(cbAfterTokenFetch) { - let config = makeIdentityConfig({username: "johndoe"}); - // A server callback to ensure we don't accidentally hit the wrong endpoint - // after a node reassignment. - let callback = { - __proto__: SyncServerCallback, - onRequest(req, resp) { - let full = `${req.scheme}://${req.host}:${req.port}${req.path}`; - do_check_true(full.startsWith(config.fxaccount.token.endpoint), - `request made to ${full}`); - } - } - let server = new SyncServer(callback); - server.registerUser("johndoe"); - server.start(); - - // Set the token endpoint for the initial token request that's done implicitly - // via configureIdentity. - config.fxaccount.token.endpoint = server.baseURI + "1.1/johndoe/"; - // And future token fetches will do magic around numReassigns. - let numReassigns = 0; - return configureIdentity(config).then(() => { - Service.identity._tokenServerClient = { - getTokenFromBrowserIDAssertion: function(uri, assertion, cb) { - // Build a new URL with trailing zeros for the SYNC_VERSION part - this - // will still be seen as equivalent by the test server, but different - // by sync itself. - numReassigns += 1; - let trailingZeros = new Array(numReassigns + 1).join('0'); - let token = config.fxaccount.token; - token.endpoint = server.baseURI + "1.1" + trailingZeros + "/johndoe"; - token.uid = config.username; - numTokenRequests += 1; - cb(null, token); - if (cbAfterTokenFetch) { - cbAfterTokenFetch(); - } - }, - }; - return server; - }); -} - -function getReassigned() { - try { - return Services.prefs.getBoolPref("services.sync.lastSyncReassigned"); - } catch (ex) { - if (ex.result == Cr.NS_ERROR_UNEXPECTED) { - return false; - } - do_throw("Got exception retrieving lastSyncReassigned: " + - Log.exceptionStr(ex)); - } -} - -/** - * Make a test request to `url`, then watch the result of two syncs - * to ensure that a node request was made. - * Runs `between` between the two. This can be used to undo deliberate failure - * setup, detach observers, etc. - */ -function* syncAndExpectNodeReassignment(server, firstNotification, between, - secondNotification, url) { - _("Starting syncAndExpectNodeReassignment\n"); - let deferred = Promise.defer(); - function onwards() { - let numTokenRequestsBefore; - function onFirstSync() { - _("First sync completed."); - Svc.Obs.remove(firstNotification, onFirstSync); - Svc.Obs.add(secondNotification, onSecondSync); - - do_check_eq(Service.clusterURL, ""); - - // Track whether we fetched a new token. - numTokenRequestsBefore = numTokenRequests; - - // Allow for tests to clean up error conditions. - between(); - } - function onSecondSync() { - _("Second sync completed."); - Svc.Obs.remove(secondNotification, onSecondSync); - Service.scheduler.clearSyncTriggers(); - - // Make absolutely sure that any event listeners are done with their work - // before we proceed. - waitForZeroTimer(function () { - _("Second sync nextTick."); - do_check_eq(numTokenRequests, numTokenRequestsBefore + 1, "fetched a new token"); - Service.startOver(); - server.stop(deferred.resolve); - }); - } - - Svc.Obs.add(firstNotification, onFirstSync); - Service.sync(); - } - - // Make sure that we really do get a 401 (but we can only do that if we are - // already logged in, as the login process is what sets up the URLs) - if (Service.isLoggedIn) { - _("Making request to " + url + " which should 401"); - let request = new RESTRequest(url); - request.get(function () { - do_check_eq(request.response.status, 401); - Utils.nextTick(onwards); - }); - } else { - _("Skipping preliminary validation check for a 401 as we aren't logged in"); - Utils.nextTick(onwards); - } - yield deferred.promise; -} - -// Check that when we sync we don't request a new token by default - our -// test setup has configured the client with a valid token, and that token -// should be used to form the cluster URL. -add_task(function* test_single_token_fetch() { - _("Test a normal sync only fetches 1 token"); - - let numTokenFetches = 0; - - function afterTokenFetch() { - numTokenFetches++; - } - - // Set the cluster URL to an "old" version - this is to ensure we don't - // use that old cached version for the first sync but prefer the value - // we got from the token (and as above, we are also checking we don't grab - // a new token). If the test actually attempts to connect to this URL - // it will crash. - Service.clusterURL = "http://example.com/"; - - let server = yield prepareServer(afterTokenFetch); - - do_check_false(Service.isLoggedIn, "not already logged in"); - Service.sync(); - do_check_eq(Status.sync, SYNC_SUCCEEDED, "sync succeeded"); - do_check_eq(numTokenFetches, 0, "didn't fetch a new token"); - // A bit hacky, but given we know how prepareServer works we can deduce - // that clusterURL we expect. - let expectedClusterURL = server.baseURI + "1.1/johndoe/"; - do_check_eq(Service.clusterURL, expectedClusterURL); - yield new Promise(resolve => server.stop(resolve)); -}); - -add_task(function* test_momentary_401_engine() { - _("Test a failure for engine URLs that's resolved by reassignment."); - let server = yield prepareServer(); - let john = server.user("johndoe"); - - _("Enabling the Rotary engine."); - let engine = Service.engineManager.get("rotary"); - engine.enabled = true; - - // We need the server to be correctly set up prior to experimenting. Do this - // through a sync. - let global = {syncID: Service.syncID, - storageVersion: STORAGE_VERSION, - rotary: {version: engine.version, - syncID: engine.syncID}} - john.createCollection("meta").insert("global", global); - - _("First sync to prepare server contents."); - Service.sync(); - - _("Setting up Rotary collection to 401."); - let rotary = john.createCollection("rotary"); - let oldHandler = rotary.collectionHandler; - rotary.collectionHandler = handleReassign.bind(this, undefined); - - // We want to verify that the clusterURL pref has been cleared after a 401 - // inside a sync. Flag the Rotary engine to need syncing. - john.collection("rotary").timestamp += 1000; - - function between() { - _("Undoing test changes."); - rotary.collectionHandler = oldHandler; - - function onLoginStart() { - // lastSyncReassigned shouldn't be cleared until a sync has succeeded. - _("Ensuring that lastSyncReassigned is still set at next sync start."); - Svc.Obs.remove("weave:service:login:start", onLoginStart); - do_check_true(getReassigned()); - } - - _("Adding observer that lastSyncReassigned is still set on login."); - Svc.Obs.add("weave:service:login:start", onLoginStart); - } - - yield syncAndExpectNodeReassignment(server, - "weave:service:sync:finish", - between, - "weave:service:sync:finish", - Service.storageURL + "rotary"); -}); - -// This test ends up being a failing info fetch *after we're already logged in*. -add_task(function* test_momentary_401_info_collections_loggedin() { - _("Test a failure for info/collections after login that's resolved by reassignment."); - let server = yield prepareServer(); - - _("First sync to prepare server contents."); - Service.sync(); - - _("Arrange for info/collections to return a 401."); - let oldHandler = server.toplevelHandlers.info; - server.toplevelHandlers.info = handleReassign; - - function undo() { - _("Undoing test changes."); - server.toplevelHandlers.info = oldHandler; - } - - do_check_true(Service.isLoggedIn, "already logged in"); - - yield syncAndExpectNodeReassignment(server, - "weave:service:sync:error", - undo, - "weave:service:sync:finish", - Service.infoURL); -}); - -// This test ends up being a failing info fetch *before we're logged in*. -// In this case we expect to recover during the login phase - so the first -// sync succeeds. -add_task(function* test_momentary_401_info_collections_loggedout() { - _("Test a failure for info/collections before login that's resolved by reassignment."); - - let oldHandler; - let sawTokenFetch = false; - - function afterTokenFetch() { - // After a single token fetch, we undo our evil handleReassign hack, so - // the next /info request returns the collection instead of a 401 - server.toplevelHandlers.info = oldHandler; - sawTokenFetch = true; - } - - let server = yield prepareServer(afterTokenFetch); - - // Return a 401 for the next /info request - it will be reset immediately - // after a new token is fetched. - oldHandler = server.toplevelHandlers.info - server.toplevelHandlers.info = handleReassign; - - do_check_false(Service.isLoggedIn, "not already logged in"); - - Service.sync(); - do_check_eq(Status.sync, SYNC_SUCCEEDED, "sync succeeded"); - // sync was successful - check we grabbed a new token. - do_check_true(sawTokenFetch, "a new token was fetched by this test.") - // and we are done. - Service.startOver(); - let deferred = Promise.defer(); - server.stop(deferred.resolve); - yield deferred.promise; -}); - -// This test ends up being a failing meta/global fetch *after we're already logged in*. -add_task(function* test_momentary_401_storage_loggedin() { - _("Test a failure for any storage URL after login that's resolved by" + - "reassignment."); - let server = yield prepareServer(); - - _("First sync to prepare server contents."); - Service.sync(); - - _("Arrange for meta/global to return a 401."); - let oldHandler = server.toplevelHandlers.storage; - server.toplevelHandlers.storage = handleReassign; - - function undo() { - _("Undoing test changes."); - server.toplevelHandlers.storage = oldHandler; - } - - do_check_true(Service.isLoggedIn, "already logged in"); - - yield syncAndExpectNodeReassignment(server, - "weave:service:sync:error", - undo, - "weave:service:sync:finish", - Service.storageURL + "meta/global"); -}); - -// This test ends up being a failing meta/global fetch *before we've logged in*. -add_task(function* test_momentary_401_storage_loggedout() { - _("Test a failure for any storage URL before login, not just engine parts. " + - "Resolved by reassignment."); - let server = yield prepareServer(); - - // Return a 401 for all storage requests. - let oldHandler = server.toplevelHandlers.storage; - server.toplevelHandlers.storage = handleReassign; - - function undo() { - _("Undoing test changes."); - server.toplevelHandlers.storage = oldHandler; - } - - do_check_false(Service.isLoggedIn, "already logged in"); - - yield syncAndExpectNodeReassignment(server, - "weave:service:login:error", - undo, - "weave:service:sync:finish", - Service.storageURL + "meta/global"); -}); +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +_("Test that node reassignment happens correctly using the FxA identity mgr."); +// The node-reassignment logic is quite different for FxA than for the legacy +// provider. In particular, there's no special request necessary for +// reassignment - it comes from the token server - so we need to ensure the +// Fxa cluster manager grabs a new token. + +Cu.import("resource://gre/modules/Log.jsm"); +Cu.import("resource://services-common/rest.js"); +Cu.import("resource://services-sync/constants.js"); +Cu.import("resource://services-sync/service.js"); +Cu.import("resource://services-sync/status.js"); +Cu.import("resource://services-sync/util.js"); +Cu.import("resource://testing-common/services/sync/rotaryengine.js"); +Cu.import("resource://services-sync/browserid_identity.js"); +Cu.import("resource://testing-common/services/sync/utils.js"); + +Service.engineManager.clear(); + +function run_test() { + Log.repository.getLogger("Sync.AsyncResource").level = Log.Level.Trace; + Log.repository.getLogger("Sync.ErrorHandler").level = Log.Level.Trace; + Log.repository.getLogger("Sync.Resource").level = Log.Level.Trace; + Log.repository.getLogger("Sync.RESTRequest").level = Log.Level.Trace; + Log.repository.getLogger("Sync.Service").level = Log.Level.Trace; + Log.repository.getLogger("Sync.SyncScheduler").level = Log.Level.Trace; + initTestLogging(); + + Service.engineManager.register(RotaryEngine); + + // Setup the FxA identity manager and cluster manager. + Status.__authManager = Service.identity = new BrowserIDManager(); + Service._clusterManager = Service.identity.createClusterManager(Service); + + // None of the failures in this file should result in a UI error. + function onUIError() { + do_throw("Errors should not be presented in the UI."); + } + Svc.Obs.add("weave:ui:login:error", onUIError); + Svc.Obs.add("weave:ui:sync:error", onUIError); + + run_next_test(); +} + + +// API-compatible with SyncServer handler. Bind `handler` to something to use +// as a ServerCollection handler. +function handleReassign(handler, req, resp) { + resp.setStatusLine(req.httpVersion, 401, "Node reassignment"); + resp.setHeader("Content-Type", "application/json"); + let reassignBody = JSON.stringify({error: "401inator in place"}); + resp.bodyOutputStream.write(reassignBody, reassignBody.length); +} + +let numTokenRequests = 0; + +function prepareServer(cbAfterTokenFetch) { + let config = makeIdentityConfig({username: "johndoe"}); + let server = new SyncServer(); + server.registerUser("johndoe"); + server.start(); + + // Set the token endpoint for the initial token request that's done implicitly + // via configureIdentity. + config.fxaccount.token.endpoint = server.baseURI + "1.1/johndoe"; + // And future token fetches will do magic around numReassigns. + let numReassigns = 0; + return configureIdentity(config).then(() => { + Service.identity._tokenServerClient = { + getTokenFromBrowserIDAssertion: function(uri, assertion, cb) { + // Build a new URL with trailing zeros for the SYNC_VERSION part - this + // will still be seen as equivalent by the test server, but different + // by sync itself. + numReassigns += 1; + let trailingZeros = new Array(numReassigns + 1).join('0'); + let token = config.fxaccount.token; + token.endpoint = server.baseURI + "1.1" + trailingZeros + "/johndoe"; + token.uid = config.username; + numTokenRequests += 1; + cb(null, token); + if (cbAfterTokenFetch) { + cbAfterTokenFetch(); + } + }, + }; + Service.clusterURL = config.fxaccount.token.endpoint; + return server; + }); +} + +function getReassigned() { + try { + return Services.prefs.getBoolPref("services.sync.lastSyncReassigned"); + } catch (ex if (ex.result == Cr.NS_ERROR_UNEXPECTED)) { + return false; + } catch (ex) { + do_throw("Got exception retrieving lastSyncReassigned: " + + Utils.exceptionStr(ex)); + } +} + +/** + * Make a test request to `url`, then watch the result of two syncs + * to ensure that a node request was made. + * Runs `between` between the two. This can be used to undo deliberate failure + * setup, detach observers, etc. + */ +function syncAndExpectNodeReassignment(server, firstNotification, between, + secondNotification, url) { + _("Starting syncAndExpectNodeReassignment\n"); + let deferred = Promise.defer(); + function onwards() { + let numTokenRequestsBefore; + function onFirstSync() { + _("First sync completed."); + Svc.Obs.remove(firstNotification, onFirstSync); + Svc.Obs.add(secondNotification, onSecondSync); + + do_check_eq(Service.clusterURL, ""); + + // Track whether we fetched a new token. + numTokenRequestsBefore = numTokenRequests; + + // Allow for tests to clean up error conditions. + between(); + } + function onSecondSync() { + _("Second sync completed."); + Svc.Obs.remove(secondNotification, onSecondSync); + Service.scheduler.clearSyncTriggers(); + + // Make absolutely sure that any event listeners are done with their work + // before we proceed. + waitForZeroTimer(function () { + _("Second sync nextTick."); + do_check_eq(numTokenRequests, numTokenRequestsBefore + 1, "fetched a new token"); + Service.startOver(); + server.stop(deferred.resolve); + }); + } + + Svc.Obs.add(firstNotification, onFirstSync); + Service.sync(); + } + + // Make sure that it works! + _("Making request to " + url + " which should 401"); + let request = new RESTRequest(url); + request.get(function () { + do_check_eq(request.response.status, 401); + Utils.nextTick(onwards); + }); + yield deferred.promise; +} + +add_task(function test_momentary_401_engine() { + _("Test a failure for engine URLs that's resolved by reassignment."); + let server = yield prepareServer(); + let john = server.user("johndoe"); + + _("Enabling the Rotary engine."); + let engine = Service.engineManager.get("rotary"); + engine.enabled = true; + + // We need the server to be correctly set up prior to experimenting. Do this + // through a sync. + let global = {syncID: Service.syncID, + storageVersion: STORAGE_VERSION, + rotary: {version: engine.version, + syncID: engine.syncID}} + john.createCollection("meta").insert("global", global); + + _("First sync to prepare server contents."); + Service.sync(); + + _("Setting up Rotary collection to 401."); + let rotary = john.createCollection("rotary"); + let oldHandler = rotary.collectionHandler; + rotary.collectionHandler = handleReassign.bind(this, undefined); + + // We want to verify that the clusterURL pref has been cleared after a 401 + // inside a sync. Flag the Rotary engine to need syncing. + john.collection("rotary").timestamp += 1000; + + function between() { + _("Undoing test changes."); + rotary.collectionHandler = oldHandler; + + function onLoginStart() { + // lastSyncReassigned shouldn't be cleared until a sync has succeeded. + _("Ensuring that lastSyncReassigned is still set at next sync start."); + Svc.Obs.remove("weave:service:login:start", onLoginStart); + do_check_true(getReassigned()); + } + + _("Adding observer that lastSyncReassigned is still set on login."); + Svc.Obs.add("weave:service:login:start", onLoginStart); + } + + yield syncAndExpectNodeReassignment(server, + "weave:service:sync:finish", + between, + "weave:service:sync:finish", + Service.storageURL + "rotary"); +}); + +// This test ends up being a failing info fetch *after we're already logged in*. +add_task(function test_momentary_401_info_collections_loggedin() { + _("Test a failure for info/collections after login that's resolved by reassignment."); + let server = yield prepareServer(); + + _("First sync to prepare server contents."); + Service.sync(); + + _("Arrange for info/collections to return a 401."); + let oldHandler = server.toplevelHandlers.info; + server.toplevelHandlers.info = handleReassign; + + function undo() { + _("Undoing test changes."); + server.toplevelHandlers.info = oldHandler; + } + + do_check_true(Service.isLoggedIn, "already logged in"); + + yield syncAndExpectNodeReassignment(server, + "weave:service:sync:error", + undo, + "weave:service:sync:finish", + Service.infoURL); +}); + +// This test ends up being a failing info fetch *before we're logged in*. +// In this case we expect to recover during the login phase - so the first +// sync succeeds. +add_task(function test_momentary_401_info_collections_loggedout() { + _("Test a failure for info/collections before login that's resolved by reassignment."); + + let oldHandler; + let sawTokenFetch = false; + + function afterTokenFetch() { + // After a single token fetch, we undo our evil handleReassign hack, so + // the next /info request returns the collection instead of a 401 + server.toplevelHandlers.info = oldHandler; + sawTokenFetch = true; + } + + let server = yield prepareServer(afterTokenFetch); + + // Return a 401 for the next /info request - it will be reset immediately + // after a new token is fetched. + oldHandler = server.toplevelHandlers.info + server.toplevelHandlers.info = handleReassign; + + do_check_false(Service.isLoggedIn, "not already logged in"); + + Service.sync(); + do_check_eq(Status.sync, SYNC_SUCCEEDED, "sync succeeded"); + // sync was successful - check we grabbed a new token. + do_check_true(sawTokenFetch, "a new token was fetched by this test.") + // and we are done. + Service.startOver(); + let deferred = Promise.defer(); + server.stop(deferred.resolve); + yield deferred.promise; +}); + +// This test ends up being a failing meta/global fetch *after we're already logged in*. +add_task(function test_momentary_401_storage_loggedin() { + _("Test a failure for any storage URL after login that's resolved by" + + "reassignment."); + let server = yield prepareServer(); + + _("First sync to prepare server contents."); + Service.sync(); + + _("Arrange for meta/global to return a 401."); + let oldHandler = server.toplevelHandlers.storage; + server.toplevelHandlers.storage = handleReassign; + + function undo() { + _("Undoing test changes."); + server.toplevelHandlers.storage = oldHandler; + } + + do_check_true(Service.isLoggedIn, "already logged in"); + + yield syncAndExpectNodeReassignment(server, + "weave:service:sync:error", + undo, + "weave:service:sync:finish", + Service.storageURL + "meta/global"); +}); + +// This test ends up being a failing meta/global fetch *before we've logged in*. +add_task(function test_momentary_401_storage_loggedout() { + _("Test a failure for any storage URL before login, not just engine parts. " + + "Resolved by reassignment."); + let server = yield prepareServer(); + + // Return a 401 for all storage requests. + let oldHandler = server.toplevelHandlers.storage; + server.toplevelHandlers.storage = handleReassign; + + function undo() { + _("Undoing test changes."); + server.toplevelHandlers.storage = oldHandler; + } + + do_check_false(Service.isLoggedIn, "already logged in"); + + yield syncAndExpectNodeReassignment(server, + "weave:service:login:error", + undo, + "weave:service:sync:finish", + Service.storageURL + "meta/global"); +}); + diff --git a/services/sync/tests/unit/test_fxa_service_cluster.js b/services/sync/tests/unit/test_fxa_service_cluster.js index b4f83a7fe..f6f97184a 100644 --- a/services/sync/tests/unit/test_fxa_service_cluster.js +++ b/services/sync/tests/unit/test_fxa_service_cluster.js @@ -1,68 +1,68 @@ -/* Any copyright is dedicated to the Public Domain. - http://creativecommons.org/publicdomain/zero/1.0/ */ - -Cu.import("resource://services-sync/service.js"); -Cu.import("resource://services-sync/util.js"); -Cu.import("resource://testing-common/services/sync/fxa_utils.js"); -Cu.import("resource://testing-common/services/sync/utils.js"); - -add_task(function* test_findCluster() { - _("Test FxA _findCluster()"); - - _("_findCluster() throws on 500 errors."); - initializeIdentityWithTokenServerResponse({ - status: 500, - headers: [], - body: "", - }); - - yield Service.identity.initializeWithCurrentIdentity(); - yield Assert.rejects(Service.identity.whenReadyToAuthenticate.promise, - "should reject due to 500"); - - Assert.throws(function() { - Service._clusterManager._findCluster(); - }); - - _("_findCluster() returns null on authentication errors."); - initializeIdentityWithTokenServerResponse({ - status: 401, - headers: {"content-type": "application/json"}, - body: "{}", - }); - - yield Service.identity.initializeWithCurrentIdentity(); - yield Assert.rejects(Service.identity.whenReadyToAuthenticate.promise, - "should reject due to 401"); - - cluster = Service._clusterManager._findCluster(); - Assert.strictEqual(cluster, null); - - _("_findCluster() works with correct tokenserver response."); - let endpoint = "http://example.com/something"; - initializeIdentityWithTokenServerResponse({ - status: 200, - headers: {"content-type": "application/json"}, - body: - JSON.stringify({ - api_endpoint: endpoint, - duration: 300, - id: "id", - key: "key", - uid: "uid", - }) - }); - - yield Service.identity.initializeWithCurrentIdentity(); - yield Service.identity.whenReadyToAuthenticate.promise; - cluster = Service._clusterManager._findCluster(); - // The cluster manager ensures a trailing "/" - Assert.strictEqual(cluster, endpoint + "/"); - - Svc.Prefs.resetBranch(""); -}); - -function run_test() { - initTestLogging(); - run_next_test(); -} +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +Cu.import("resource://services-sync/service.js"); +Cu.import("resource://services-sync/util.js"); +Cu.import("resource://testing-common/services/sync/fxa_utils.js"); +Cu.import("resource://testing-common/services/sync/utils.js"); + +add_task(function test_findCluster() { + _("Test FxA _findCluster()"); + + _("_findCluster() throws on 500 errors."); + initializeIdentityWithTokenServerResponse({ + status: 500, + headers: [], + body: "", + }); + + yield Service.identity.initializeWithCurrentIdentity(); + yield Assert.rejects(Service.identity.whenReadyToAuthenticate.promise, + "should reject due to 500"); + + Assert.throws(function() { + Service._clusterManager._findCluster(); + }); + + _("_findCluster() returns null on authentication errors."); + initializeIdentityWithTokenServerResponse({ + status: 401, + headers: {"content-type": "application/json"}, + body: "{}", + }); + + yield Service.identity.initializeWithCurrentIdentity(); + yield Assert.rejects(Service.identity.whenReadyToAuthenticate.promise, + "should reject due to 401"); + + cluster = Service._clusterManager._findCluster(); + Assert.strictEqual(cluster, null); + + _("_findCluster() works with correct tokenserver response."); + let endpoint = "http://example.com/something"; + initializeIdentityWithTokenServerResponse({ + status: 200, + headers: {"content-type": "application/json"}, + body: + JSON.stringify({ + api_endpoint: endpoint, + duration: 300, + id: "id", + key: "key", + uid: "uid", + }) + }); + + yield Service.identity.initializeWithCurrentIdentity(); + yield Service.identity.whenReadyToAuthenticate.promise; + cluster = Service._clusterManager._findCluster(); + // The cluster manager ensures a trailing "/" + Assert.strictEqual(cluster, endpoint + "/"); + + Svc.Prefs.resetBranch(""); +}); + +function run_test() { + initTestLogging(); + run_next_test(); +} diff --git a/services/sync/tests/unit/test_fxa_startOver.js b/services/sync/tests/unit/test_fxa_startOver.js index 629379648..e27d86ea0 100644 --- a/services/sync/tests/unit/test_fxa_startOver.js +++ b/services/sync/tests/unit/test_fxa_startOver.js @@ -1,63 +1,63 @@ -/* Any copyright is dedicated to the Public Domain. - * http://creativecommons.org/publicdomain/zero/1.0/ */ - -Cu.import("resource://testing-common/services/sync/utils.js"); -Cu.import("resource://services-sync/identity.js"); -Cu.import("resource://services-sync/browserid_identity.js"); -Cu.import("resource://services-sync/service.js"); - -function run_test() { - initTestLogging("Trace"); - run_next_test(); -} - -add_task(function* test_startover() { - let oldValue = Services.prefs.getBoolPref("services.sync-testing.startOverKeepIdentity", true); - Services.prefs.setBoolPref("services.sync-testing.startOverKeepIdentity", false); - - ensureLegacyIdentityManager(); - yield configureIdentity({username: "johndoe"}); - - // The boolean flag on the xpcom service should reflect a legacy provider. - let xps = Cc["@mozilla.org/weave/service;1"] - .getService(Components.interfaces.nsISupports) - .wrappedJSObject; - do_check_false(xps.fxAccountsEnabled); - - // we expect the "legacy" provider (but can't instanceof that, as BrowserIDManager - // extends it) - do_check_false(Service.identity instanceof BrowserIDManager); - - Service.serverURL = "https://localhost/"; - Service.clusterURL = Service.serverURL; - - Service.login(); - // We should have a cluster URL - do_check_true(Service.clusterURL.length > 0); - - // remember some stuff so we can reset it after. - let oldIdentity = Service.identity; - let oldClusterManager = Service._clusterManager; - let deferred = Promise.defer(); - Services.obs.addObserver(function observeStartOverFinished() { - Services.obs.removeObserver(observeStartOverFinished, "weave:service:start-over:finish"); - deferred.resolve(); - }, "weave:service:start-over:finish", false); - - Service.startOver(); - yield deferred.promise; // wait for the observer to fire. - - // the xpcom service should indicate FxA is enabled. - do_check_true(xps.fxAccountsEnabled); - // should have swapped identities. - do_check_true(Service.identity instanceof BrowserIDManager); - // should have clobbered the cluster URL - do_check_eq(Service.clusterURL, ""); - - // we should have thrown away the old identity provider and cluster manager. - do_check_neq(oldIdentity, Service.identity); - do_check_neq(oldClusterManager, Service._clusterManager); - - // reset the world. - Services.prefs.setBoolPref("services.sync-testing.startOverKeepIdentity", oldValue); -}); +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +Cu.import("resource://testing-common/services/sync/utils.js"); +Cu.import("resource://services-sync/identity.js"); +Cu.import("resource://services-sync/browserid_identity.js"); +Cu.import("resource://services-sync/service.js"); + +function run_test() { + initTestLogging("Trace"); + run_next_test(); +} + +add_task(function* test_startover() { + let oldValue = Services.prefs.getBoolPref("services.sync-testing.startOverKeepIdentity", true); + Services.prefs.setBoolPref("services.sync-testing.startOverKeepIdentity", false); + + ensureLegacyIdentityManager(); + yield configureIdentity({username: "johndoe"}); + + // The boolean flag on the xpcom service should reflect a legacy provider. + let xps = Cc["@mozilla.org/weave/service;1"] + .getService(Components.interfaces.nsISupports) + .wrappedJSObject; + do_check_false(xps.fxAccountsEnabled); + + // we expect the "legacy" provider (but can't instanceof that, as BrowserIDManager + // extends it) + do_check_false(Service.identity instanceof BrowserIDManager); + + Service.serverURL = "https://localhost/"; + Service.clusterURL = Service.serverURL; + + Service.login(); + // We should have a cluster URL + do_check_true(Service.clusterURL.length > 0); + + // remember some stuff so we can reset it after. + let oldIdentity = Service.identity; + let oldClusterManager = Service._clusterManager; + let deferred = Promise.defer(); + Services.obs.addObserver(function observeStartOverFinished() { + Services.obs.removeObserver(observeStartOverFinished, "weave:service:start-over:finish"); + deferred.resolve(); + }, "weave:service:start-over:finish", false); + + Service.startOver(); + yield deferred.promise; // wait for the observer to fire. + + // the xpcom service should indicate FxA is enabled. + do_check_true(xps.fxAccountsEnabled); + // should have swapped identities. + do_check_true(Service.identity instanceof BrowserIDManager); + // should have clobbered the cluster URL + do_check_eq(Service.clusterURL, ""); + + // we should have thrown away the old identity provider and cluster manager. + do_check_neq(oldIdentity, Service.identity); + do_check_neq(oldClusterManager, Service._clusterManager); + + // reset the world. + Services.prefs.setBoolPref("services.sync-testing.startOverKeepIdentity", oldValue); +}); diff --git a/services/sync/tests/unit/test_healthreport.js b/services/sync/tests/unit/test_healthreport.js new file mode 100644 index 000000000..486320b6a --- /dev/null +++ b/services/sync/tests/unit/test_healthreport.js @@ -0,0 +1,194 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +Cu.import("resource://gre/modules/Metrics.jsm", this); +Cu.import("resource://gre/modules/Preferences.jsm", this); +Cu.import("resource://gre/modules/Promise.jsm", this); +Cu.import("resource://services-sync/main.js", this); +Cu.import("resource://services-sync/healthreport.jsm", this); +Cu.import("resource://testing-common/services/common/logging.js", this); +Cu.import("resource://testing-common/services/healthreport/utils.jsm", this); + +function run_test() { + initTestLogging(); + + run_next_test(); +} + +add_task(function test_constructor() { + let provider = new SyncProvider(); +}); + +// Provider can initialize and de-initialize properly. +add_task(function* test_init() { + let storage = yield Metrics.Storage("init"); + let provider = new SyncProvider(); + yield provider.init(storage); + yield provider.shutdown(); + yield storage.close(); +}); + +add_task(function* test_collect() { + let storage = yield Metrics.Storage("collect"); + let provider = new SyncProvider(); + yield provider.init(storage); + + // Initially nothing should be configured. + let now = new Date(); + yield provider.collectDailyData(); + + let m = provider.getMeasurement("sync", 1); + let values = yield m.getValues(); + Assert.equal(values.days.size, 1); + Assert.ok(values.days.hasDay(now)); + let day = values.days.getDay(now); + Assert.ok(day.has("enabled")); + Assert.ok(day.has("activeProtocol")); + Assert.ok(day.has("preferredProtocol")); + Assert.equal(day.get("enabled"), 0); + Assert.equal(day.get("preferredProtocol"), "1.5"); + Assert.equal(day.get("activeProtocol"), "1.5", + "Protocol without setup should be FX Accounts version."); + + // Now check for old Sync setup. + let branch = new Preferences("services.sync."); + branch.set("username", "foo"); + branch.reset("fxaccounts.enabled"); + yield provider.collectDailyData(); + values = yield m.getValues(); + Assert.equal(values.days.getDay(now).get("activeProtocol"), "1.1", + "Protocol with old Sync setup is correct."); + + Assert.equal(Weave.Status.__authManager, undefined, "Detect code changes"); + + // Let's enable Sync so we can get more useful data. + // We need to do this because the FHR probe only records more info if Sync + // is configured properly. + Weave.Service.identity.account = "johndoe"; + Weave.Service.identity.basicPassword = "ilovejane"; + Weave.Service.identity.syncKey = Weave.Utils.generatePassphrase(); + Weave.Service.clusterURL = "http://localhost/"; + Assert.equal(Weave.Status.checkSetup(), Weave.STATUS_OK); + + yield provider.collectDailyData(); + values = yield m.getValues(); + day = values.days.getDay(now); + Assert.equal(day.get("enabled"), 1); + + // An empty account should have 1 device: us. + let dm = provider.getMeasurement("devices", 1); + values = yield dm.getValues(); + Assert.ok(values.days.hasDay(now)); + day = values.days.getDay(now); + Assert.equal(day.size, 1); + let engine = Weave.Service.clientsEngine; + Assert.ok(engine); + Assert.ok(day.has(engine.localType)); + Assert.equal(day.get(engine.localType), 1); + + // Add some devices and ensure they show up. + engine._store._remoteClients["id1"] = {type: "mobile"}; + engine._store._remoteClients["id2"] = {type: "tablet"}; + engine._store._remoteClients["id3"] = {type: "mobile"}; + + yield provider.collectDailyData(); + values = yield dm.getValues(); + day = values.days.getDay(now); + + let expected = { + "foobar": 0, + "tablet": 1, + "mobile": 2, + "desktop": 0, + }; + + for (let type in expected) { + let count = expected[type]; + + if (engine.localType == type) { + count++; + } + + if (!count) { + Assert.ok(!day.has(type)); + } else { + Assert.ok(day.has(type)); + Assert.equal(day.get(type), count); + } + } + + engine._store._remoteClients = {}; + + yield provider.shutdown(); + yield storage.close(); +}); + +add_task(function* test_sync_events() { + let storage = yield Metrics.Storage("sync_events"); + let provider = new SyncProvider(); + yield provider.init(storage); + + let m = provider.getMeasurement("sync", 1); + + for (let i = 0; i < 5; i++) { + Services.obs.notifyObservers(null, "weave:service:sync:start", null); + } + + for (let i = 0; i < 3; i++) { + Services.obs.notifyObservers(null, "weave:service:sync:finish", null); + } + + for (let i = 0; i < 2; i++) { + Services.obs.notifyObservers(null, "weave:service:sync:error", null); + } + + // Wait for storage to complete. + yield m.storage.enqueueOperation(() => { + return Promise.resolve(); + }); + + let values = yield m.getValues(); + let now = new Date(); + Assert.ok(values.days.hasDay(now)); + let day = values.days.getDay(now); + + Assert.ok(day.has("syncStart")); + Assert.ok(day.has("syncSuccess")); + Assert.ok(day.has("syncError")); + Assert.equal(day.get("syncStart"), 5); + Assert.equal(day.get("syncSuccess"), 3); + Assert.equal(day.get("syncError"), 2); + + yield provider.shutdown(); + yield storage.close(); +}); + +add_task(function* test_healthreporter_json() { + let reporter = yield getHealthReporter("healthreporter_json"); + yield reporter.init(); + try { + yield reporter._providerManager.registerProvider(new SyncProvider()); + yield reporter.collectMeasurements(); + let payload = yield reporter.getJSONPayload(true); + let now = new Date(); + let today = reporter._formatDate(now); + + Assert.ok(today in payload.data.days); + let day = payload.data.days[today]; + + Assert.ok("org.mozilla.sync.sync" in day); + Assert.ok("org.mozilla.sync.devices" in day); + + let devices = day["org.mozilla.sync.devices"]; + let engine = Weave.Service.clientsEngine; + Assert.ok(engine); + let type = engine.localType; + Assert.ok(type); + Assert.ok(type in devices); + Assert.equal(devices[type], 1); + } finally { + reporter._shutdown(); + } +}); diff --git a/services/sync/tests/unit/test_healthreport_migration.js b/services/sync/tests/unit/test_healthreport_migration.js new file mode 100644 index 000000000..23f756748 --- /dev/null +++ b/services/sync/tests/unit/test_healthreport_migration.js @@ -0,0 +1,155 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +Cu.import("resource://gre/modules/Metrics.jsm", this); +Cu.import("resource://gre/modules/Preferences.jsm", this); +Cu.import("resource://gre/modules/Promise.jsm", this); +Cu.import("resource://services-sync/healthreport.jsm", this); +Cu.import("resource://services-sync/FxaMigrator.jsm", this); +Cu.import("resource://testing-common/services/common/logging.js", this); +Cu.import("resource://testing-common/services/healthreport/utils.jsm", this); + + +function run_test() { + initTestLogging(); + + run_next_test(); +} + +add_task(function* test_no_data() { + let storage = yield Metrics.Storage("collect"); + let provider = new SyncProvider(); + yield provider.init(storage); + + try { + // Initially nothing should be configured. + let now = new Date(); + yield provider.collectDailyData(); + + let m = provider.getMeasurement("migration", 1); + let values = yield m.getValues(); + Assert.equal(values.days.size, 0); + Assert.ok(!values.days.hasDay(now)); + } finally { + yield provider.shutdown(); + yield storage.close(); + } +}); + +function checkCorrectStateRecorded(provider, state) { + // Wait for storage to complete. + yield m.storage.enqueueOperation(() => { + return Promise.resolve(); + }); + + let m = provider.getMeasurement("migration", 1); + let values = yield m.getValues(); + Assert.equal(values.days.size, 1); + Assert.ok(values.days.hasDay(now)); + let day = values.days.getDay(now); + + Assert.ok(day.has("state")); + Assert.equal(day.get("state"), state); +} + +add_task(function* test_state() { + let storage = yield Metrics.Storage("collect"); + let provider = new SyncProvider(); + yield provider.init(storage); + + try { + // Initially nothing should be configured. + let now = new Date(); + + // We record both a "user" and "internal" state in the same field. + // So simulate a "user" state first. + Services.obs.notifyObservers(null, "fxa-migration:state-changed", + fxaMigrator.STATE_USER_FXA_VERIFIED); + checkCorrectStateRecorded(provider, fxaMigrator.STATE_USER_FXA_VERIFIED); + + // And an internal state. + Services.obs.notifyObservers(null, "fxa-migration:internal-state-changed", + fxaMigrator.STATE_INTERNAL_WAITING_SYNC_COMPLETE); + checkCorrectStateRecorded(provider, fxaMigrator.STATE_INTERNAL_WAITING_SYNC_COMPLETE); + } finally { + yield provider.shutdown(); + yield storage.close(); + } +}); + +add_task(function* test_flags() { + let storage = yield Metrics.Storage("collect"); + let provider = new SyncProvider(); + yield provider.init(storage); + + try { + // Initially nothing should be configured. + let now = new Date(); + + let m = provider.getMeasurement("migration", 1); + + let record = function*(what) { + Services.obs.notifyObservers(null, "fxa-migration:internal-telemetry", what); + // Wait for storage to complete. + yield m.storage.enqueueOperation(Promise.resolve); + let values = yield m.getValues(); + Assert.equal(values.days.size, 1); + return values.days.getDay(now); + } + + let values = yield m.getValues(); + Assert.equal(values.days.size, 1); + let day = values.days.getDay(now); + Assert.ok(!day.has(fxaMigrator.TELEMETRY_ACCEPTED)); + Assert.ok(!day.has(fxaMigrator.TELEMETRY_DECLINED)); + Assert.ok(!day.has(fxaMigrator.TELEMETRY_UNLINKED)); + + // let's send an unknown value to ensure our error mitigation works. + day = yield record("unknown"); + Assert.ok(!day.has(fxaMigrator.TELEMETRY_ACCEPTED)); + Assert.ok(!day.has(fxaMigrator.TELEMETRY_DECLINED)); + Assert.ok(!day.has(fxaMigrator.TELEMETRY_UNLINKED)); + + // record an fxaMigrator.TELEMETRY_ACCEPTED state. + day = yield record(fxaMigrator.TELEMETRY_ACCEPTED); + Assert.ok(day.has(fxaMigrator.TELEMETRY_ACCEPTED)); + Assert.ok(!day.has(fxaMigrator.TELEMETRY_DECLINED)); + Assert.ok(!day.has(fxaMigrator.TELEMETRY_UNLINKED)); + Assert.equal(day.get(fxaMigrator.TELEMETRY_ACCEPTED), 1); + + // and again - it should get 2. + day = yield record(fxaMigrator.TELEMETRY_ACCEPTED); + Assert.equal(day.get(fxaMigrator.TELEMETRY_ACCEPTED), 2); + + // record fxaMigrator.TELEMETRY_DECLINED - also a counter. + day = yield record(fxaMigrator.TELEMETRY_DECLINED); + Assert.ok(day.has(fxaMigrator.TELEMETRY_ACCEPTED)); + Assert.ok(day.has(fxaMigrator.TELEMETRY_DECLINED)); + Assert.ok(!day.has(fxaMigrator.TELEMETRY_UNLINKED)); + Assert.equal(day.get(fxaMigrator.TELEMETRY_ACCEPTED), 2); + Assert.equal(day.get(fxaMigrator.TELEMETRY_DECLINED), 1); + + day = yield record(fxaMigrator.TELEMETRY_DECLINED); + Assert.ok(day.has(fxaMigrator.TELEMETRY_ACCEPTED)); + Assert.ok(day.has(fxaMigrator.TELEMETRY_DECLINED)); + Assert.ok(!day.has(fxaMigrator.TELEMETRY_UNLINKED)); + Assert.equal(day.get(fxaMigrator.TELEMETRY_ACCEPTED), 2); + Assert.equal(day.get(fxaMigrator.TELEMETRY_DECLINED), 2); + + // and fxaMigrator.TELEMETRY_UNLINKED - this is conceptually a "daily bool". + // (ie, it's DAILY_LAST_NUMERIC_FIELD and only ever has |1| written to it) + day = yield record(fxaMigrator.TELEMETRY_UNLINKED); + Assert.ok(day.has(fxaMigrator.TELEMETRY_ACCEPTED)); + Assert.ok(day.has(fxaMigrator.TELEMETRY_DECLINED)); + Assert.ok(day.has(fxaMigrator.TELEMETRY_UNLINKED)); + Assert.equal(day.get(fxaMigrator.TELEMETRY_UNLINKED), 1); + // and doing it again still leaves us with |1| + day = yield record(fxaMigrator.TELEMETRY_UNLINKED); + Assert.equal(day.get(fxaMigrator.TELEMETRY_UNLINKED), 1); + } finally { + yield provider.shutdown(); + yield storage.close(); + } +}); diff --git a/services/sync/tests/unit/test_history_store.js b/services/sync/tests/unit/test_history_store.js index 207b621e0..2381f103d 100644 --- a/services/sync/tests/unit/test_history_store.js +++ b/services/sync/tests/unit/test_history_store.js @@ -68,12 +68,12 @@ function ensureThrows(func) { }; } -var store = new HistoryEngine(Service)._store; +let store = new HistoryEngine(Service)._store; function applyEnsureNoFailures(records) { do_check_eq(store.applyIncomingBatch(records).length, 0); } -var fxuri, fxguid, tburi, tbguid; +let fxuri, fxguid, tburi, tbguid; function run_test() { initTestLogging("Trace"); @@ -189,8 +189,8 @@ add_test(function test_invalid_records() { .DBConnection; let stmt = connection.createAsyncStatement( "INSERT INTO moz_places " - + "(url, url_hash, title, rev_host, visit_count, last_visit_date) " - + "VALUES ('invalid-uri', hash('invalid-uri'), 'Invalid URI', '.', 1, " + TIMESTAMP3 + ")" + + "(url, title, rev_host, visit_count, last_visit_date) " + + "VALUES ('invalid-uri', 'Invalid URI', '.', 1, " + TIMESTAMP3 + ")" ); Async.querySpinningly(stmt); stmt.finalize(); @@ -198,7 +198,7 @@ add_test(function test_invalid_records() { stmt = connection.createAsyncStatement( "INSERT INTO moz_historyvisits " + "(place_id, visit_date, visit_type, session) " - + "VALUES ((SELECT id FROM moz_places WHERE url_hash = hash('invalid-uri') AND url = 'invalid-uri'), " + + "VALUES ((SELECT id FROM moz_places WHERE url = 'invalid-uri'), " + TIMESTAMP3 + ", " + Ci.nsINavHistoryService.TRANSITION_TYPED + ", 1)" ); Async.querySpinningly(stmt); @@ -226,7 +226,7 @@ add_test(function test_invalid_records() { type: Ci.nsINavHistoryService.TRANSITION_EMBED}]} ]); - _("Make sure we handle records with invalid visit codes or visit dates, gracefully ignoring those visits."); + _("Make sure we report records with invalid visits, gracefully handle non-integer dates."); let no_date_visit_guid = Utils.makeGUID(); let no_type_visit_guid = Utils.makeGUID(); let invalid_type_visit_guid = Utils.makeGUID(); @@ -235,11 +235,11 @@ add_test(function test_invalid_records() { {id: no_date_visit_guid, histUri: "http://no.date.visit/", title: "Visit has no date", - visits: [{type: Ci.nsINavHistoryService.TRANSITION_EMBED}]}, + visits: [{date: TIMESTAMP3}]}, {id: no_type_visit_guid, histUri: "http://no.type.visit/", title: "Visit has no type", - visits: [{date: TIMESTAMP3}]}, + visits: [{type: Ci.nsINavHistoryService.TRANSITION_EMBED}]}, {id: invalid_type_visit_guid, histUri: "http://invalid.type.visit/", title: "Visit has invalid type", @@ -251,7 +251,14 @@ add_test(function test_invalid_records() { visits: [{date: 1234.567, type: Ci.nsINavHistoryService.TRANSITION_EMBED}]} ]); - do_check_eq(failed.length, 0); + do_check_eq(failed.length, 3); + failed.sort(); + let expected = [no_date_visit_guid, + no_type_visit_guid, + invalid_type_visit_guid].sort(); + for (let i = 0; i < expected.length; i++) { + do_check_eq(failed[i], expected[i]); + } _("Make sure we handle records with javascript: URLs gracefully."); applyEnsureNoFailures([ diff --git a/services/sync/tests/unit/test_history_tracker.js b/services/sync/tests/unit/test_history_tracker.js index 5ed022fb0..ca1090b79 100644 --- a/services/sync/tests/unit/test_history_tracker.js +++ b/services/sync/tests/unit/test_history_tracker.js @@ -22,13 +22,13 @@ function onScoreUpdated(callback) { Service.engineManager.clear(); Service.engineManager.register(HistoryEngine); -var engine = Service.engineManager.get("history"); -var tracker = engine._tracker; +let engine = Service.engineManager.get("history"); +let tracker = engine._tracker; // Don't write out by default. tracker.persistChangedIDs = false; -var _counter = 0; +let _counter = 0; function addVisit() { let uriString = "http://getfirefox.com/" + _counter++; let uri = Utils.makeURI(uriString); diff --git a/services/sync/tests/unit/test_hmac_error.js b/services/sync/tests/unit/test_hmac_error.js index 272c0de47..e41ff3797 100644 --- a/services/sync/tests/unit/test_hmac_error.js +++ b/services/sync/tests/unit/test_hmac_error.js @@ -8,7 +8,7 @@ Cu.import("resource://testing-common/services/sync/rotaryengine.js"); Cu.import("resource://testing-common/services/sync/utils.js"); // Track HMAC error counts. -var hmacErrorCount = 0; +let hmacErrorCount = 0; (function () { let hHE = Service.handleHMACEvent; Service.handleHMACEvent = function () { @@ -49,7 +49,7 @@ function shared_setup() { return [engine, rotaryColl, clientsColl, keysWBO, global]; } -add_task(function *hmac_error_during_404() { +add_test(function hmac_error_during_404() { _("Attempt to replicate the HMAC error setup."); let [engine, rotaryColl, clientsColl, keysWBO, global] = shared_setup(); @@ -83,14 +83,13 @@ add_task(function *hmac_error_during_404() { try { _("Syncing."); - yield sync_and_validate_telem(); - + Service.sync(); _("Partially resetting client, as if after a restart, and forcing redownload."); Service.collectionKeys.clear(); engine.lastSync = 0; // So that we redownload records. key404Counter = 1; _("---------------------------"); - yield sync_and_validate_telem(); + Service.sync(); _("---------------------------"); // Two rotary items, one client record... no errors. @@ -98,7 +97,7 @@ add_task(function *hmac_error_during_404() { } finally { Svc.Prefs.resetBranch(""); Service.recordManager.clearCache(); - yield new Promise(resolve => server.stop(resolve)); + server.stop(run_next_test); } }); diff --git a/services/sync/tests/unit/test_identity_manager.js b/services/sync/tests/unit/test_identity_manager.js index 1ac198ade..97dace95f 100644 --- a/services/sync/tests/unit/test_identity_manager.js +++ b/services/sync/tests/unit/test_identity_manager.js @@ -5,7 +5,7 @@ Cu.import("resource://services-sync/constants.js"); Cu.import("resource://services-sync/identity.js"); Cu.import("resource://services-sync/util.js"); -var identity = new IdentityManager(); +let identity = new IdentityManager(); function run_test() { initTestLogging("Trace"); diff --git a/services/sync/tests/unit/test_interval_triggers.js b/services/sync/tests/unit/test_interval_triggers.js index eca5ec289..0f355e636 100644 --- a/services/sync/tests/unit/test_interval_triggers.js +++ b/services/sync/tests/unit/test_interval_triggers.js @@ -10,13 +10,8 @@ Cu.import("resource://testing-common/services/sync/utils.js"); Svc.DefaultPrefs.set("registerEngines", ""); Cu.import("resource://services-sync/service.js"); -var scheduler = Service.scheduler; -var 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 = id => {}; +let scheduler = Service.scheduler; +let clientsEngine = Service.clientsEngine; function promiseStopServer(server) { let deferred = Promise.defer(); @@ -46,7 +41,7 @@ function sync_httpd_setup() { }); } -function* setUp(server) { +function setUp(server) { yield configureIdentity({username: "johndoe"}); Service.serverURL = server.baseURI + "/"; Service.clusterURL = server.baseURI + "/"; @@ -65,7 +60,7 @@ function run_test() { run_next_test(); } -add_identity_test(this, function* test_successful_sync_adjustSyncInterval() { +add_identity_test(this, function test_successful_sync_adjustSyncInterval() { _("Test successful sync calling adjustSyncInterval"); let syncSuccesses = 0; function onSyncFinish() { @@ -164,7 +159,7 @@ add_identity_test(this, function* test_successful_sync_adjustSyncInterval() { yield promiseStopServer(server); }); -add_identity_test(this, function* test_unsuccessful_sync_adjustSyncInterval() { +add_identity_test(this, function test_unsuccessful_sync_adjustSyncInterval() { _("Test unsuccessful sync calling adjustSyncInterval"); let syncFailures = 0; @@ -269,7 +264,7 @@ add_identity_test(this, function* test_unsuccessful_sync_adjustSyncInterval() { yield promiseStopServer(server); }); -add_identity_test(this, function* test_back_triggers_sync() { +add_identity_test(this, function test_back_triggers_sync() { let server = sync_httpd_setup(); yield setUp(server); @@ -301,7 +296,7 @@ add_identity_test(this, function* test_back_triggers_sync() { yield deferred.promise; }); -add_identity_test(this, function* test_adjust_interval_on_sync_error() { +add_identity_test(this, function test_adjust_interval_on_sync_error() { let server = sync_httpd_setup(); yield setUp(server); @@ -332,7 +327,7 @@ add_identity_test(this, function* test_adjust_interval_on_sync_error() { yield promiseStopServer(server); }); -add_identity_test(this, function* test_bug671378_scenario() { +add_identity_test(this, function test_bug671378_scenario() { // Test scenario similar to bug 671378. This bug appeared when a score // update occurred that wasn't large enough to trigger a sync so // scheduleNextSync() was called without a time interval parameter, diff --git a/services/sync/tests/unit/test_jpakeclient.js b/services/sync/tests/unit/test_jpakeclient.js index 783edb460..ff13c5716 100644 --- a/services/sync/tests/unit/test_jpakeclient.js +++ b/services/sync/tests/unit/test_jpakeclient.js @@ -38,8 +38,8 @@ function new_channel() { return cid; } -var server; -var channels = {}; // Map channel -> ServerChannel object +let server; +let channels = {}; // Map channel -> ServerChannel object function server_new_channel(request, response) { check_headers(request); let cid = new_channel(); @@ -48,7 +48,7 @@ function server_new_channel(request, response) { response.bodyOutputStream.write(body, body.length); } -var error_report; +let error_report; function server_report(request, response) { check_headers(request); @@ -68,7 +68,7 @@ function server_report(request, response) { } // Hook for test code. -var hooks = {}; +let hooks = {}; function initHooks() { hooks.onGET = function onGET(request) {}; } @@ -146,7 +146,7 @@ ServerChannel.prototype = { /** * Controller that throws for everything. */ -var BaseController = { +let BaseController = { displayPIN: function displayPIN() { do_throw("displayPIN() shouldn't have been called!"); }, @@ -369,7 +369,7 @@ add_test(function test_wrongPIN() { displayPIN: function displayPIN(pin) { this.cid = pin.slice(JPAKE_LENGTH_SECRET); let secret = pin.slice(0, JPAKE_LENGTH_SECRET); - secret = Array.prototype.slice.call(secret).reverse().join(""); + secret = [char for each (char in secret)].reverse().join(""); let new_pin = secret + this.cid; _("Received PIN " + pin + ", but I'm entering " + new_pin); diff --git a/services/sync/tests/unit/test_keys.js b/services/sync/tests/unit/test_keys.js index a828b619c..6a2fdd027 100644 --- a/services/sync/tests/unit/test_keys.js +++ b/services/sync/tests/unit/test_keys.js @@ -7,7 +7,7 @@ Cu.import("resource://services-sync/keys.js"); Cu.import("resource://services-sync/record.js"); Cu.import("resource://services-sync/util.js"); -var collectionKeys = new CollectionKeyManager(); +let collectionKeys = new CollectionKeyManager(); function sha256HMAC(message, key) { let h = Utils.makeHMACHasher(Ci.nsICryptoHMAC.SHA256, key); diff --git a/services/sync/tests/unit/test_load_modules.js b/services/sync/tests/unit/test_load_modules.js index 0b222520c..4f561bae6 100644 --- a/services/sync/tests/unit/test_load_modules.js +++ b/services/sync/tests/unit/test_load_modules.js @@ -9,7 +9,6 @@ const modules = [ "engines/addons.js", "engines/bookmarks.js", "engines/clients.js", - "engines/extension-storage.js", "engines/forms.js", "engines/history.js", "engines/passwords.js", @@ -20,6 +19,7 @@ const modules = [ "jpakeclient.js", "keys.js", "main.js", + "notifications.js", "policies.js", "record.js", "resource.js", diff --git a/services/sync/tests/unit/test_node_reassignment.js b/services/sync/tests/unit/test_node_reassignment.js index 66d21b6f1..7fe5ed7ed 100644 --- a/services/sync/tests/unit/test_node_reassignment.js +++ b/services/sync/tests/unit/test_node_reassignment.js @@ -23,7 +23,7 @@ function run_test() { Log.repository.getLogger("Sync.Service").level = Log.Level.Trace; Log.repository.getLogger("Sync.SyncScheduler").level = Log.Level.Trace; initTestLogging(); - validate_all_future_pings(); + ensureLegacyIdentityManager(); Service.engineManager.register(RotaryEngine); @@ -92,12 +92,11 @@ function prepareServer() { function getReassigned() { try { return Services.prefs.getBoolPref("services.sync.lastSyncReassigned"); + } catch (ex if (ex.result == Cr.NS_ERROR_UNEXPECTED)) { + return false; } catch (ex) { - if (ex.result == Cr.NS_ERROR_UNEXPECTED) { - return false; - } do_throw("Got exception retrieving lastSyncReassigned: " + - Log.exceptionStr(ex)); + Utils.exceptionStr(ex)); } } @@ -107,7 +106,7 @@ function getReassigned() { * Runs `between` between the two. This can be used to undo deliberate failure * setup, detach observers, etc. */ -function* syncAndExpectNodeReassignment(server, firstNotification, between, +function syncAndExpectNodeReassignment(server, firstNotification, between, secondNotification, url) { let deferred = Promise.defer(); function onwards() { @@ -161,7 +160,7 @@ function* syncAndExpectNodeReassignment(server, firstNotification, between, yield deferred.promise; } -add_task(function* test_momentary_401_engine() { +add_task(function test_momentary_401_engine() { _("Test a failure for engine URLs that's resolved by reassignment."); let server = yield prepareServer(); let john = server.user("johndoe"); @@ -213,7 +212,7 @@ add_task(function* test_momentary_401_engine() { }); // This test ends up being a failing fetch *after we're already logged in*. -add_task(function* test_momentary_401_info_collections() { +add_task(function test_momentary_401_info_collections() { _("Test a failure for info/collections that's resolved by reassignment."); let server = yield prepareServer(); @@ -236,7 +235,7 @@ add_task(function* test_momentary_401_info_collections() { Service.infoURL); }); -add_task(function* test_momentary_401_storage_loggedin() { +add_task(function test_momentary_401_storage_loggedin() { _("Test a failure for any storage URL, not just engine parts. " + "Resolved by reassignment."); let server = yield prepareServer(); @@ -261,7 +260,7 @@ add_task(function* test_momentary_401_storage_loggedin() { Service.storageURL + "meta/global"); }); -add_task(function* test_momentary_401_storage_loggedout() { +add_task(function test_momentary_401_storage_loggedout() { _("Test a failure for any storage URL, not just engine parts. " + "Resolved by reassignment."); let server = yield prepareServer(); @@ -283,7 +282,7 @@ add_task(function* test_momentary_401_storage_loggedout() { Service.storageURL + "meta/global"); }); -add_task(function* test_loop_avoidance_storage() { +add_task(function test_loop_avoidance_storage() { _("Test that a repeated failure doesn't result in a sync loop " + "if node reassignment cannot resolve the failure."); @@ -383,7 +382,7 @@ add_task(function* test_loop_avoidance_storage() { yield deferred.promise; }); -add_task(function* test_loop_avoidance_engine() { +add_task(function test_loop_avoidance_engine() { _("Test that a repeated 401 in an engine doesn't result in a sync loop " + "if node reassignment cannot resolve the failure."); let server = yield prepareServer(); diff --git a/services/sync/tests/unit/test_notifications.js b/services/sync/tests/unit/test_notifications.js new file mode 100644 index 000000000..9d6da1d2d --- /dev/null +++ b/services/sync/tests/unit/test_notifications.js @@ -0,0 +1,32 @@ +Cu.import("resource://services-sync/notifications.js"); + +function run_test() { + var logStats = initTestLogging("Info"); + + var blah = 0; + + function callback(i) { + blah = i; + } + + let button = new NotificationButton("label", "accessKey", callback); + + button.callback(5); + + do_check_eq(blah, 5); + do_check_eq(logStats.errorsLogged, 0); + + function badCallback() { + throw new Error("oops"); + } + + button = new NotificationButton("label", "accessKey", badCallback); + + try { + button.callback(); + } catch (e) { + do_check_eq(e.message, "oops"); + } + + do_check_eq(logStats.errorsLogged, 1); +} diff --git a/services/sync/tests/unit/test_password_store.js b/services/sync/tests/unit/test_password_store.js index d232d5e63..c56901d79 100644 --- a/services/sync/tests/unit/test_password_store.js +++ b/services/sync/tests/unit/test_password_store.js @@ -5,137 +5,6 @@ Cu.import("resource://services-sync/engines/passwords.js"); Cu.import("resource://services-sync/service.js"); Cu.import("resource://services-sync/util.js"); - -function checkRecord(name, record, expectedCount, timeCreated, - expectedTimeCreated, timePasswordChanged, - expectedTimePasswordChanged, recordIsUpdated) { - let engine = Service.engineManager.get("passwords"); - let store = engine._store; - - let count = {}; - let logins = Services.logins.findLogins(count, record.hostname, - record.formSubmitURL, null); - - _("Record" + name + ":" + JSON.stringify(logins)); - _("Count" + name + ":" + count.value); - - do_check_eq(count.value, expectedCount); - - if (expectedCount > 0) { - do_check_true(!!store.getAllIDs()[record.id]); - let stored_record = logins[0].QueryInterface(Ci.nsILoginMetaInfo); - - if (timeCreated !== undefined) { - do_check_eq(stored_record.timeCreated, expectedTimeCreated); - } - - if (timePasswordChanged !== undefined) { - if (recordIsUpdated) { - do_check_true(stored_record.timePasswordChanged >= expectedTimePasswordChanged); - } else { - do_check_eq(stored_record.timePasswordChanged, expectedTimePasswordChanged); - } - return stored_record.timePasswordChanged; - } - } else { - do_check_true(!store.getAllIDs()[record.id]); - } -} - - -function changePassword(name, hostname, password, expectedCount, timeCreated, - expectedTimeCreated, timePasswordChanged, - expectedTimePasswordChanged, insert, recordIsUpdated) { - - const BOGUS_GUID = "zzzzzz" + hostname; - - let record = {id: BOGUS_GUID, - hostname: hostname, - formSubmitURL: hostname, - username: "john", - password: password, - usernameField: "username", - passwordField: "password"}; - - if (timeCreated !== undefined) { - record.timeCreated = timeCreated; - } - - if (timePasswordChanged !== undefined) { - record.timePasswordChanged = timePasswordChanged; - } - - - let engine = Service.engineManager.get("passwords"); - let store = engine._store; - - if (insert) { - do_check_eq(store.applyIncomingBatch([record]).length, 0); - } - - return checkRecord(name, record, expectedCount, timeCreated, - expectedTimeCreated, timePasswordChanged, - expectedTimePasswordChanged, recordIsUpdated); - -} - - -function test_apply_records_with_times(hostname, timeCreated, timePasswordChanged) { - // The following record is going to be inserted in the store and it needs - // to be found there. Then its timestamps are going to be compared to - // the expected values. - changePassword(" ", hostname, "password", 1, timeCreated, timeCreated, - timePasswordChanged, timePasswordChanged, true); -} - - -function test_apply_multiple_records_with_times() { - // The following records are going to be inserted in the store and they need - // to be found there. Then their timestamps are going to be compared to - // the expected values. - changePassword("A", "http://foo.a.com", "password", 1, undefined, undefined, - undefined, undefined, true); - changePassword("B", "http://foo.b.com", "password", 1, 1000, 1000, undefined, - undefined, true); - changePassword("C", "http://foo.c.com", "password", 1, undefined, undefined, - 1000, 1000, true); - changePassword("D", "http://foo.d.com", "password", 1, 1000, 1000, 1000, - 1000, true); - - // The following records are not going to be inserted in the store and they - // are not going to be found there. - changePassword("NotInStoreA", "http://foo.aaaa.com", "password", 0, - undefined, undefined, undefined, undefined, false); - changePassword("NotInStoreB", "http://foo.bbbb.com", "password", 0, 1000, - 1000, undefined, undefined, false); - changePassword("NotInStoreC", "http://foo.cccc.com", "password", 0, - undefined, undefined, 1000, 1000, false); - changePassword("NotInStoreD", "http://foo.dddd.com", "password", 0, 1000, - 1000, 1000, 1000, false); -} - - -function test_apply_same_record_with_different_times() { - // The following record is going to be inserted multiple times in the store - // and it needs to be found there. Then its timestamps are going to be - // compared to the expected values. - var timePasswordChanged = 100; - timePasswordChanged = changePassword("A", "http://a.tn", "password", 1, 100, - 100, 100, timePasswordChanged, true); - timePasswordChanged = changePassword("A", "http://a.tn", "password", 1, 100, - 100, 800, timePasswordChanged, true, - true); - timePasswordChanged = changePassword("A", "http://a.tn", "password", 1, 500, - 100, 800, timePasswordChanged, true, - true); - timePasswordChanged = changePassword("A", "http://a.tn", "password2", 1, 500, - 100, 1536213005222, timePasswordChanged, - true, true); - timePasswordChanged = changePassword("A", "http://a.tn", "password2", 1, 500, - 100, 800, timePasswordChanged, true, true); -} - - function run_test() { initTestLogging("Trace"); Log.repository.getLogger("Sync.Engine.Passwords").level = Log.Level.Trace; @@ -161,9 +30,12 @@ function run_test() { let engine = Service.engineManager.get("passwords"); let store = engine._store; + function applyEnsureNoFailures(records) { + do_check_eq(store.applyIncomingBatch(records).length, 0); + } try { - do_check_eq(store.applyIncomingBatch([recordA, recordB]).length, 0); + applyEnsureNoFailures([recordA, recordB]); // Only the good record makes it to Services.logins. let badCount = {}; @@ -183,17 +55,7 @@ function run_test() { do_check_true(!!store.getAllIDs()[BOGUS_GUID_B]); do_check_true(!store.getAllIDs()[BOGUS_GUID_A]); - - test_apply_records_with_times("http://afoo.baz.com", undefined, undefined); - test_apply_records_with_times("http://bfoo.baz.com", 1000, undefined); - test_apply_records_with_times("http://cfoo.baz.com", undefined, 2000); - test_apply_records_with_times("http://dfoo.baz.com", 1000, 2000); - - test_apply_multiple_records_with_times(); - - test_apply_same_record_with_different_times(); - } finally { store.wipe(); } -} \ No newline at end of file +} diff --git a/services/sync/tests/unit/test_password_tracker.js b/services/sync/tests/unit/test_password_tracker.js index 09ca141a6..ddfc524ab 100644 --- a/services/sync/tests/unit/test_password_tracker.js +++ b/services/sync/tests/unit/test_password_tracker.js @@ -8,9 +8,9 @@ Cu.import("resource://services-sync/service.js"); Cu.import("resource://services-sync/util.js"); Service.engineManager.register(PasswordEngine); -var engine = Service.engineManager.get("passwords"); -var store = engine._store; -var tracker = engine._tracker; +let engine = Service.engineManager.get("passwords"); +let store = engine._store; +let tracker = engine._tracker; // Don't do asynchronous writes. tracker.persistChangedIDs = false; diff --git a/services/sync/tests/unit/test_password_validator.js b/services/sync/tests/unit/test_password_validator.js deleted file mode 100644 index a4a148fbe..000000000 --- a/services/sync/tests/unit/test_password_validator.js +++ /dev/null @@ -1,158 +0,0 @@ -/* Any copyright is dedicated to the Public Domain. - http://creativecommons.org/publicdomain/zero/1.0/ */ - -Components.utils.import("resource://services-sync/engines/passwords.js"); - -function getDummyServerAndClient() { - return { - server: [ - { - id: "11111", - guid: "11111", - hostname: "https://www.11111.com", - formSubmitURL: "https://www.11111.com/login", - password: "qwerty123", - passwordField: "pass", - username: "foobar", - usernameField: "user", - httpRealm: null, - }, - { - id: "22222", - guid: "22222", - hostname: "https://www.22222.org", - formSubmitURL: "https://www.22222.org/login", - password: "hunter2", - passwordField: "passwd", - username: "baz12345", - usernameField: "user", - httpRealm: null, - }, - { - id: "33333", - guid: "33333", - hostname: "https://www.33333.com", - formSubmitURL: "https://www.33333.com/login", - password: "p4ssw0rd", - passwordField: "passwad", - username: "quux", - usernameField: "user", - httpRealm: null, - }, - ], - client: [ - { - id: "11111", - guid: "11111", - hostname: "https://www.11111.com", - formSubmitURL: "https://www.11111.com/login", - password: "qwerty123", - passwordField: "pass", - username: "foobar", - usernameField: "user", - httpRealm: null, - }, - { - id: "22222", - guid: "22222", - hostname: "https://www.22222.org", - formSubmitURL: "https://www.22222.org/login", - password: "hunter2", - passwordField: "passwd", - username: "baz12345", - usernameField: "user", - httpRealm: null, - - }, - { - id: "33333", - guid: "33333", - hostname: "https://www.33333.com", - formSubmitURL: "https://www.33333.com/login", - password: "p4ssw0rd", - passwordField: "passwad", - username: "quux", - usernameField: "user", - httpRealm: null, - } - ] - }; -} - - -add_test(function test_valid() { - let { server, client } = getDummyServerAndClient(); - let validator = new PasswordValidator(); - let { problemData, clientRecords, records, deletedRecords } = - validator.compareClientWithServer(client, server); - equal(clientRecords.length, 3); - equal(records.length, 3) - equal(deletedRecords.length, 0); - deepEqual(problemData, validator.emptyProblemData()); - - run_next_test(); -}); - -add_test(function test_missing() { - let validator = new PasswordValidator(); - { - let { server, client } = getDummyServerAndClient(); - - client.pop(); - - let { problemData, clientRecords, records, deletedRecords } = - validator.compareClientWithServer(client, server); - - equal(clientRecords.length, 2); - equal(records.length, 3) - equal(deletedRecords.length, 0); - - let expected = validator.emptyProblemData(); - expected.clientMissing.push("33333"); - deepEqual(problemData, expected); - } - { - let { server, client } = getDummyServerAndClient(); - - server.pop(); - - let { problemData, clientRecords, records, deletedRecords } = - validator.compareClientWithServer(client, server); - - equal(clientRecords.length, 3); - equal(records.length, 2) - equal(deletedRecords.length, 0); - - let expected = validator.emptyProblemData(); - expected.serverMissing.push("33333"); - deepEqual(problemData, expected); - } - - run_next_test(); -}); - - -add_test(function test_deleted() { - let { server, client } = getDummyServerAndClient(); - let deletionRecord = { id: "444444", guid: "444444", deleted: true }; - - server.push(deletionRecord); - let validator = new PasswordValidator(); - - let { problemData, clientRecords, records, deletedRecords } = - validator.compareClientWithServer(client, server); - - equal(clientRecords.length, 3); - equal(records.length, 4); - deepEqual(deletedRecords, [deletionRecord]); - - let expected = validator.emptyProblemData(); - deepEqual(problemData, expected); - - run_next_test(); -}); - - -function run_test() { - run_next_test(); -} diff --git a/services/sync/tests/unit/test_postqueue.js b/services/sync/tests/unit/test_postqueue.js deleted file mode 100644 index e60008a96..000000000 --- a/services/sync/tests/unit/test_postqueue.js +++ /dev/null @@ -1,455 +0,0 @@ -/* Any copyright is dedicated to the Public Domain. - * http://creativecommons.org/publicdomain/zero/1.0/ */ - -let { PostQueue } = Cu.import("resource://services-sync/record.js", {}); - -initTestLogging("Trace"); - -function makeRecord(nbytes) { - // make a string 2-bytes less - the added quotes will make it correct. - return { - toJSON: () => "x".repeat(nbytes-2), - } -} - -function makePostQueue(config, lastModTime, responseGenerator) { - let stats = { - posts: [], - } - let poster = (data, headers, batch, commit) => { - let thisPost = { nbytes: data.length, batch, commit }; - if (headers.length) { - thisPost.headers = headers; - } - stats.posts.push(thisPost); - return responseGenerator.next().value; - } - - let done = () => {} - let pq = new PostQueue(poster, lastModTime, config, getTestLogger(), done); - return { pq, stats }; -} - -add_test(function test_simple() { - let config = { - max_post_bytes: 1000, - max_post_records: 100, - max_batch_bytes: Infinity, - max_batch_records: Infinity, - } - - const time = 11111111; - - function* responseGenerator() { - yield { success: true, status: 200, headers: { 'x-weave-timestamp': time + 100, 'x-last-modified': time + 100 } }; - } - - let { pq, stats } = makePostQueue(config, time, responseGenerator()); - pq.enqueue(makeRecord(10)); - pq.flush(true); - - deepEqual(stats.posts, [{ - nbytes: 12, // expect our 10 byte record plus "[]" to wrap it. - commit: true, // we don't know if we have batch semantics, so committed. - headers: [["x-if-unmodified-since", time]], - batch: "true"}]); - - run_next_test(); -}); - -// Test we do the right thing when we need to make multiple posts when there -// are no batch semantics -add_test(function test_max_post_bytes_no_batch() { - let config = { - max_post_bytes: 50, - max_post_records: 4, - max_batch_bytes: Infinity, - max_batch_records: Infinity, - } - - const time = 11111111; - function* responseGenerator() { - yield { success: true, status: 200, headers: { 'x-weave-timestamp': time + 100, 'x-last-modified': time + 100 } }; - yield { success: true, status: 200, headers: { 'x-weave-timestamp': time + 200, 'x-last-modified': time + 200 } }; - } - - let { pq, stats } = makePostQueue(config, time, responseGenerator()); - pq.enqueue(makeRecord(20)); // total size now 22 bytes - "[" + record + "]" - pq.enqueue(makeRecord(20)); // total size now 43 bytes - "[" + record + "," + record + "]" - pq.enqueue(makeRecord(20)); // this will exceed our byte limit, so will be in the 2nd POST. - pq.flush(true); - - deepEqual(stats.posts, [ - { - nbytes: 43, // 43 for the first post - commit: false, - headers: [["x-if-unmodified-since", time]], - batch: "true", - },{ - nbytes: 22, - commit: false, // we know we aren't in a batch, so never commit. - headers: [["x-if-unmodified-since", time + 100]], - batch: null, - } - ]); - equal(pq.lastModified, time + 200); - - run_next_test(); -}); - -// Similar to the above, but we've hit max_records instead of max_bytes. -add_test(function test_max_post_records_no_batch() { - let config = { - max_post_bytes: 100, - max_post_records: 2, - max_batch_bytes: Infinity, - max_batch_records: Infinity, - } - - const time = 11111111; - - function* responseGenerator() { - yield { success: true, status: 200, headers: { 'x-weave-timestamp': time + 100, 'x-last-modified': time + 100 } }; - yield { success: true, status: 200, headers: { 'x-weave-timestamp': time + 200, 'x-last-modified': time + 200 } }; - } - - let { pq, stats } = makePostQueue(config, time, responseGenerator()); - pq.enqueue(makeRecord(20)); // total size now 22 bytes - "[" + record + "]" - pq.enqueue(makeRecord(20)); // total size now 43 bytes - "[" + record + "," + record + "]" - pq.enqueue(makeRecord(20)); // this will exceed our records limit, so will be in the 2nd POST. - pq.flush(true); - - deepEqual(stats.posts, [ - { - nbytes: 43, // 43 for the first post - commit: false, - batch: "true", - headers: [["x-if-unmodified-since", time]], - },{ - nbytes: 22, - commit: false, // we know we aren't in a batch, so never commit. - batch: null, - headers: [["x-if-unmodified-since", time + 100]], - } - ]); - equal(pq.lastModified, time + 200); - - run_next_test(); -}); - -// Batch tests. - -// Test making a single post when batch semantics are in place. -add_test(function test_single_batch() { - let config = { - max_post_bytes: 1000, - max_post_records: 100, - max_batch_bytes: 2000, - max_batch_records: 200, - } - const time = 11111111; - function* responseGenerator() { - yield { success: true, status: 202, obj: { batch: 1234 }, - headers: { 'x-last-modified': time, 'x-weave-timestamp': time + 100 }, - }; - } - - let { pq, stats } = makePostQueue(config, time, responseGenerator()); - ok(pq.enqueue(makeRecord(10)).enqueued); - pq.flush(true); - - deepEqual(stats.posts, [ - { - nbytes: 12, // expect our 10 byte record plus "[]" to wrap it. - commit: true, // we don't know if we have batch semantics, so committed. - batch: "true", - headers: [["x-if-unmodified-since", time]], - } - ]); - - run_next_test(); -}); - -// Test we do the right thing when we need to make multiple posts when there -// are batch semantics in place. -add_test(function test_max_post_bytes_batch() { - let config = { - max_post_bytes: 50, - max_post_records: 4, - max_batch_bytes: 5000, - max_batch_records: 100, - } - - const time = 11111111; - function* responseGenerator() { - yield { success: true, status: 202, obj: { batch: 1234 }, - headers: { 'x-last-modified': time, 'x-weave-timestamp': time + 100 }, - }; - yield { success: true, status: 202, obj: { batch: 1234 }, - headers: { 'x-last-modified': time + 200, 'x-weave-timestamp': time + 200 }, - }; - } - - let { pq, stats } = makePostQueue(config, time, responseGenerator()); - ok(pq.enqueue(makeRecord(20)).enqueued); // total size now 22 bytes - "[" + record + "]" - ok(pq.enqueue(makeRecord(20)).enqueued); // total size now 43 bytes - "[" + record + "," + record + "]" - ok(pq.enqueue(makeRecord(20)).enqueued); // this will exceed our byte limit, so will be in the 2nd POST. - pq.flush(true); - - deepEqual(stats.posts, [ - { - nbytes: 43, // 43 for the first post - commit: false, - batch: "true", - headers: [['x-if-unmodified-since', time]], - },{ - nbytes: 22, - commit: true, - batch: 1234, - headers: [['x-if-unmodified-since', time]], - } - ]); - - equal(pq.lastModified, time + 200); - - run_next_test(); -}); - -// Test we do the right thing when the batch bytes limit is exceeded. -add_test(function test_max_post_bytes_batch() { - let config = { - max_post_bytes: 50, - max_post_records: 20, - max_batch_bytes: 70, - max_batch_records: 100, - } - - const time0 = 11111111; - const time1 = 22222222; - function* responseGenerator() { - yield { success: true, status: 202, obj: { batch: 1234 }, - headers: { 'x-last-modified': time0, 'x-weave-timestamp': time0 + 100 }, - }; - yield { success: true, status: 202, obj: { batch: 1234 }, - headers: { 'x-last-modified': time1, 'x-weave-timestamp': time1 }, - }; - yield { success: true, status: 202, obj: { batch: 5678 }, - headers: { 'x-last-modified': time1, 'x-weave-timestamp': time1 + 100 }, - }; - yield { success: true, status: 202, obj: { batch: 5678 }, - headers: { 'x-last-modified': time1 + 200, 'x-weave-timestamp': time1 + 200 }, - }; - } - - let { pq, stats } = makePostQueue(config, time0, responseGenerator()); - ok(pq.enqueue(makeRecord(20)).enqueued); // total size now 22 bytes - "[" + record + "]" - ok(pq.enqueue(makeRecord(20)).enqueued); // total size now 43 bytes - "[" + record + "," + record + "]" - // this will exceed our POST byte limit, so will be in the 2nd POST - but still in the first batch. - ok(pq.enqueue(makeRecord(20)).enqueued); // 22 bytes for 2nd post, 55 bytes in the batch. - // this will exceed our batch byte limit, so will be in a new batch. - ok(pq.enqueue(makeRecord(20)).enqueued); // 22 bytes in 3rd post/2nd batch - ok(pq.enqueue(makeRecord(20)).enqueued); // 43 bytes in 3rd post/2nd batch - // This will exceed POST byte limit, so will be in the 4th post, part of the 2nd batch. - ok(pq.enqueue(makeRecord(20)).enqueued); // 22 bytes for 4th post/2nd batch - pq.flush(true); - - deepEqual(stats.posts, [ - { - nbytes: 43, // 43 for the first post - commit: false, - batch: "true", - headers: [['x-if-unmodified-since', time0]], - },{ - // second post of 22 bytes in the first batch, committing it. - nbytes: 22, - commit: true, - batch: 1234, - headers: [['x-if-unmodified-since', time0]], - }, { - // 3rd post of 43 bytes in a new batch, not yet committing it. - nbytes: 43, - commit: false, - batch: "true", - headers: [['x-if-unmodified-since', time1]], - },{ - // 4th post of 22 bytes in second batch, committing it. - nbytes: 22, - commit: true, - batch: 5678, - headers: [['x-if-unmodified-since', time1]], - }, - ]); - - equal(pq.lastModified, time1 + 200); - - run_next_test(); -}); - -// Test we split up the posts when we exceed the record limit when batch semantics -// are in place. -add_test(function test_max_post_bytes_batch() { - let config = { - max_post_bytes: 1000, - max_post_records: 2, - max_batch_bytes: 5000, - max_batch_records: 100, - } - - const time = 11111111; - function* responseGenerator() { - yield { success: true, status: 202, obj: { batch: 1234 }, - headers: { 'x-last-modified': time, 'x-weave-timestamp': time + 100 }, - }; - yield { success: true, status: 202, obj: { batch: 1234 }, - headers: { 'x-last-modified': time + 200, 'x-weave-timestamp': time + 200 }, - }; - } - - let { pq, stats } = makePostQueue(config, time, responseGenerator()); - ok(pq.enqueue(makeRecord(20)).enqueued); // total size now 22 bytes - "[" + record + "]" - ok(pq.enqueue(makeRecord(20)).enqueued); // total size now 43 bytes - "[" + record + "," + record + "]" - ok(pq.enqueue(makeRecord(20)).enqueued); // will exceed record limit, so will be in 2nd post. - pq.flush(true); - - deepEqual(stats.posts, [ - { - nbytes: 43, // 43 for the first post - commit: false, - batch: "true", - headers: [['x-if-unmodified-since', time]], - },{ - nbytes: 22, - commit: true, - batch: 1234, - headers: [['x-if-unmodified-since', time]], - } - ]); - - equal(pq.lastModified, time + 200); - - run_next_test(); -}); - -// Test that a single huge record fails to enqueue -add_test(function test_huge_record() { - let config = { - max_post_bytes: 50, - max_post_records: 100, - max_batch_bytes: 5000, - max_batch_records: 100, - } - - const time = 11111111; - function* responseGenerator() { - yield { success: true, status: 202, obj: { batch: 1234 }, - headers: { 'x-last-modified': time, 'x-weave-timestamp': time + 100 }, - }; - yield { success: true, status: 202, obj: { batch: 1234 }, - headers: { 'x-last-modified': time + 200, 'x-weave-timestamp': time + 200 }, - }; - } - - let { pq, stats } = makePostQueue(config, time, responseGenerator()); - ok(pq.enqueue(makeRecord(20)).enqueued); - - let { enqueued, error } = pq.enqueue(makeRecord(1000)); - ok(!enqueued); - notEqual(error, undefined); - - // make sure that we keep working, skipping the bad record entirely - // (handling the error the queue reported is left up to caller) - ok(pq.enqueue(makeRecord(20)).enqueued); - ok(pq.enqueue(makeRecord(20)).enqueued); - - pq.flush(true); - - deepEqual(stats.posts, [ - { - nbytes: 43, // 43 for the first post - commit: false, - batch: "true", - headers: [['x-if-unmodified-since', time]], - },{ - nbytes: 22, - commit: true, - batch: 1234, - headers: [['x-if-unmodified-since', time]], - } - ]); - - equal(pq.lastModified, time + 200); - - run_next_test(); -}); - -// Test we do the right thing when the batch record limit is exceeded. -add_test(function test_max_records_batch() { - let config = { - max_post_bytes: 1000, - max_post_records: 3, - max_batch_bytes: 10000, - max_batch_records: 5, - } - - const time0 = 11111111; - const time1 = 22222222; - function* responseGenerator() { - yield { success: true, status: 202, obj: { batch: 1234 }, - headers: { 'x-last-modified': time0, 'x-weave-timestamp': time0 + 100 }, - }; - yield { success: true, status: 202, obj: { batch: 1234 }, - headers: { 'x-last-modified': time1, 'x-weave-timestamp': time1 }, - }; - yield { success: true, status: 202, obj: { batch: 5678 }, - headers: { 'x-last-modified': time1, 'x-weave-timestamp': time1 + 100 }, - }; - yield { success: true, status: 202, obj: { batch: 5678 }, - headers: { 'x-last-modified': time1 + 200, 'x-weave-timestamp': time1 + 200 }, - }; - } - - let { pq, stats } = makePostQueue(config, time0, responseGenerator()); - - ok(pq.enqueue(makeRecord(20)).enqueued); - ok(pq.enqueue(makeRecord(20)).enqueued); - ok(pq.enqueue(makeRecord(20)).enqueued); - - ok(pq.enqueue(makeRecord(20)).enqueued); - ok(pq.enqueue(makeRecord(20)).enqueued); - - ok(pq.enqueue(makeRecord(20)).enqueued); - ok(pq.enqueue(makeRecord(20)).enqueued); - ok(pq.enqueue(makeRecord(20)).enqueued); - - ok(pq.enqueue(makeRecord(20)).enqueued); - - pq.flush(true); - - deepEqual(stats.posts, [ - { // 3 records - nbytes: 64, - commit: false, - batch: "true", - headers: [['x-if-unmodified-since', time0]], - },{ // 2 records -- end batch1 - nbytes: 43, - commit: true, - batch: 1234, - headers: [['x-if-unmodified-since', time0]], - }, { // 3 records - nbytes: 64, - commit: false, - batch: "true", - headers: [['x-if-unmodified-since', time1]], - },{ // 1 record -- end batch2 - nbytes: 22, - commit: true, - batch: 5678, - headers: [['x-if-unmodified-since', time1]], - }, - ]); - - equal(pq.lastModified, time1 + 200); - - run_next_test(); -}); \ No newline at end of file diff --git a/services/sync/tests/unit/test_prefs_store.js b/services/sync/tests/unit/test_prefs_store.js index 9c321bceb..51b220d53 100644 --- a/services/sync/tests/unit/test_prefs_store.js +++ b/services/sync/tests/unit/test_prefs_store.js @@ -23,22 +23,25 @@ function makePersona(id) { } function run_test() { - _("Test fixtures."); - // read our custom prefs file before doing anything. - Services.prefs.readUserPrefs(do_get_file("prefs_test_prefs_store.js")); - // Now we've read from this file, any writes the pref service makes will be - // back to this prefs_test_prefs_store.js directly in the obj dir. This - // upsets things in confusing ways :) We avoid this by explicitly telling the - // pref service to use a file in our profile dir. - let prefFile = do_get_profile(); - prefFile.append("prefs.js"); - Services.prefs.savePrefFile(prefFile); - Services.prefs.readUserPrefs(prefFile); - let store = Service.engineManager.get("prefs")._store; let prefs = new Preferences(); try { + _("Test fixtures."); + Svc.Prefs.set("prefs.sync.testing.int", true); + Svc.Prefs.set("prefs.sync.testing.string", true); + Svc.Prefs.set("prefs.sync.testing.bool", true); + Svc.Prefs.set("prefs.sync.testing.dont.change", true); + Svc.Prefs.set("prefs.sync.testing.turned.off", false); + Svc.Prefs.set("prefs.sync.testing.nonexistent", true); + + prefs.set("testing.int", 123); + prefs.set("testing.string", "ohai"); + prefs.set("testing.bool", true); + prefs.set("testing.dont.change", "Please don't change me."); + prefs.set("testing.turned.off", "I won't get synced."); + prefs.set("testing.not.turned.on", "I won't get synced either!"); + _("The GUID corresponds to XUL App ID."); let allIDs = store.getAllIDs(); let ids = Object.keys(allIDs); @@ -58,22 +61,17 @@ function run_test() { do_check_eq(record.value["testing.int"], 123); do_check_eq(record.value["testing.string"], "ohai"); do_check_eq(record.value["testing.bool"], true); - // non-existing prefs get null as the value do_check_eq(record.value["testing.nonexistent"], null); - // as do prefs that have a default value. - do_check_eq(record.value["testing.default"], null); do_check_false("testing.turned.off" in record.value); do_check_false("testing.not.turned.on" in record.value); - _("Prefs record contains non-default pref sync prefs too."); - do_check_eq(record.value["services.sync.prefs.sync.testing.int"], null); - do_check_eq(record.value["services.sync.prefs.sync.testing.string"], null); - do_check_eq(record.value["services.sync.prefs.sync.testing.bool"], null); - do_check_eq(record.value["services.sync.prefs.sync.testing.dont.change"], null); - // but this one is a user_pref so *will* be synced. + _("Prefs record contains pref sync prefs too."); + do_check_eq(record.value["services.sync.prefs.sync.testing.int"], true); + do_check_eq(record.value["services.sync.prefs.sync.testing.string"], true); + do_check_eq(record.value["services.sync.prefs.sync.testing.bool"], true); + do_check_eq(record.value["services.sync.prefs.sync.testing.dont.change"], true); do_check_eq(record.value["services.sync.prefs.sync.testing.turned.off"], false); - do_check_eq(record.value["services.sync.prefs.sync.testing.nonexistent"], null); - do_check_eq(record.value["services.sync.prefs.sync.testing.default"], null); + do_check_eq(record.value["services.sync.prefs.sync.testing.nonexistent"], true); _("Update some prefs, including one that's to be reset/deleted."); Svc.Prefs.set("testing.deleteme", "I'm going to be deleted!"); @@ -99,28 +97,28 @@ function run_test() { // Ensure we don't go to the network to fetch personas and end up leaking // stuff. Services.io.offline = true; - do_check_false(!!prefs.get("lightweightThemes.selectedThemeID")); + do_check_false(!!prefs.get("lightweightThemes.isThemeSelected")); do_check_eq(LightweightThemeManager.currentTheme, null); let persona1 = makePersona(); let persona2 = makePersona(); let usedThemes = JSON.stringify([persona1, persona2]); record.value = { - "lightweightThemes.selectedThemeID": persona1.id, + "lightweightThemes.isThemeSelected": true, "lightweightThemes.usedThemes": usedThemes }; store.update(record); - do_check_eq(prefs.get("lightweightThemes.selectedThemeID"), persona1.id); + do_check_true(prefs.get("lightweightThemes.isThemeSelected")); do_check_true(Utils.deepEquals(LightweightThemeManager.currentTheme, persona1)); _("Disable persona"); record.value = { - "lightweightThemes.selectedThemeID": null, + "lightweightThemes.isThemeSelected": false, "lightweightThemes.usedThemes": usedThemes }; store.update(record); - do_check_false(!!prefs.get("lightweightThemes.selectedThemeID")); + do_check_false(prefs.get("lightweightThemes.isThemeSelected")); do_check_eq(LightweightThemeManager.currentTheme, null); _("Only the current app's preferences are applied."); @@ -131,37 +129,6 @@ function run_test() { store.update(record); do_check_eq(prefs.get("testing.int"), 42); - _("The light-weight theme preference is handled correctly."); - let lastThemeID = undefined; - let orig_updateLightWeightTheme = store._updateLightWeightTheme; - store._updateLightWeightTheme = function(themeID) { - lastThemeID = themeID; - } - try { - record = new PrefRec("prefs", PREFS_GUID); - record.value = { - "testing.int": 42, - }; - store.update(record); - do_check_true(lastThemeID === undefined, - "should not have tried to change the theme with an unrelated pref."); - Services.prefs.setCharPref("lightweightThemes.selectedThemeID", "foo"); - record.value = { - "lightweightThemes.selectedThemeID": "foo", - }; - store.update(record); - do_check_true(lastThemeID === undefined, - "should not have tried to change the theme when the incoming pref matches current value."); - - record.value = { - "lightweightThemes.selectedThemeID": "bar", - }; - store.update(record); - do_check_eq(lastThemeID, "bar", - "should have tried to change the theme when the incoming pref was different."); - } finally { - store._updateLightWeightTheme = orig_updateLightWeightTheme; - } } finally { prefs.resetBranch(""); } diff --git a/services/sync/tests/unit/test_records_crypto.js b/services/sync/tests/unit/test_records_crypto.js index 392a746ef..4d623c917 100644 --- a/services/sync/tests/unit/test_records_crypto.js +++ b/services/sync/tests/unit/test_records_crypto.js @@ -10,7 +10,7 @@ Cu.import("resource://services-sync/service.js"); Cu.import("resource://services-sync/util.js"); Cu.import("resource://testing-common/services/sync/utils.js"); -var cryptoWrap; +let cryptoWrap; function crypted_resource_handler(metadata, response) { let obj = {id: "resource", @@ -148,32 +148,6 @@ function run_test() { do_check_eq(bookmarkItem.decrypt(Service.collectionKeys.keyForCollection("bookmarks")).stuff, "my payload here"); - do_check_true(Service.collectionKeys.hasKeysFor(["bookmarks"])); - - // Add a key for some new collection and verify that it isn't the - // default key. - do_check_false(Service.collectionKeys.hasKeysFor(["forms"])); - do_check_false(Service.collectionKeys.hasKeysFor(["bookmarks", "forms"])); - let oldFormsKey = Service.collectionKeys.keyForCollection("forms"); - do_check_eq(oldFormsKey, Service.collectionKeys._default); - let newKeys = Service.collectionKeys.ensureKeysFor(["forms"]); - do_check_true(newKeys.hasKeysFor(["forms"])); - do_check_true(newKeys.hasKeysFor(["bookmarks", "forms"])); - let newFormsKey = newKeys.keyForCollection("forms"); - do_check_neq(newFormsKey, oldFormsKey); - - // Verify that this doesn't overwrite keys - let regetKeys = newKeys.ensureKeysFor(["forms"]); - do_check_eq(regetKeys.keyForCollection("forms"), newFormsKey); - - const emptyKeys = new CollectionKeyManager(); - payload = { - default: Service.collectionKeys._default.keyPairB64, - collections: {} - }; - // Verify that not passing `modified` doesn't throw - emptyKeys.setContents(payload, null); - log.info("Done!"); } finally { diff --git a/services/sync/tests/unit/test_resource.js b/services/sync/tests/unit/test_resource.js index 8f5534c92..027d662b4 100644 --- a/services/sync/tests/unit/test_resource.js +++ b/services/sync/tests/unit/test_resource.js @@ -7,9 +7,9 @@ Cu.import("resource://services-sync/identity.js"); Cu.import("resource://services-sync/resource.js"); Cu.import("resource://services-sync/util.js"); -var logger; +let logger; -var fetched = false; +let fetched = false; function server_open(metadata, response) { let body; if (metadata.method == "GET") { @@ -45,7 +45,7 @@ function server_404(metadata, response) { response.bodyOutputStream.write(body, body.length); } -var pacFetched = false; +let pacFetched = false; function server_pac(metadata, response) { pacFetched = true; let body = 'function FindProxyForURL(url, host) { return "DIRECT"; }'; @@ -55,7 +55,7 @@ function server_pac(metadata, response) { } -var sample_data = { +let sample_data = { some: "sample_data", injson: "format", number: 42 @@ -140,7 +140,7 @@ function server_headers(metadata, response) { header_names = header_names.sort(); headers = {}; - for (let header of header_names) { + for each (let header in header_names) { headers[header] = metadata.getHeader(header); } let body = JSON.stringify(headers); @@ -442,8 +442,6 @@ function run_test() { // It throws and logs. do_check_eq(error.result, Cr.NS_ERROR_MALFORMED_URI); do_check_eq(error, "Error: NS_ERROR_MALFORMED_URI"); - // Note the strings haven't been formatted yet, but that's OK for this test. - do_check_eq(warnings.pop(), "${action} request to ${url} failed: ${ex}"); do_check_eq(warnings.pop(), "Got exception calling onProgress handler during fetch of " + server.baseURI + "/json"); @@ -467,7 +465,6 @@ function run_test() { // It throws and logs. do_check_eq(error.result, Cr.NS_ERROR_XPC_JS_THREW_STRING); do_check_eq(error, "Error: NS_ERROR_XPC_JS_THREW_STRING"); - do_check_eq(warnings.pop(), "${action} request to ${url} failed: ${ex}"); do_check_eq(warnings.pop(), "Got exception calling onProgress handler during fetch of " + server.baseURI + "/json"); diff --git a/services/sync/tests/unit/test_resource_async.js b/services/sync/tests/unit/test_resource_async.js index 0db91a1b5..c4b9a3804 100644 --- a/services/sync/tests/unit/test_resource_async.js +++ b/services/sync/tests/unit/test_resource_async.js @@ -7,9 +7,9 @@ Cu.import("resource://services-sync/identity.js"); Cu.import("resource://services-sync/resource.js"); Cu.import("resource://services-sync/util.js"); -var logger; +let logger; -var fetched = false; +let fetched = false; function server_open(metadata, response) { let body; if (metadata.method == "GET") { @@ -45,7 +45,7 @@ function server_404(metadata, response) { response.bodyOutputStream.write(body, body.length); } -var pacFetched = false; +let pacFetched = false; function server_pac(metadata, response) { _("Invoked PAC handler."); pacFetched = true; @@ -55,7 +55,7 @@ function server_pac(metadata, response) { response.bodyOutputStream.write(body, body.length); } -var sample_data = { +let sample_data = { some: "sample_data", injson: "format", number: 42 @@ -140,7 +140,7 @@ function server_headers(metadata, response) { header_names = header_names.sort(); headers = {}; - for (let header of header_names) { + for each (let header in header_names) { headers[header] = metadata.getHeader(header); } let body = JSON.stringify(headers); @@ -148,7 +148,7 @@ function server_headers(metadata, response) { response.bodyOutputStream.write(body, body.length); } -var quotaValue; +let quotaValue; Observers.add("weave:service:quota:remaining", function (subject) { quotaValue = subject; }); @@ -221,7 +221,7 @@ add_test(function test_new_channel() { }); -var server; +let server; add_test(function setup() { server = httpd_setup({ diff --git a/services/sync/tests/unit/test_resource_header.js b/services/sync/tests/unit/test_resource_header.js index 4f28e01da..1835cc0e0 100644 --- a/services/sync/tests/unit/test_resource_header.js +++ b/services/sync/tests/unit/test_resource_header.js @@ -11,7 +11,7 @@ function run_test() { run_next_test(); } -var httpServer = new HttpServer(); +let httpServer = new HttpServer(); httpServer.registerPathHandler("/content", contentHandler); httpServer.start(-1); @@ -20,8 +20,8 @@ const TEST_URL = "http://localhost:" + HTTP_PORT + "/content"; const BODY = "response body"; // Keep headers for later inspection. -var auth = null; -var foo = null; +let auth = null; +let foo = null; function contentHandler(metadata, response) { _("Handling request."); auth = metadata.getHeader("Authorization"); diff --git a/services/sync/tests/unit/test_resource_ua.js b/services/sync/tests/unit/test_resource_ua.js index 31c2cd379..279a2b3e6 100644 --- a/services/sync/tests/unit/test_resource_ua.js +++ b/services/sync/tests/unit/test_resource_ua.js @@ -7,18 +7,15 @@ Cu.import("resource://services-sync/service.js"); Cu.import("resource://services-sync/util.js"); Cu.import("resource://testing-common/services/sync/utils.js"); -var httpProtocolHandler = Cc["@mozilla.org/network/protocol;1?name=http"] - .getService(Ci.nsIHttpProtocolHandler); - // Tracking info/collections. -var collectionsHelper = track_collections_helper(); -var collections = collectionsHelper.collections; +let collectionsHelper = track_collections_helper(); +let collections = collectionsHelper.collections; -var meta_global; -var server; +let meta_global; +let server; -var expectedUA; -var ua; +let expectedUA; +let ua; function uaHandler(f) { return function(request, response) { ua = request.getHeader("User-Agent"); @@ -40,10 +37,7 @@ function run_test() { Service.clusterURL = server.baseURI + "/"; _("Server URL: " + server.baseURI); - // Note this string is missing the trailing ".destkop" as the test - // adjusts the "client.type" pref where that portion comes from. expectedUA = Services.appinfo.name + "/" + Services.appinfo.version + - " (" + httpProtocolHandler.oscpu + ")" + " FxSync/" + WEAVE_VERSION + "." + Services.appinfo.appBuildID; diff --git a/services/sync/tests/unit/test_score_triggers.js b/services/sync/tests/unit/test_score_triggers.js index 513be685a..98d3e094a 100644 --- a/services/sync/tests/unit/test_score_triggers.js +++ b/services/sync/tests/unit/test_score_triggers.js @@ -12,13 +12,13 @@ Cu.import("resource://testing-common/services/sync/utils.js"); Service.engineManager.clear(); Service.engineManager.register(RotaryEngine); -var engine = Service.engineManager.get("rotary"); -var tracker = engine._tracker; +let engine = Service.engineManager.get("rotary"); +let tracker = engine._tracker; engine.enabled = true; // Tracking info/collections. -var collectionsHelper = track_collections_helper(); -var upd = collectionsHelper.with_updated_collection; +let collectionsHelper = track_collections_helper(); +let upd = collectionsHelper.with_updated_collection; function sync_httpd_setup() { let handlers = {}; diff --git a/services/sync/tests/unit/test_service_attributes.js b/services/sync/tests/unit/test_service_attributes.js index 931c7741a..dc82f5edb 100644 --- a/services/sync/tests/unit/test_service_attributes.js +++ b/services/sync/tests/unit/test_service_attributes.js @@ -29,6 +29,7 @@ function test_urls() { Service.serverURL = "http://weave.server/"; Service.clusterURL = "http://weave.cluster/"; + do_check_eq(Svc.Prefs.get("clusterURL"), "http://weave.cluster/"); do_check_eq(Service.userBaseURL, "http://weave.cluster/1.1/johndoe/"); do_check_eq(Service.infoURL, @@ -62,11 +63,11 @@ function test_urls() { _("The 'serverURL' attributes updates/resets preferences."); // Identical value doesn't do anything Service.serverURL = Service.serverURL; - do_check_eq(Service.clusterURL, "http://weave.cluster/"); + do_check_eq(Svc.Prefs.get("clusterURL"), "http://weave.cluster/"); Service.serverURL = "http://different.auth.node/"; do_check_eq(Svc.Prefs.get("serverURL"), "http://different.auth.node/"); - do_check_eq(Service.clusterURL, ""); + do_check_eq(Svc.Prefs.get("clusterURL"), undefined); } finally { Svc.Prefs.resetBranch(""); @@ -83,12 +84,12 @@ function test_syncID() { do_check_eq(Svc.Prefs.get("client.syncID"), undefined); // Performing the first get on the attribute will generate a new GUID. - do_check_eq(Service.syncID, "fake-guid-00"); - do_check_eq(Svc.Prefs.get("client.syncID"), "fake-guid-00"); + do_check_eq(Service.syncID, "fake-guid-0"); + do_check_eq(Svc.Prefs.get("client.syncID"), "fake-guid-0"); Svc.Prefs.set("client.syncID", Utils.makeGUID()); - do_check_eq(Svc.Prefs.get("client.syncID"), "fake-guid-01"); - do_check_eq(Service.syncID, "fake-guid-01"); + do_check_eq(Svc.Prefs.get("client.syncID"), "fake-guid-1"); + do_check_eq(Service.syncID, "fake-guid-1"); } finally { Svc.Prefs.resetBranch(""); new FakeGUIDService(); diff --git a/services/sync/tests/unit/test_service_detect_upgrade.js b/services/sync/tests/unit/test_service_detect_upgrade.js index 0f46832d9..528bd751b 100644 --- a/services/sync/tests/unit/test_service_detect_upgrade.js +++ b/services/sync/tests/unit/test_service_detect_upgrade.js @@ -60,7 +60,7 @@ add_test(function v4_upgrade() { }]}]}; delete Svc.Session; Svc.Session = { - getBrowserState: () => JSON.stringify(myTabs) + getBrowserState: function () JSON.stringify(myTabs) }; Service.status.resetSync(); @@ -229,7 +229,7 @@ add_test(function v5_upgrade() { }]}]}; delete Svc.Session; Svc.Session = { - getBrowserState: () => JSON.stringify(myTabs) + getBrowserState: function () JSON.stringify(myTabs) }; Service.status.resetSync(); diff --git a/services/sync/tests/unit/test_service_getStorageInfo.js b/services/sync/tests/unit/test_service_getStorageInfo.js index 841dceb78..4d463044b 100644 --- a/services/sync/tests/unit/test_service_getStorageInfo.js +++ b/services/sync/tests/unit/test_service_getStorageInfo.js @@ -7,10 +7,7 @@ Cu.import("resource://services-sync/service.js"); Cu.import("resource://services-sync/util.js"); Cu.import("resource://testing-common/services/sync/utils.js"); -var httpProtocolHandler = Cc["@mozilla.org/network/protocol;1?name=http"] - .getService(Ci.nsIHttpProtocolHandler); - -var collections = {steam: 65.11328, +let collections = {steam: 65.11328, petrol: 82.488281, diesel: 2.25488281}; @@ -40,7 +37,6 @@ add_test(function test_success() { Service.identity.username, Service.identity.basicPassword)); let expectedUA = Services.appinfo.name + "/" + Services.appinfo.version + - " (" + httpProtocolHandler.oscpu + ")" + " FxSync/" + WEAVE_VERSION + "." + Services.appinfo.appBuildID + ".desktop"; do_check_eq(handler.request.getHeader("User-Agent"), expectedUA); diff --git a/services/sync/tests/unit/test_service_login.js b/services/sync/tests/unit/test_service_login.js index 2ecb0a377..52ee5e63a 100644 --- a/services/sync/tests/unit/test_service_login.js +++ b/services/sync/tests/unit/test_service_login.js @@ -183,7 +183,7 @@ add_test(function test_login_on_sync() { // This test exercises these two branches. _("We're ready to sync if locked."); - Service.enabled = true; + Svc.Prefs.set("enabled", true); Services.io.offline = false; Service.scheduler.checkSyncStatus(); do_check_true(scheduleCalled); diff --git a/services/sync/tests/unit/test_service_passwordUTF8.js b/services/sync/tests/unit/test_service_passwordUTF8.js index e781050b3..733911291 100644 --- a/services/sync/tests/unit/test_service_passwordUTF8.js +++ b/services/sync/tests/unit/test_service_passwordUTF8.js @@ -11,13 +11,13 @@ const APPLES = "\uf8ff\uf8ff\uf8ff\uf8ff"; const LOWBYTES = "\xff\xff\xff\xff"; // Poor man's /etc/passwd. Static since there's no btoa()/atob() in xpcshell. -var basicauth = {}; +let basicauth = {}; basicauth[LOWBYTES] = "Basic am9obmRvZTr/////"; basicauth[Utils.encodeUTF8(JAPANESE)] = "Basic am9obmRvZTrjk7/jl7/jm7/jn78="; // Global var for the server password, read by info_collections(), // modified by change_password(). -var server_password; +let server_password; function login_handling(handler) { return function (request, response) { diff --git a/services/sync/tests/unit/test_service_startOver.js b/services/sync/tests/unit/test_service_startOver.js index 899420548..6fb0a66d7 100644 --- a/services/sync/tests/unit/test_service_startOver.js +++ b/services/sync/tests/unit/test_service_startOver.js @@ -28,7 +28,7 @@ function run_test() { run_next_test(); } -add_identity_test(this, function* test_resetLocalData() { +add_identity_test(this, function test_resetLocalData() { yield configureIdentity(); Service.status.enforceBackoff = true; Service.status.backoffInterval = 42; diff --git a/services/sync/tests/unit/test_service_startup.js b/services/sync/tests/unit/test_service_startup.js index 5148f6d13..6ced39da9 100644 --- a/services/sync/tests/unit/test_service_startup.js +++ b/services/sync/tests/unit/test_service_startup.js @@ -10,7 +10,6 @@ Svc.Prefs.set("registerEngines", "Tab,Bookmarks,Form,History"); Cu.import("resource://services-sync/service.js"); function run_test() { - validate_all_future_pings(); _("When imported, Service.onStartup is called"); initTestLogging("Trace"); @@ -21,7 +20,7 @@ function run_test() { // Test fixtures Service.identity.username = "johndoe"; - do_check_true(xps.enabled); + do_check_false(xps.enabled); Cu.import("resource://services-sync/service.js"); @@ -30,7 +29,7 @@ function run_test() { _("Engines are registered."); let engines = Service.engineManager.getAll(); - do_check_true(Utils.deepEquals(engines.map(engine => engine.name), + do_check_true(Utils.deepEquals([engine.name for each (engine in engines)], ['tabs', 'bookmarks', 'forms', 'history'])); _("Observers are notified of startup"); @@ -46,4 +45,10 @@ function run_test() { Svc.Prefs.resetBranch(""); do_test_finished(); }); + + do_check_false(xps.enabled); + + Service.identity.account = "johndoe"; + Service.clusterURL = "http://localhost/"; + do_check_true(xps.enabled); } diff --git a/services/sync/tests/unit/test_service_sync_locked.js b/services/sync/tests/unit/test_service_sync_locked.js index ee952c7ee..e2cbbfa92 100644 --- a/services/sync/tests/unit/test_service_sync_locked.js +++ b/services/sync/tests/unit/test_service_sync_locked.js @@ -5,17 +5,14 @@ Cu.import("resource://services-sync/service.js"); Cu.import("resource://services-sync/util.js"); function run_test() { - validate_all_future_pings(); let debug = []; let info = []; function augmentLogger(old) { let d = old.debug; let i = old.info; - // For the purposes of this test we don't need to do full formatting - // of the 2nd param, as the ones we care about are always strings. - old.debug = function(m, p) { debug.push(p ? m + ": " + p : m); d.call(old, m, p); } - old.info = function(m, p) { info.push(p ? m + ": " + p : m); i.call(old, m, p); } + old.debug = function(m) { debug.push(m); d.call(old, m); } + old.info = function(m) { info.push(m); i.call(old, m); } return old; } @@ -31,7 +28,9 @@ function run_test() { Service.sync(); Service._locked = false; - do_check_true(debug[debug.length - 2].startsWith("Exception calling WrappedLock: Could not acquire lock. Label: \"service.js: login\".")); - do_check_eq(info[info.length - 1], "Cannot start sync: already syncing?"); + do_check_eq(debug[debug.length - 2], + "Exception: Could not acquire lock. Label: \"service.js: login\". No traceback available"); + do_check_eq(info[info.length - 1], + "Cannot start sync: already syncing?"); } diff --git a/services/sync/tests/unit/test_service_sync_remoteSetup.js b/services/sync/tests/unit/test_service_sync_remoteSetup.js index 83dbf3cd7..852ba64d5 100644 --- a/services/sync/tests/unit/test_service_sync_remoteSetup.js +++ b/services/sync/tests/unit/test_service_sync_remoteSetup.js @@ -10,7 +10,6 @@ Cu.import("resource://testing-common/services/sync/fakeservices.js"); Cu.import("resource://testing-common/services/sync/utils.js"); function run_test() { - validate_all_future_pings(); let logger = Log.repository.rootLogger; Log.repository.rootLogger.addAppender(new Log.DumpAppender()); @@ -54,27 +53,15 @@ function run_test() { return_timestamp(request, response, ts); } - const GLOBAL_PATH = "/1.1/johndoe/storage/meta/global"; - const INFO_PATH = "/1.1/johndoe/info/collections"; - - let handlers = { + let server = httpd_setup({ "/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/storage/meta": upd("meta", wasCalledHandler(metaColl)), "/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 { _("Log in."); @@ -102,63 +89,6 @@ function run_test() { Service.recordManager.get(Service.metaURL).payload.syncID = "foobar"; do_check_true(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."); - do_check_false(Service._remoteSetup()); - mock.restore(); - - Service.login("johndoe", "ilovejane", syncKey); - mock = mockHandler(GLOBAL_PATH, returnStatusCode("GET", 503)); - Service.recordManager.del(Service.metaURL); - _("Checking that remoteSetup returns false on 503 on first get /meta/global."); - do_check_false(Service._remoteSetup()); - do_check_eq(Service.status.sync, METARECORD_DOWNLOAD_FAIL); - mock.restore(); - - mock = mockHandler(GLOBAL_PATH, returnStatusCode("GET", 404)); - Service.recordManager.del(Service.metaURL); - _("Checking that remoteSetup recovers on 404 on first get /meta/global."); - do_check_true(Service._remoteSetup()); - mock.restore(); - - let makeOutdatedMeta = () => { - Service.metaModified = 0; - let infoResponse = 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 }); - do_check_true(Service._remoteSetup(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 }); - do_check_false(Service._remoteSetup(makeOutdatedMeta())); - do_check_eq(Service.status.sync, ""); - mock.restore(); - - metaColl.delete({}); - _("Do an initial sync."); let beforeSync = Date.now()/1000; Service.sync(); @@ -230,6 +160,7 @@ function run_test() { do_check_false(Service.verifyAndFetchSymmetricKeys()); do_check_eq(Service.status.login, LOGIN_FAILED_INVALID_PASSPHRASE); + } finally { Svc.Prefs.resetBranch(""); server.stop(do_test_finished); diff --git a/services/sync/tests/unit/test_service_sync_specified.js b/services/sync/tests/unit/test_service_sync_specified.js deleted file mode 100644 index 7cb0f9d9c..000000000 --- a/services/sync/tests/unit/test_service_sync_specified.js +++ /dev/null @@ -1,160 +0,0 @@ -/* 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/engines.js"); -Cu.import("resource://services-sync/engines/clients.js"); -Cu.import("resource://services-sync/record.js"); -Cu.import("resource://services-sync/service.js"); -Cu.import("resource://services-sync/util.js"); -Cu.import("resource://testing-common/services/sync/utils.js"); - -initTestLogging(); -Service.engineManager.clear(); - -let syncedEngines = [] - -function SteamEngine() { - SyncEngine.call(this, "Steam", Service); -} -SteamEngine.prototype = { - __proto__: SyncEngine.prototype, - _sync: function _sync() { - syncedEngines.push(this.name); - } -}; -Service.engineManager.register(SteamEngine); - -function StirlingEngine() { - SyncEngine.call(this, "Stirling", Service); -} -StirlingEngine.prototype = { - __proto__: SteamEngine.prototype, - _sync: function _sync() { - syncedEngines.push(this.name); - } -}; -Service.engineManager.register(StirlingEngine); - -// Tracking info/collections. -var collectionsHelper = track_collections_helper(); -var upd = collectionsHelper.with_updated_collection; - -function sync_httpd_setup(handlers) { - - handlers["/1.1/johndoe/info/collections"] = collectionsHelper.handler; - delete collectionsHelper.collections.crypto; - delete collectionsHelper.collections.meta; - - let cr = new ServerWBO("keys"); - handlers["/1.1/johndoe/storage/crypto/keys"] = - upd("crypto", cr.handler()); - - let cl = new ServerCollection(); - handlers["/1.1/johndoe/storage/clients"] = - upd("clients", cl.handler()); - - return httpd_setup(handlers); -} - -function setUp() { - syncedEngines = []; - let engine = Service.engineManager.get("steam"); - engine.enabled = true; - engine.syncPriority = 1; - - engine = Service.engineManager.get("stirling"); - engine.enabled = true; - engine.syncPriority = 2; - - let server = sync_httpd_setup({ - "/1.1/johndoe/storage/meta/global": new ServerWBO("global", {}).handler(), - }); - new SyncTestingInfrastructure(server, "johndoe", "ilovejane", - "abcdeabcdeabcdeabcdeabcdea"); - return server; -} - -function run_test() { - initTestLogging("Trace"); - validate_all_future_pings(); - Log.repository.getLogger("Sync.Service").level = Log.Level.Trace; - Log.repository.getLogger("Sync.ErrorHandler").level = Log.Level.Trace; - - run_next_test(); -} - -add_test(function test_noEngines() { - _("Test: An empty array of engines to sync does nothing."); - let server = setUp(); - - try { - _("Sync with no engines specified."); - Service.sync([]); - deepEqual(syncedEngines, [], "no engines were synced"); - - } finally { - Service.startOver(); - server.stop(run_next_test); - } -}); - -add_test(function test_oneEngine() { - _("Test: Only one engine is synced."); - let server = setUp(); - - try { - - _("Sync with 1 engine specified."); - Service.sync(["steam"]); - deepEqual(syncedEngines, ["steam"]) - - } finally { - Service.startOver(); - server.stop(run_next_test); - } -}); - -add_test(function test_bothEnginesSpecified() { - _("Test: All engines are synced when specified in the correct order (1)."); - let server = setUp(); - - try { - _("Sync with both engines specified."); - Service.sync(["steam", "stirling"]); - deepEqual(syncedEngines, ["steam", "stirling"]) - - } finally { - Service.startOver(); - server.stop(run_next_test); - } -}); - -add_test(function test_bothEnginesSpecified() { - _("Test: All engines are synced when specified in the correct order (2)."); - let server = setUp(); - - try { - _("Sync with both engines specified."); - Service.sync(["stirling", "steam"]); - deepEqual(syncedEngines, ["stirling", "steam"]) - - } finally { - Service.startOver(); - server.stop(run_next_test); - } -}); - -add_test(function test_bothEnginesDefault() { - _("Test: All engines are synced when nothing is specified."); - let server = setUp(); - - try { - Service.sync(); - deepEqual(syncedEngines, ["steam", "stirling"]) - - } finally { - Service.startOver(); - server.stop(run_next_test); - } -}); diff --git a/services/sync/tests/unit/test_service_sync_updateEnabledEngines.js b/services/sync/tests/unit/test_service_sync_updateEnabledEngines.js index ee1800fd3..c945cb6c2 100644 --- a/services/sync/tests/unit/test_service_sync_updateEnabledEngines.js +++ b/services/sync/tests/unit/test_service_sync_updateEnabledEngines.js @@ -41,15 +41,13 @@ function StirlingEngine() { StirlingEngine.prototype = { __proto__: SteamEngine.prototype, // This engine's enabled state is the same as the SteamEngine's. - get prefName() { - return "steam"; - } + get prefName() "steam" }; Service.engineManager.register(StirlingEngine); // Tracking info/collections. -var collectionsHelper = track_collections_helper(); -var upd = collectionsHelper.with_updated_collection; +let collectionsHelper = track_collections_helper(); +let upd = collectionsHelper.with_updated_collection; function sync_httpd_setup(handlers) { @@ -86,7 +84,6 @@ function run_test() { initTestLogging("Trace"); Log.repository.getLogger("Sync.Service").level = Log.Level.Trace; Log.repository.getLogger("Sync.ErrorHandler").level = Log.Level.Trace; - validate_all_future_pings(); run_next_test(); } diff --git a/services/sync/tests/unit/test_service_wipeServer.js b/services/sync/tests/unit/test_service_wipeServer.js index 9320f4b88..3fc45cf86 100644 --- a/services/sync/tests/unit/test_service_wipeServer.js +++ b/services/sync/tests/unit/test_service_wipeServer.js @@ -31,7 +31,7 @@ FakeCollection.prototype = { } }; -function* setUpTestFixtures(server) { +function setUpTestFixtures(server) { let cryptoService = new FakeCryptoService(); Service.serverURL = server.baseURI + "/"; @@ -52,7 +52,7 @@ function promiseStopServer(server) { return deferred.promise; } -add_identity_test(this, function* test_wipeServer_list_success() { +add_identity_test(this, function test_wipeServer_list_success() { _("Service.wipeServer() deletes collections given as argument."); let steam_coll = new FakeCollection(); @@ -86,7 +86,7 @@ add_identity_test(this, function* test_wipeServer_list_success() { } }); -add_identity_test(this, function* test_wipeServer_list_503() { +add_identity_test(this, function test_wipeServer_list_503() { _("Service.wipeServer() deletes collections given as argument."); let steam_coll = new FakeCollection(); @@ -127,7 +127,7 @@ add_identity_test(this, function* test_wipeServer_list_503() { } }); -add_identity_test(this, function* test_wipeServer_all_success() { +add_identity_test(this, function test_wipeServer_all_success() { _("Service.wipeServer() deletes all the things."); /** @@ -157,7 +157,7 @@ add_identity_test(this, function* test_wipeServer_all_success() { Svc.Prefs.resetBranch(""); }); -add_identity_test(this, function* test_wipeServer_all_404() { +add_identity_test(this, function test_wipeServer_all_404() { _("Service.wipeServer() accepts a 404."); /** @@ -189,7 +189,7 @@ add_identity_test(this, function* test_wipeServer_all_404() { Svc.Prefs.resetBranch(""); }); -add_identity_test(this, function* test_wipeServer_all_503() { +add_identity_test(this, function test_wipeServer_all_503() { _("Service.wipeServer() throws if it encounters a non-200/404 response."); /** @@ -221,7 +221,7 @@ add_identity_test(this, function* test_wipeServer_all_503() { Svc.Prefs.resetBranch(""); }); -add_identity_test(this, function* test_wipeServer_all_connectionRefused() { +add_identity_test(this, function test_wipeServer_all_connectionRefused() { _("Service.wipeServer() throws if it encounters a network problem."); let server = httpd_setup({}); yield setUpTestFixtures(server); diff --git a/services/sync/tests/unit/test_status.js b/services/sync/tests/unit/test_status.js index 378aafe90..bc2d67f42 100644 --- a/services/sync/tests/unit/test_status.js +++ b/services/sync/tests/unit/test_status.js @@ -18,9 +18,9 @@ function run_test() { // Check login status - for (let code of [LOGIN_FAILED_NO_USERNAME, - LOGIN_FAILED_NO_PASSWORD, - LOGIN_FAILED_NO_PASSPHRASE]) { + for each (let code in [LOGIN_FAILED_NO_USERNAME, + LOGIN_FAILED_NO_PASSWORD, + LOGIN_FAILED_NO_PASSPHRASE]) { Status.login = code; do_check_eq(Status.login, code); do_check_eq(Status.service, CLIENT_NOT_CONFIGURED); diff --git a/services/sync/tests/unit/test_syncedtabs.js b/services/sync/tests/unit/test_syncedtabs.js deleted file mode 100644 index fe2cb6d1b..000000000 --- a/services/sync/tests/unit/test_syncedtabs.js +++ /dev/null @@ -1,221 +0,0 @@ -/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- - * vim:set ts=2 sw=2 sts=2 et: -*/ -"use strict"; - -Cu.import("resource://services-sync/main.js"); -Cu.import("resource://services-sync/SyncedTabs.jsm"); -Cu.import("resource://gre/modules/Log.jsm"); - -const faviconService = Cc["@mozilla.org/browser/favicon-service;1"] - .getService(Ci.nsIFaviconService); - -Log.repository.getLogger("Sync.RemoteTabs").addAppender(new Log.DumpAppender()); - -// A mock "Tabs" engine which the SyncedTabs module will use instead of the real -// engine. We pass a constructor that Sync creates. -function MockTabsEngine() { - this.clients = {}; // We'll set this dynamically -} - -MockTabsEngine.prototype = { - name: "tabs", - enabled: true, - - getAllClients() { - return this.clients; - }, - - getOpenURLs() { - return new Set(); - }, -} - -// A clients engine that doesn't need to be a constructor. -let MockClientsEngine = { - clientSettings: null, // Set in `configureClients`. - - isMobile(guid) { - if (!guid.endsWith("desktop") && !guid.endsWith("mobile")) { - throw new Error("this module expected guids to end with 'desktop' or 'mobile'"); - } - return guid.endsWith("mobile"); - }, - remoteClientExists(id) { - return this.clientSettings[id] !== false; - }, - getClientName(id) { - if (this.clientSettings[id]) { - return this.clientSettings[id]; - } - let engine = Weave.Service.engineManager.get("tabs"); - return engine.clients[id].clientName; - }, -} - -// Configure Sync with our mock tabs engine and force it to become initialized. -Services.prefs.setCharPref("services.sync.username", "someone@somewhere.com"); - -Weave.Service.engineManager.unregister("tabs"); -Weave.Service.engineManager.register(MockTabsEngine); -Weave.Service.clientsEngine = MockClientsEngine; - -// Tell the Sync XPCOM service it is initialized. -let weaveXPCService = Cc["@mozilla.org/weave/service;1"] - .getService(Ci.nsISupports) - .wrappedJSObject; -weaveXPCService.ready = true; - -function configureClients(clients, clientSettings = {}) { - // Configure the instance Sync created. - let engine = Weave.Service.engineManager.get("tabs"); - // each client record is expected to have an id. - for (let [guid, client] of Object.entries(clients)) { - client.id = guid; - } - engine.clients = clients; - // Apply clients collection overrides. - MockClientsEngine.clientSettings = clientSettings; - // Send an observer that pretends the engine just finished a sync. - Services.obs.notifyObservers(null, "weave:engine:sync:finish", "tabs"); -} - -// The tests. -add_task(function* test_noClients() { - // no clients, can't be tabs. - yield configureClients({}); - - let tabs = yield SyncedTabs.getTabClients(); - equal(Object.keys(tabs).length, 0); -}); - -add_task(function* test_clientWithTabs() { - yield configureClients({ - guid_desktop: { - clientName: "My Desktop", - tabs: [ - { - urlHistory: ["http://foo.com/"], - icon: "http://foo.com/favicon", - }], - }, - guid_mobile: { - clientName: "My Phone", - tabs: [], - } - }); - - let clients = yield SyncedTabs.getTabClients(); - equal(clients.length, 2); - clients.sort((a, b) => { return a.name.localeCompare(b.name);}); - equal(clients[0].tabs.length, 1); - equal(clients[0].tabs[0].url, "http://foo.com/"); - equal(clients[0].tabs[0].icon, "http://foo.com/favicon"); - // second client has no tabs. - equal(clients[1].tabs.length, 0); -}); - -add_task(function* test_staleClientWithTabs() { - yield configureClients({ - guid_desktop: { - clientName: "My Desktop", - tabs: [ - { - urlHistory: ["http://foo.com/"], - icon: "http://foo.com/favicon", - }], - }, - guid_mobile: { - clientName: "My Phone", - tabs: [], - }, - guid_stale_mobile: { - clientName: "My Deleted Phone", - tabs: [], - }, - guid_stale_desktop: { - clientName: "My Deleted Laptop", - tabs: [ - { - urlHistory: ["https://bar.com/"], - icon: "https://bar.com/favicon", - }], - }, - guid_stale_name_desktop: { - clientName: "My Generic Device", - tabs: [ - { - urlHistory: ["https://example.edu/"], - icon: "https://example.edu/favicon", - }], - }, - }, { - guid_stale_mobile: false, - guid_stale_desktop: false, - // We should always use the device name from the clients collection, instead - // of the possibly stale tabs collection. - guid_stale_name_desktop: "My Laptop", - }); - let clients = yield SyncedTabs.getTabClients(); - clients.sort((a, b) => { return a.name.localeCompare(b.name);}); - equal(clients.length, 3); - equal(clients[0].name, "My Desktop"); - equal(clients[0].tabs.length, 1); - equal(clients[0].tabs[0].url, "http://foo.com/"); - equal(clients[1].name, "My Laptop"); - equal(clients[1].tabs.length, 1); - equal(clients[1].tabs[0].url, "https://example.edu/"); - equal(clients[2].name, "My Phone"); - equal(clients[2].tabs.length, 0); -}); - -add_task(function* test_clientWithTabsIconsDisabled() { - Services.prefs.setBoolPref("services.sync.syncedTabs.showRemoteIcons", false); - yield configureClients({ - guid_desktop: { - clientName: "My Desktop", - tabs: [ - { - urlHistory: ["http://foo.com/"], - icon: "http://foo.com/favicon", - }], - }, - }); - - let clients = yield SyncedTabs.getTabClients(); - equal(clients.length, 1); - clients.sort((a, b) => { return a.name.localeCompare(b.name);}); - equal(clients[0].tabs.length, 1); - equal(clients[0].tabs[0].url, "http://foo.com/"); - // expect the default favicon (empty string) due to the pref being false. - equal(clients[0].tabs[0].icon, ""); - Services.prefs.clearUserPref("services.sync.syncedTabs.showRemoteIcons"); -}); - -add_task(function* test_filter() { - // Nothing matches. - yield configureClients({ - guid_desktop: { - clientName: "My Desktop", - tabs: [ - { - urlHistory: ["http://foo.com/"], - title: "A test page.", - }, - { - urlHistory: ["http://bar.com/"], - title: "Another page.", - }], - }, - }); - - let clients = yield SyncedTabs.getTabClients("foo"); - equal(clients.length, 1); - equal(clients[0].tabs.length, 1); - equal(clients[0].tabs[0].url, "http://foo.com/"); - // check it matches the title. - clients = yield SyncedTabs.getTabClients("test"); - equal(clients.length, 1); - equal(clients[0].tabs.length, 1); - equal(clients[0].tabs[0].url, "http://foo.com/"); -}); diff --git a/services/sync/tests/unit/test_syncengine.js b/services/sync/tests/unit/test_syncengine.js index 8c01ca048..393e49607 100644 --- a/services/sync/tests/unit/test_syncengine.js +++ b/services/sync/tests/unit/test_syncengine.js @@ -10,7 +10,7 @@ function makeSteamEngine() { return new SyncEngine('Steam', Service); } -var server; +let server; function test_url_attributes() { _("SyncEngine url attributes"); @@ -35,12 +35,12 @@ function test_syncID() { do_check_eq(Svc.Prefs.get("steam.syncID"), undefined); // Performing the first get on the attribute will generate a new GUID. - do_check_eq(engine.syncID, "fake-guid-00"); - do_check_eq(Svc.Prefs.get("steam.syncID"), "fake-guid-00"); + do_check_eq(engine.syncID, "fake-guid-0"); + do_check_eq(Svc.Prefs.get("steam.syncID"), "fake-guid-0"); Svc.Prefs.set("steam.syncID", Utils.makeGUID()); - do_check_eq(Svc.Prefs.get("steam.syncID"), "fake-guid-01"); - do_check_eq(engine.syncID, "fake-guid-01"); + do_check_eq(Svc.Prefs.get("steam.syncID"), "fake-guid-1"); + do_check_eq(engine.syncID, "fake-guid-1"); } finally { Svc.Prefs.resetBranch(""); } diff --git a/services/sync/tests/unit/test_syncengine_sync.js b/services/sync/tests/unit/test_syncengine_sync.js index 97289962f..6a6d047bf 100644 --- a/services/sync/tests/unit/test_syncengine_sync.js +++ b/services/sync/tests/unit/test_syncengine_sync.js @@ -15,22 +15,13 @@ function makeRotaryEngine() { return new RotaryEngine(Service); } -function clean() { +function cleanAndGo(server) { Svc.Prefs.resetBranch(""); Svc.Prefs.set("log.logger.engine.rotary", "Trace"); Service.recordManager.clearCache(); -} - -function cleanAndGo(server) { - clean(); server.stop(run_next_test); } -function promiseClean(server) { - clean(); - return new Promise(resolve => server.stop(resolve)); -} - function configureService(server, username, password) { Service.clusterURL = server.baseURI; @@ -181,7 +172,7 @@ add_test(function test_syncStartup_syncIDMismatchResetsClient() { try { // Confirm initial environment - do_check_eq(engine.syncID, 'fake-guid-00'); + do_check_eq(engine.syncID, 'fake-guid-0'); do_check_eq(engine._tracker.changedIDs["rekolok"], undefined); engine.lastSync = Date.now() / 1000; @@ -676,7 +667,7 @@ add_test(function test_processIncoming_mobile_batchSize() { }); -add_task(function *test_processIncoming_store_toFetch() { +add_test(function test_processIncoming_store_toFetch() { _("If processIncoming fails in the middle of a batch on mobile, state is saved in toFetch and lastSync."); Service.identity.username = "foo"; Svc.Prefs.set("client.type", "mobile"); @@ -723,10 +714,11 @@ add_task(function *test_processIncoming_store_toFetch() { let error; try { - yield sync_engine_and_validate_telem(engine, true); + engine.sync(); } catch (ex) { error = ex; } + do_check_true(!!error); // Only the first two batches have been applied. do_check_eq(Object.keys(engine._store.items).length, @@ -738,7 +730,7 @@ add_task(function *test_processIncoming_store_toFetch() { do_check_eq(engine.lastSync, collection.wbo("record-no-99").modified); } finally { - yield promiseClean(server); + cleanAndGo(server); } }); @@ -1229,7 +1221,7 @@ add_test(function test_processIncoming_failed_records() { }); -add_task(function *test_processIncoming_decrypt_failed() { +add_test(function test_processIncoming_decrypt_failed() { _("Ensure that records failing to decrypt are either replaced or refetched."); Service.identity.username = "foo"; @@ -1288,10 +1280,7 @@ add_task(function *test_processIncoming_decrypt_failed() { }); engine.lastSync = collection.wbo("nojson").modified - 1; - let ping = yield sync_engine_and_validate_telem(engine, true); - do_check_eq(ping.engines[0].incoming.applied, 2); - do_check_eq(ping.engines[0].incoming.failed, 4); - do_check_eq(ping.engines[0].incoming.newFailed, 4); + engine.sync(); do_check_eq(engine.previousFailed.length, 4); do_check_eq(engine.previousFailed[0], "nojson"); @@ -1305,7 +1294,7 @@ add_task(function *test_processIncoming_decrypt_failed() { do_check_eq(observerSubject.failed, 4); } finally { - yield promiseClean(server); + cleanAndGo(server); } }); @@ -1369,7 +1358,7 @@ add_test(function test_uploadOutgoing_toEmptyServer() { }); -add_task(function *test_uploadOutgoing_failed() { +add_test(function test_uploadOutgoing_failed() { _("SyncEngine._uploadOutgoing doesn't clear the tracker of objects that failed to upload."); Service.identity.username = "foo"; @@ -1412,7 +1401,7 @@ add_task(function *test_uploadOutgoing_failed() { do_check_eq(engine._tracker.changedIDs['peppercorn'], PEPPERCORN_CHANGED); engine.enabled = true; - yield sync_engine_and_validate_telem(engine, true); + engine.sync(); // Local timestamp has been set. do_check_true(engine.lastSyncLocal > 0); @@ -1427,14 +1416,11 @@ add_task(function *test_uploadOutgoing_failed() { do_check_eq(engine._tracker.changedIDs['peppercorn'], PEPPERCORN_CHANGED); } finally { - yield promiseClean(server); + cleanAndGo(server); } }); -/* A couple of "functional" tests to ensure we split records into appropriate - POST requests. More comprehensive unit-tests for this "batching" are in - test_postqueue.js. -*/ + add_test(function test_uploadOutgoing_MAX_UPLOAD_RECORDS() { _("SyncEngine._uploadOutgoing uploads in batches of MAX_UPLOAD_RECORDS"); @@ -1444,18 +1430,9 @@ add_test(function test_uploadOutgoing_MAX_UPLOAD_RECORDS() { // Let's count how many times the client posts to the server var noOfUploads = 0; collection.post = (function(orig) { - return function(data, request) { - // This test doesn't arrange for batch semantics - so we expect the - // first request to come in with batch=true and the others to have no - // batch related headers at all (as the first response did not provide - // a batch ID) - if (noOfUploads == 0) { - do_check_eq(request.queryString, "batch=true"); - } else { - do_check_eq(request.queryString, ""); - } + return function() { noOfUploads++; - return orig.call(this, data, request); + return orig.apply(this, arguments); }; }(collection.post)); @@ -1500,44 +1477,6 @@ add_test(function test_uploadOutgoing_MAX_UPLOAD_RECORDS() { } }); -add_test(function test_uploadOutgoing_largeRecords() { - _("SyncEngine._uploadOutgoing throws on records larger than MAX_UPLOAD_BYTES"); - - Service.identity.username = "foo"; - let collection = new ServerCollection(); - - let engine = makeRotaryEngine(); - engine.allowSkippedRecord = false; - engine._store.items["large-item"] = "Y".repeat(MAX_UPLOAD_BYTES*2); - engine._tracker.addChangedID("large-item", 0); - collection.insert("large-item"); - - - let meta_global = Service.recordManager.set(engine.metaURL, - new WBORecord(engine.metaURL)); - meta_global.payload.engines = {rotary: {version: engine.version, - syncID: engine.syncID}}; - - let server = sync_httpd_setup({ - "/1.1/foo/storage/rotary": collection.handler() - }); - - let syncTesting = new SyncTestingInfrastructure(server); - - try { - engine._syncStartup(); - let error = null; - try { - engine._uploadOutgoing(); - } catch (e) { - error = e; - } - ok(!!error); - } finally { - cleanAndGo(server); - } -}); - add_test(function test_syncFinish_noDelete() { _("SyncEngine._syncFinish resets tracker's score"); @@ -1667,7 +1606,7 @@ add_test(function test_syncFinish_deleteLotsInBatches() { }); -add_task(function *test_sync_partialUpload() { +add_test(function test_sync_partialUpload() { _("SyncEngine.sync() keeps changedIDs that couldn't be uploaded."); Service.identity.username = "foo"; @@ -1715,12 +1654,11 @@ add_task(function *test_sync_partialUpload() { engine.enabled = true; let error; try { - yield sync_engine_and_validate_telem(engine, true); + engine.sync(); } catch (ex) { error = ex; } - - ok(!!error); + do_check_true(!!error); // The timestamp has been updated. do_check_true(engine.lastSyncLocal > 456); @@ -1738,7 +1676,7 @@ add_task(function *test_sync_partialUpload() { } } finally { - yield promiseClean(server); + cleanAndGo(server); } }); diff --git a/services/sync/tests/unit/test_syncscheduler.js b/services/sync/tests/unit/test_syncscheduler.js index 730a3f996..d496b8838 100644 --- a/services/sync/tests/unit/test_syncscheduler.js +++ b/services/sync/tests/unit/test_syncscheduler.js @@ -26,13 +26,8 @@ CatapultEngine.prototype = { Service.engineManager.register(CatapultEngine); -var scheduler = new SyncScheduler(Service); -var 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 = id => {}; +let scheduler = new SyncScheduler(Service); +let clientsEngine = Service.clientsEngine; function sync_httpd_setup() { let global = new ServerWBO("global", { @@ -74,7 +69,6 @@ function setUp(server) { function cleanUpAndGo(server) { let deferred = Promise.defer(); Utils.nextTick(function () { - clientsEngine._store.wipe(); Service.startOver(); if (server) { server.stop(deferred.resolve); @@ -90,7 +84,6 @@ function run_test() { Log.repository.getLogger("Sync.Service").level = Log.Level.Trace; Log.repository.getLogger("Sync.scheduler").level = Log.Level.Trace; - validate_all_future_pings(); // The scheduler checks Weave.fxaEnabled to determine whether to use // FxA defaults or legacy defaults. As .fxaEnabled checks the username, we @@ -148,33 +141,22 @@ add_test(function test_prefAttributes() { Svc.Prefs.get("scheduler.immediateInterval") * 1000); _("Custom values for prefs will take effect after a restart."); - Svc.Prefs.set("scheduler.sync11.singleDeviceInterval", 420); - Svc.Prefs.set("scheduler.idleInterval", 230); - Svc.Prefs.set("scheduler.activeInterval", 180); + Svc.Prefs.set("scheduler.sync11.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, 230000); - do_check_eq(scheduler.singleDeviceInterval, 420000); - do_check_eq(scheduler.activeInterval, 180000); + do_check_eq(scheduler.idleInterval, 23000); + do_check_eq(scheduler.singleDeviceInterval, 42000); + do_check_eq(scheduler.activeInterval, 18000); do_check_eq(scheduler.immediateInterval, 31415000); - _("Custom values for interval prefs can't be less than 60 seconds."); - Svc.Prefs.set("scheduler.sync11.singleDeviceInterval", 42); - Svc.Prefs.set("scheduler.idleInterval", 50); - Svc.Prefs.set("scheduler.activeInterval", 50); - Svc.Prefs.set("scheduler.immediateInterval", 10); - scheduler.setDefaults(); - do_check_eq(scheduler.idleInterval, 60000); - do_check_eq(scheduler.singleDeviceInterval, 60000); - do_check_eq(scheduler.activeInterval, 60000); - do_check_eq(scheduler.immediateInterval, 60000); - Svc.Prefs.resetBranch(""); scheduler.setDefaults(); run_next_test(); }); -add_identity_test(this, function* test_updateClientMode() { +add_identity_test(this, 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); @@ -204,7 +186,7 @@ add_identity_test(this, function* test_updateClientMode() { yield cleanUpAndGo(); }); -add_identity_test(this, function* test_masterpassword_locked_retry_interval() { +add_identity_test(this, 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() { @@ -241,7 +223,7 @@ add_identity_test(this, function* test_masterpassword_locked_retry_interval() { yield cleanUpAndGo(server); }); -add_identity_test(this, function* test_calculateBackoff() { +add_identity_test(this, function test_calculateBackoff() { do_check_eq(Status.backoffInterval, 0); // Test no interval larger than the maximum backoff is used if @@ -263,7 +245,7 @@ add_identity_test(this, function* test_calculateBackoff() { yield cleanUpAndGo(); }); -add_identity_test(this, function* test_scheduleNextSync_nowOrPast() { +add_identity_test(this, function test_scheduleNextSync_nowOrPast() { let deferred = Promise.defer(); Svc.Obs.add("weave:service:sync:finish", function onSyncFinish() { Svc.Obs.remove("weave:service:sync:finish", onSyncFinish); @@ -278,7 +260,7 @@ add_identity_test(this, function* test_scheduleNextSync_nowOrPast() { yield deferred.promise; }); -add_identity_test(this, function* test_scheduleNextSync_future_noBackoff() { +add_identity_test(this, 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); @@ -327,7 +309,7 @@ add_identity_test(this, function* test_scheduleNextSync_future_noBackoff() { yield cleanUpAndGo(); }); -add_identity_test(this, function* test_scheduleNextSync_future_backoff() { +add_identity_test(this, 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; @@ -377,7 +359,7 @@ add_identity_test(this, function* test_scheduleNextSync_future_backoff() { yield cleanUpAndGo(); }); -add_identity_test(this, function* test_handleSyncError() { +add_identity_test(this, function test_handleSyncError() { let server = sync_httpd_setup(); yield setUp(server); @@ -443,7 +425,7 @@ add_identity_test(this, function* test_handleSyncError() { yield deferred.promise; }); -add_identity_test(this, function* test_client_sync_finish_updateClientMode() { +add_identity_test(this, function test_client_sync_finish_updateClientMode() { let server = sync_httpd_setup(); yield setUp(server); @@ -477,7 +459,7 @@ add_identity_test(this, function* test_client_sync_finish_updateClientMode() { yield cleanUpAndGo(server); }); -add_identity_test(this, function* test_autoconnect_nextSync_past() { +add_identity_test(this, function test_autoconnect_nextSync_past() { let deferred = Promise.defer(); // nextSync will be 0 by default, so it's way in the past. @@ -493,7 +475,7 @@ add_identity_test(this, function* test_autoconnect_nextSync_past() { yield deferred.promise; }); -add_identity_test(this, function* test_autoconnect_nextSync_future() { +add_identity_test(this, function test_autoconnect_nextSync_future() { let deferred = Promise.defer(); let previousSync = Date.now() + scheduler.syncInterval / 2; scheduler.nextSync = previousSync; @@ -522,7 +504,7 @@ add_identity_test(this, function* test_autoconnect_nextSync_future() { // XXX - this test can't be run with the browserid identity as it relies // on the syncKey getter behaving in a certain way... -add_task(function* test_autoconnect_mp_locked() { +add_task(function test_autoconnect_mp_locked() { let server = sync_httpd_setup(); yield setUp(server); @@ -559,7 +541,7 @@ add_task(function* test_autoconnect_mp_locked() { yield deferred.promise; }); -add_identity_test(this, function* test_no_autoconnect_during_wizard() { +add_identity_test(this, function test_no_autoconnect_during_wizard() { let server = sync_httpd_setup(); yield setUp(server); @@ -582,7 +564,7 @@ add_identity_test(this, function* test_no_autoconnect_during_wizard() { yield deferred.promise; }); -add_identity_test(this, function* test_no_autoconnect_status_not_ok() { +add_identity_test(this, 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). @@ -605,7 +587,7 @@ add_identity_test(this, function* test_no_autoconnect_status_not_ok() { yield deferred.promise; }); -add_identity_test(this, function* test_autoconnectDelay_pref() { +add_identity_test(this, function test_autoconnectDelay_pref() { let deferred = Promise.defer(); Svc.Obs.add("weave:service:sync:finish", function onSyncFinish() { Svc.Obs.remove("weave:service:sync:finish", onSyncFinish); @@ -625,7 +607,7 @@ add_identity_test(this, function* test_autoconnectDelay_pref() { yield deferred.promise; }); -add_identity_test(this, function* test_idle_adjustSyncInterval() { +add_identity_test(this, function test_idle_adjustSyncInterval() { // Confirm defaults. do_check_eq(scheduler.idle, false); @@ -645,7 +627,7 @@ add_identity_test(this, function* test_idle_adjustSyncInterval() { yield cleanUpAndGo(); }); -add_identity_test(this, function* test_back_triggersSync() { +add_identity_test(this, function test_back_triggersSync() { // Confirm defaults. do_check_false(scheduler.idle); do_check_eq(Status.backoffInterval, 0); @@ -668,7 +650,7 @@ add_identity_test(this, function* test_back_triggersSync() { yield deferred.promise; }); -add_identity_test(this, function* test_active_triggersSync_observesBackoff() { +add_identity_test(this, function test_active_triggersSync_observesBackoff() { // Confirm defaults. do_check_false(scheduler.idle); @@ -699,7 +681,7 @@ add_identity_test(this, function* test_active_triggersSync_observesBackoff() { yield deferred.promise; }); -add_identity_test(this, function* test_back_debouncing() { +add_identity_test(this, function test_back_debouncing() { _("Ensure spurious back-then-idle events, as observed on OS X, don't trigger a sync."); // Confirm defaults. @@ -727,7 +709,7 @@ add_identity_test(this, function* test_back_debouncing() { yield deferred.promise; }); -add_identity_test(this, function* test_no_sync_node() { +add_identity_test(this, 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(); @@ -742,7 +724,7 @@ add_identity_test(this, function* test_no_sync_node() { yield cleanUpAndGo(server); }); -add_identity_test(this, function* test_sync_failed_partial_500s() { +add_identity_test(this, function test_sync_failed_partial_500s() { _("Test a 5xx status calls handleSyncError."); scheduler._syncErrors = MAX_ERROR_COUNT_BEFORE_BACKOFF; let server = sync_httpd_setup(); @@ -769,7 +751,7 @@ add_identity_test(this, function* test_sync_failed_partial_500s() { yield cleanUpAndGo(server); }); -add_identity_test(this, function* test_sync_failed_partial_400s() { +add_identity_test(this, 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(); @@ -799,7 +781,7 @@ add_identity_test(this, function* test_sync_failed_partial_400s() { yield cleanUpAndGo(server); }); -add_identity_test(this, function* test_sync_X_Weave_Backoff() { +add_identity_test(this, function test_sync_X_Weave_Backoff() { let server = sync_httpd_setup(); yield setUp(server); @@ -842,9 +824,9 @@ add_identity_test(this, function* test_sync_X_Weave_Backoff() { Service.sync(); do_check_true(Status.backoffInterval >= BACKOFF * 1000); - // Allowing 3 seconds worth of of leeway between when Status.minimumNextSync + // Allowing 1 second worth of of leeway between when Status.minimumNextSync // was set and when this line gets executed. - let minimumExpectedDelay = (BACKOFF - 3) * 1000; + 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. @@ -854,7 +836,7 @@ add_identity_test(this, function* test_sync_X_Weave_Backoff() { yield cleanUpAndGo(server); }); -add_identity_test(this, function* test_sync_503_Retry_After() { +add_identity_test(this, function test_sync_503_Retry_After() { let server = sync_httpd_setup(); yield setUp(server); @@ -901,9 +883,9 @@ add_identity_test(this, function* test_sync_503_Retry_After() { do_check_true(Status.enforceBackoff); do_check_true(Status.backoffInterval >= BACKOFF * 1000); - // Allowing 3 seconds worth of of leeway between when Status.minimumNextSync + // Allowing 1 second worth of of leeway between when Status.minimumNextSync // was set and when this line gets executed. - let minimumExpectedDelay = (BACKOFF - 3) * 1000; + 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. @@ -913,7 +895,7 @@ add_identity_test(this, function* test_sync_503_Retry_After() { yield cleanUpAndGo(server); }); -add_identity_test(this, function* test_loginError_recoverable_reschedules() { +add_identity_test(this, function test_loginError_recoverable_reschedules() { _("Verify that a recoverable login error schedules a new sync."); yield configureIdentity({username: "johndoe"}); Service.serverURL = "http://localhost:1234/"; @@ -957,7 +939,7 @@ add_identity_test(this, function* test_loginError_recoverable_reschedules() { yield deferred.promise; }); -add_identity_test(this, function* test_loginError_fatal_clearsTriggers() { +add_identity_test(this, function test_loginError_fatal_clearsTriggers() { _("Verify that a fatal login error clears sync triggers."); yield configureIdentity({username: "johndoe"}); @@ -974,22 +956,11 @@ add_identity_test(this, function* test_loginError_fatal_clearsTriggers() { 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); - if (isConfiguredWithLegacyIdentity()) { - // for the "legacy" identity, a 401 on info/collections means the - // password is wrong, so we enter a "login rejected" state. - do_check_eq(Status.login, LOGIN_FAILED_LOGIN_REJECTED); - - do_check_eq(scheduler.nextSync, 0); - do_check_eq(scheduler.syncTimer, null); - } else { - // For the FxA identity, a 401 on info/collections means a transient - // error, probably due to an inability to fetch a token. - do_check_eq(Status.login, LOGIN_FAILED_NETWORK_ERROR); - // syncs should still be scheduled. - do_check_true(scheduler.nextSync > Date.now()); - do_check_true(scheduler.syncTimer.delay > 0); - } cleanUpAndGo(server).then(deferred.resolve); }); }); @@ -1004,7 +975,7 @@ add_identity_test(this, function* test_loginError_fatal_clearsTriggers() { yield deferred.promise; }); -add_identity_test(this, function* test_proper_interval_on_only_failing() { +add_identity_test(this, 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 diff --git a/services/sync/tests/unit/test_syncstoragerequest.js b/services/sync/tests/unit/test_syncstoragerequest.js index 14e5daade..7c5246bab 100644 --- a/services/sync/tests/unit/test_syncstoragerequest.js +++ b/services/sync/tests/unit/test_syncstoragerequest.js @@ -8,9 +8,6 @@ Cu.import("resource://services-sync/service.js"); Cu.import("resource://services-sync/util.js"); Cu.import("resource://testing-common/services/sync/utils.js"); -var httpProtocolHandler = Cc["@mozilla.org/network/protocol;1?name=http"] - .getService(Ci.nsIHttpProtocolHandler); - function run_test() { Log.repository.getLogger("Sync.RESTRequest").level = Log.Level.Trace; initTestLogging(); @@ -25,7 +22,6 @@ add_test(function test_user_agent_desktop() { let server = httpd_setup({"/resource": handler}); let expectedUA = Services.appinfo.name + "/" + Services.appinfo.version + - " (" + httpProtocolHandler.oscpu + ")" + " FxSync/" + WEAVE_VERSION + "." + Services.appinfo.appBuildID + ".desktop"; @@ -45,7 +41,6 @@ add_test(function test_user_agent_mobile() { Svc.Prefs.set("client.type", "mobile"); let expectedUA = Services.appinfo.name + "/" + Services.appinfo.version + - " (" + httpProtocolHandler.oscpu + ")" + " FxSync/" + WEAVE_VERSION + "." + Services.appinfo.appBuildID + ".mobile"; diff --git a/services/sync/tests/unit/test_tab_engine.js b/services/sync/tests/unit/test_tab_engine.js index 049250230..db4b20a70 100644 --- a/services/sync/tests/unit/test_tab_engine.js +++ b/services/sync/tests/unit/test_tab_engine.js @@ -1,7 +1,6 @@ /* 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/engines/tabs.js"); Cu.import("resource://services-sync/record.js"); Cu.import("resource://services-sync/service.js"); @@ -24,12 +23,11 @@ add_test(function test_getOpenURLs() { _("Test getOpenURLs."); let [engine, store] = getMocks(); - let superLongURL = "http://" + (new Array(MAX_UPLOAD_BYTES).join("w")) + ".com/"; - let urls = ["http://bar.com", "http://foo.com", "http://foobar.com", superLongURL]; - function fourURLs() { + let urls = ["http://bar.com", "http://foo.com", "http://foobar.com"]; + function threeURLs() { return urls.pop(); } - store.getWindowEnumerator = mockGetWindowEnumerator.bind(this, fourURLs, 1, 4); + store.getWindowEnumerator = mockGetWindowEnumerator.bind(this, threeURLs, 1, 3); let matches; @@ -42,10 +40,6 @@ add_test(function test_getOpenURLs() { matches = openurlsset.has("http://barfoo.com"); ok(!matches); - _(" test matching works (too long)"); - matches = openurlsset.has(superLongURL); - ok(!matches); - run_next_test(); }); diff --git a/services/sync/tests/unit/test_tab_store.js b/services/sync/tests/unit/test_tab_store.js index 93b60f0c7..f8265492f 100644 --- a/services/sync/tests/unit/test_tab_store.js +++ b/services/sync/tests/unit/test_tab_store.js @@ -20,26 +20,32 @@ function test_create() { _("Create a first record"); let rec = {id: "id1", clientName: "clientName1", - cleartext: { "foo": "bar" }, + cleartext: "cleartext1", modified: 1000}; store.applyIncoming(rec); - deepEqual(store._remoteClients["id1"], { lastModified: 1000, foo: "bar" }); + do_check_eq(store._remoteClients["id1"], "cleartext1"); + do_check_eq(Svc.Prefs.get("notifyTabState"), 1); _("Create a second record"); rec = {id: "id2", clientName: "clientName2", - cleartext: { "foo2": "bar2" }, + cleartext: "cleartext2", modified: 2000}; store.applyIncoming(rec); - deepEqual(store._remoteClients["id2"], { lastModified: 2000, foo2: "bar2" }); + do_check_eq(store._remoteClients["id2"], "cleartext2"); + do_check_eq(Svc.Prefs.get("notifyTabState"), 0); _("Create a third record"); rec = {id: "id3", clientName: "clientName3", - cleartext: { "foo3": "bar3" }, + cleartext: "cleartext3", modified: 3000}; store.applyIncoming(rec); - deepEqual(store._remoteClients["id3"], { lastModified: 3000, foo3: "bar3" }); + do_check_eq(store._remoteClients["id3"], "cleartext3"); + do_check_eq(Svc.Prefs.get("notifyTabState"), 0); + + // reset the notifyTabState + Svc.Prefs.reset("notifyTabState"); } function test_getAllTabs() { @@ -53,20 +59,20 @@ function test_getAllTabs() { _("Get all tabs."); tabs = store.getAllTabs(); _("Tabs: " + JSON.stringify(tabs)); - equal(tabs.length, 1); - equal(tabs[0].title, "title"); - equal(tabs[0].urlHistory.length, 2); - equal(tabs[0].urlHistory[0], "http://foo.com"); - equal(tabs[0].urlHistory[1], "http://bar.com"); - equal(tabs[0].icon, "image"); - equal(tabs[0].lastUsed, 1); + do_check_eq(tabs.length, 1); + do_check_eq(tabs[0].title, "title"); + do_check_eq(tabs[0].urlHistory.length, 2); + do_check_eq(tabs[0].urlHistory[0], "http://foo.com"); + do_check_eq(tabs[0].urlHistory[1], "http://bar.com"); + do_check_eq(tabs[0].icon, "image"); + do_check_eq(tabs[0].lastUsed, 1); _("Get all tabs, and check that filtering works."); let twoUrls = ["about:foo", "http://fuubar.com"]; store.getWindowEnumerator = mockGetWindowEnumerator.bind(this, "http://foo.com", 1, 1, () => 2, () => twoUrls); tabs = store.getAllTabs(true); _("Filtered: " + JSON.stringify(tabs)); - equal(tabs.length, 0); + do_check_eq(tabs.length, 0); _("Get all tabs, and check that the entries safety limit works."); let allURLs = []; @@ -79,10 +85,10 @@ function test_getAllTabs() { tabs = store.getAllTabs((url) => url.startsWith("about")); _("Sliced: " + JSON.stringify(tabs)); - equal(tabs.length, 1); - equal(tabs[0].urlHistory.length, 25); - equal(tabs[0].urlHistory[0], "http://foo40.bar"); - equal(tabs[0].urlHistory[24], "http://foo16.bar"); + do_check_eq(tabs.length, 1); + do_check_eq(tabs[0].urlHistory.length, 25); + do_check_eq(tabs[0].urlHistory[0], "http://foo40.bar"); + do_check_eq(tabs[0].urlHistory[24], "http://foo16.bar"); } function test_createRecord() { @@ -99,14 +105,14 @@ function test_createRecord() { store.getWindowEnumerator = mockGetWindowEnumerator.bind(this, "http://foo.com", 1, 1); record = store.createRecord("fake-guid"); - ok(record instanceof TabSetRecord); - equal(record.tabs.length, 1); + do_check_true(record instanceof TabSetRecord); + do_check_eq(record.tabs.length, 1); _("create a big record"); store.getWindowEnumerator = mockGetWindowEnumerator.bind(this, "http://foo.com", 1, numtabs); record = store.createRecord("fake-guid"); - ok(record instanceof TabSetRecord); - equal(record.tabs.length, 256); + do_check_true(record instanceof TabSetRecord); + do_check_eq(record.tabs.length, 256); } function run_test() { diff --git a/services/sync/tests/unit/test_tab_tracker.js b/services/sync/tests/unit/test_tab_tracker.js index f98920a44..e7dd48829 100644 --- a/services/sync/tests/unit/test_tab_tracker.js +++ b/services/sync/tests/unit/test_tab_tracker.js @@ -5,7 +5,7 @@ Cu.import("resource://services-sync/engines/tabs.js"); Cu.import("resource://services-sync/service.js"); Cu.import("resource://services-sync/util.js"); -var clientsEngine = Service.clientsEngine; +let clientsEngine = Service.clientsEngine; function fakeSvcWinMediator() { // actions on windows are captured in logs @@ -15,11 +15,9 @@ function fakeSvcWinMediator() { getEnumerator: function() { return { cnt: 2, - hasMoreElements: function() { - return this.cnt-- > 0; - }, + hasMoreElements: function() this.cnt-- > 0, getNext: function() { - let elt = {addTopics: [], remTopics: [], numAPL: 0, numRPL: 0}; + let elt = {addTopics: [], remTopics: []}; logs.push(elt); return { addEventListener: function(topic) { @@ -27,15 +25,7 @@ function fakeSvcWinMediator() { }, removeEventListener: function(topic) { elt.remTopics.push(topic); - }, - gBrowser: { - addProgressListener() { - elt.numAPL++; - }, - removeProgressListener() { - elt.numRPL++; - }, - }, + } }; } }; @@ -61,7 +51,7 @@ function run_test() { logs = fakeSvcWinMediator(); Svc.Obs.notify("weave:engine:start-tracking"); do_check_eq(logs.length, 2); - for (let log of logs) { + for each (let log in logs) { do_check_eq(log.addTopics.length, 5); do_check_true(log.addTopics.indexOf("pageshow") >= 0); do_check_true(log.addTopics.indexOf("TabOpen") >= 0); @@ -69,15 +59,13 @@ function run_test() { do_check_true(log.addTopics.indexOf("TabSelect") >= 0); do_check_true(log.addTopics.indexOf("unload") >= 0); do_check_eq(log.remTopics.length, 0); - do_check_eq(log.numAPL, 1, "Added 1 progress listener"); - do_check_eq(log.numRPL, 0, "Didn't remove a progress listener"); } _("Test listeners are unregistered on windows"); logs = fakeSvcWinMediator(); Svc.Obs.notify("weave:engine:stop-tracking"); do_check_eq(logs.length, 2); - for (let log of logs) { + for each (let log in logs) { do_check_eq(log.addTopics.length, 0); do_check_eq(log.remTopics.length, 5); do_check_true(log.remTopics.indexOf("pageshow") >= 0); @@ -85,12 +73,10 @@ function run_test() { do_check_true(log.remTopics.indexOf("TabClose") >= 0); do_check_true(log.remTopics.indexOf("TabSelect") >= 0); do_check_true(log.remTopics.indexOf("unload") >= 0); - do_check_eq(log.numAPL, 0, "Didn't add a progress listener"); - do_check_eq(log.numRPL, 1, "Removed 1 progress listener"); } _("Test tab listener"); - for (let evttype of ["TabOpen", "TabClose", "TabSelect"]) { + for each (let evttype in ["TabOpen", "TabClose", "TabSelect"]) { // Pretend we just synced. tracker.clearChangedIDs(); do_check_false(tracker.modified); @@ -109,19 +95,4 @@ function run_test() { tracker.onTab({type: "pageshow", originalTarget: "pageshow"}); do_check_true(Utils.deepEquals(Object.keys(engine.getChangedIDs()), [clientsEngine.localID])); - - // Pretend we just synced and saw some progress listeners. - tracker.clearChangedIDs(); - do_check_false(tracker.modified); - tracker.onLocationChange({ isTopLevel: false }, undefined, undefined, 0); - do_check_false(tracker.modified, "non-toplevel request didn't flag as modified"); - - tracker.onLocationChange({ isTopLevel: true }, undefined, undefined, - Ci.nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT); - do_check_false(tracker.modified, "location change within the same document request didn't flag as modified"); - - tracker.onLocationChange({ isTopLevel: true }, undefined, undefined, 0); - do_check_true(tracker.modified, "location change for a new top-level document flagged as modified"); - do_check_true(Utils.deepEquals(Object.keys(engine.getChangedIDs()), - [clientsEngine.localID])); } diff --git a/services/sync/tests/unit/test_telemetry.js b/services/sync/tests/unit/test_telemetry.js deleted file mode 100644 index 50a3d136b..000000000 --- a/services/sync/tests/unit/test_telemetry.js +++ /dev/null @@ -1,564 +0,0 @@ -/* Any copyright is dedicated to the Public Domain. - http://creativecommons.org/publicdomain/zero/1.0/ */ - -Cu.import("resource://services-common/observers.js"); -Cu.import("resource://services-sync/telemetry.js"); -Cu.import("resource://services-sync/service.js"); -Cu.import("resource://services-sync/record.js"); -Cu.import("resource://services-sync/resource.js"); -Cu.import("resource://services-sync/constants.js"); -Cu.import("resource://services-sync/engines.js"); -Cu.import("resource://services-sync/engines/bookmarks.js"); -Cu.import("resource://services-sync/engines/clients.js"); -Cu.import("resource://testing-common/services/sync/utils.js"); -Cu.import("resource://testing-common/services/sync/fxa_utils.js"); -Cu.import("resource://testing-common/services/sync/rotaryengine.js"); -Cu.import("resource://gre/modules/osfile.jsm", this); - -Cu.import("resource://gre/modules/PlacesUtils.jsm"); -Cu.import("resource://services-sync/util.js"); - -initTestLogging("Trace"); - -function SteamStore(engine) { - Store.call(this, "Steam", engine); -} - -SteamStore.prototype = { - __proto__: Store.prototype, -}; - -function SteamTracker(name, engine) { - Tracker.call(this, name || "Steam", engine); -} - -SteamTracker.prototype = { - __proto__: Tracker.prototype -}; - -function SteamEngine(service) { - Engine.call(this, "steam", service); -} - -SteamEngine.prototype = { - __proto__: Engine.prototype, - _storeObj: SteamStore, - _trackerObj: SteamTracker, - _errToThrow: null, - _sync() { - if (this._errToThrow) { - throw this._errToThrow; - } - } -}; - -function BogusEngine(service) { - Engine.call(this, "bogus", service); -} - -BogusEngine.prototype = Object.create(SteamEngine.prototype); - -function cleanAndGo(server) { - Svc.Prefs.resetBranch(""); - Svc.Prefs.set("log.logger.engine.rotary", "Trace"); - Service.recordManager.clearCache(); - return new Promise(resolve => server.stop(resolve)); -} - -// Avoid addon manager complaining about not being initialized -Service.engineManager.unregister("addons"); - -add_identity_test(this, function *test_basic() { - let helper = track_collections_helper(); - let upd = helper.with_updated_collection; - - yield configureIdentity({ username: "johndoe" }); - let handlers = { - "/1.1/johndoe/info/collections": helper.handler, - "/1.1/johndoe/storage/crypto/keys": upd("crypto", new ServerWBO("keys").handler()), - "/1.1/johndoe/storage/meta/global": upd("meta", new ServerWBO("global").handler()) - }; - - let collections = ["clients", "bookmarks", "forms", "history", "passwords", "prefs", "tabs"]; - - for (let coll of collections) { - handlers["/1.1/johndoe/storage/" + coll] = upd(coll, new ServerCollection({}, true).handler()); - } - - let server = httpd_setup(handlers); - Service.serverURL = server.baseURI; - - yield sync_and_validate_telem(true); - - yield new Promise(resolve => server.stop(resolve)); -}); - -add_task(function* test_processIncoming_error() { - let engine = new BookmarksEngine(Service); - let store = engine._store; - let server = serverForUsers({"foo": "password"}, { - meta: {global: {engines: {bookmarks: {version: engine.version, - syncID: engine.syncID}}}}, - bookmarks: {} - }); - new SyncTestingInfrastructure(server.server); - let collection = server.user("foo").collection("bookmarks"); - try { - // Create a bogus record that when synced down will provoke a - // network error which in turn provokes an exception in _processIncoming. - const BOGUS_GUID = "zzzzzzzzzzzz"; - let bogus_record = collection.insert(BOGUS_GUID, "I'm a bogus record!"); - bogus_record.get = function get() { - throw "Sync this!"; - }; - // Make the 10 minutes old so it will only be synced in the toFetch phase. - bogus_record.modified = Date.now() / 1000 - 60 * 10; - engine.lastSync = Date.now() / 1000 - 60; - engine.toFetch = [BOGUS_GUID]; - - let error, ping; - try { - yield sync_engine_and_validate_telem(engine, true, errPing => ping = errPing); - } catch(ex) { - error = ex; - } - ok(!!error); - ok(!!ping); - equal(ping.uid, "0".repeat(32)); - deepEqual(ping.failureReason, { - name: "othererror", - error: "error.engine.reason.record_download_fail" - }); - - equal(ping.engines.length, 1); - equal(ping.engines[0].name, "bookmarks"); - deepEqual(ping.engines[0].failureReason, { - name: "othererror", - error: "error.engine.reason.record_download_fail" - }); - - } finally { - store.wipe(); - yield cleanAndGo(server); - } -}); - -add_task(function *test_uploading() { - let engine = new BookmarksEngine(Service); - let store = engine._store; - let server = serverForUsers({"foo": "password"}, { - meta: {global: {engines: {bookmarks: {version: engine.version, - syncID: engine.syncID}}}}, - bookmarks: {} - }); - new SyncTestingInfrastructure(server.server); - - let parent = PlacesUtils.toolbarFolderId; - let uri = Utils.makeURI("http://getfirefox.com/"); - let title = "Get Firefox"; - - let bmk_id = PlacesUtils.bookmarks.insertBookmark(parent, uri, - PlacesUtils.bookmarks.DEFAULT_INDEX, "Get Firefox!"); - - let guid = store.GUIDForId(bmk_id); - let record = store.createRecord(guid); - - let collection = server.user("foo").collection("bookmarks"); - try { - let ping = yield sync_engine_and_validate_telem(engine, false); - ok(!!ping); - equal(ping.engines.length, 1); - equal(ping.engines[0].name, "bookmarks"); - ok(!!ping.engines[0].outgoing); - greater(ping.engines[0].outgoing[0].sent, 0) - ok(!ping.engines[0].incoming); - - PlacesUtils.bookmarks.setItemTitle(bmk_id, "New Title"); - - store.wipe(); - engine.resetClient(); - - ping = yield sync_engine_and_validate_telem(engine, false); - equal(ping.engines.length, 1); - equal(ping.engines[0].name, "bookmarks"); - equal(ping.engines[0].outgoing.length, 1); - ok(!!ping.engines[0].incoming); - - } finally { - // Clean up. - store.wipe(); - yield cleanAndGo(server); - } -}); - -add_task(function *test_upload_failed() { - Service.identity.username = "foo"; - let collection = new ServerCollection(); - collection._wbos.flying = new ServerWBO('flying'); - - let server = sync_httpd_setup({ - "/1.1/foo/storage/rotary": collection.handler() - }); - - let syncTesting = new SyncTestingInfrastructure(server); - - let engine = new RotaryEngine(Service); - engine.lastSync = 123; // needs to be non-zero so that tracker is queried - engine.lastSyncLocal = 456; - engine._store.items = { - flying: "LNER Class A3 4472", - scotsman: "Flying Scotsman", - peppercorn: "Peppercorn Class" - }; - const FLYING_CHANGED = 12345; - const SCOTSMAN_CHANGED = 23456; - const PEPPERCORN_CHANGED = 34567; - engine._tracker.addChangedID("flying", FLYING_CHANGED); - engine._tracker.addChangedID("scotsman", SCOTSMAN_CHANGED); - engine._tracker.addChangedID("peppercorn", PEPPERCORN_CHANGED); - - let meta_global = Service.recordManager.set(engine.metaURL, new WBORecord(engine.metaURL)); - meta_global.payload.engines = { rotary: { version: engine.version, syncID: engine.syncID } }; - - try { - engine.enabled = true; - let ping = yield sync_engine_and_validate_telem(engine, true); - ok(!!ping); - equal(ping.engines.length, 1); - equal(ping.engines[0].incoming, null); - deepEqual(ping.engines[0].outgoing, [{ sent: 3, failed: 2 }]); - engine.lastSync = 123; - engine.lastSyncLocal = 456; - - ping = yield sync_engine_and_validate_telem(engine, true); - ok(!!ping); - equal(ping.engines.length, 1); - equal(ping.engines[0].incoming.reconciled, 1); - deepEqual(ping.engines[0].outgoing, [{ sent: 2, failed: 2 }]); - - } finally { - yield cleanAndGo(server); - } -}); - -add_task(function *test_sync_partialUpload() { - Service.identity.username = "foo"; - - let collection = new ServerCollection(); - let server = sync_httpd_setup({ - "/1.1/foo/storage/rotary": collection.handler() - }); - let syncTesting = new SyncTestingInfrastructure(server); - generateNewKeys(Service.collectionKeys); - - let engine = new RotaryEngine(Service); - engine.lastSync = 123; - engine.lastSyncLocal = 456; - - - // Create a bunch of records (and server side handlers) - for (let i = 0; i < 234; i++) { - let id = 'record-no-' + i; - engine._store.items[id] = "Record No. " + i; - engine._tracker.addChangedID(id, i); - // Let two items in the first upload batch fail. - if (i != 23 && i != 42) { - collection.insert(id); - } - } - - let meta_global = Service.recordManager.set(engine.metaURL, - new WBORecord(engine.metaURL)); - meta_global.payload.engines = {rotary: {version: engine.version, - syncID: engine.syncID}}; - - try { - engine.enabled = true; - let ping = yield sync_engine_and_validate_telem(engine, true); - - ok(!!ping); - ok(!ping.failureReason); - equal(ping.engines.length, 1); - equal(ping.engines[0].name, "rotary"); - ok(!ping.engines[0].incoming); - ok(!ping.engines[0].failureReason); - deepEqual(ping.engines[0].outgoing, [{ sent: 234, failed: 2 }]); - - collection.post = function() { throw "Failure"; } - - engine._store.items["record-no-1000"] = "Record No. 1000"; - engine._tracker.addChangedID("record-no-1000", 1000); - collection.insert("record-no-1000", 1000); - - engine.lastSync = 123; - engine.lastSyncLocal = 456; - ping = null; - - try { - // should throw - yield sync_engine_and_validate_telem(engine, true, errPing => ping = errPing); - } catch (e) {} - // It would be nice if we had a more descriptive error for this... - let uploadFailureError = { - name: "othererror", - error: "error.engine.reason.record_upload_fail" - }; - - ok(!!ping); - deepEqual(ping.failureReason, uploadFailureError); - equal(ping.engines.length, 1); - equal(ping.engines[0].name, "rotary"); - deepEqual(ping.engines[0].incoming, { - failed: 1, - newFailed: 1, - reconciled: 232 - }); - ok(!ping.engines[0].outgoing); - deepEqual(ping.engines[0].failureReason, uploadFailureError); - - } finally { - yield cleanAndGo(server); - } -}); - -add_task(function* test_generic_engine_fail() { - Service.engineManager.register(SteamEngine); - let engine = Service.engineManager.get("steam"); - engine.enabled = true; - let store = engine._store; - let server = serverForUsers({"foo": "password"}, { - meta: {global: {engines: {steam: {version: engine.version, - syncID: engine.syncID}}}}, - steam: {} - }); - new SyncTestingInfrastructure(server.server); - let e = new Error("generic failure message") - engine._errToThrow = e; - - try { - let ping = yield sync_and_validate_telem(true); - equal(ping.status.service, SYNC_FAILED_PARTIAL); - deepEqual(ping.engines.find(e => e.name === "steam").failureReason, { - name: "unexpectederror", - error: String(e) - }); - } finally { - Service.engineManager.unregister(engine); - yield cleanAndGo(server); - } -}); - -add_task(function* test_engine_fail_ioerror() { - Service.engineManager.register(SteamEngine); - let engine = Service.engineManager.get("steam"); - engine.enabled = true; - let store = engine._store; - let server = serverForUsers({"foo": "password"}, { - meta: {global: {engines: {steam: {version: engine.version, - syncID: engine.syncID}}}}, - steam: {} - }); - new SyncTestingInfrastructure(server.server); - // create an IOError to re-throw as part of Sync. - try { - // (Note that fakeservices.js has replaced Utils.jsonMove etc, but for - // this test we need the real one so we get real exceptions from the - // filesystem.) - yield Utils._real_jsonMove("file-does-not-exist", "anything", {}); - } catch (ex) { - engine._errToThrow = ex; - } - ok(engine._errToThrow, "expecting exception"); - - try { - let ping = yield sync_and_validate_telem(true); - equal(ping.status.service, SYNC_FAILED_PARTIAL); - let failureReason = ping.engines.find(e => e.name === "steam").failureReason; - equal(failureReason.name, "unexpectederror"); - // ensure the profile dir in the exception message has been stripped. - ok(!failureReason.error.includes(OS.Constants.Path.profileDir), failureReason.error); - ok(failureReason.error.includes("[profileDir]"), failureReason.error); - } finally { - Service.engineManager.unregister(engine); - yield cleanAndGo(server); - } -}); - -add_task(function* test_initial_sync_engines() { - Service.engineManager.register(SteamEngine); - let engine = Service.engineManager.get("steam"); - engine.enabled = true; - let store = engine._store; - let engines = {}; - // These are the only ones who actually have things to sync at startup. - let engineNames = ["clients", "bookmarks", "prefs", "tabs"]; - let conf = { meta: { global: { engines } } }; - for (let e of engineNames) { - engines[e] = { version: engine.version, syncID: engine.syncID }; - conf[e] = {}; - } - let server = serverForUsers({"foo": "password"}, conf); - new SyncTestingInfrastructure(server.server); - try { - let ping = yield wait_for_ping(() => Service.sync(), true); - - equal(ping.engines.find(e => e.name === "clients").outgoing[0].sent, 1); - equal(ping.engines.find(e => e.name === "tabs").outgoing[0].sent, 1); - - // for the rest we don't care about specifics - for (let e of ping.engines) { - if (!engineNames.includes(engine.name)) { - continue; - } - greaterOrEqual(e.took, 1); - ok(!!e.outgoing) - equal(e.outgoing.length, 1); - notEqual(e.outgoing[0].sent, undefined); - equal(e.outgoing[0].failed, undefined); - } - } finally { - yield cleanAndGo(server); - } -}); - -add_task(function* test_nserror() { - Service.engineManager.register(SteamEngine); - let engine = Service.engineManager.get("steam"); - engine.enabled = true; - let store = engine._store; - let server = serverForUsers({"foo": "password"}, { - meta: {global: {engines: {steam: {version: engine.version, - syncID: engine.syncID}}}}, - steam: {} - }); - new SyncTestingInfrastructure(server.server); - engine._errToThrow = Components.Exception("NS_ERROR_UNKNOWN_HOST", Cr.NS_ERROR_UNKNOWN_HOST); - try { - let ping = yield sync_and_validate_telem(true); - deepEqual(ping.status, { - service: SYNC_FAILED_PARTIAL, - sync: LOGIN_FAILED_NETWORK_ERROR - }); - let enginePing = ping.engines.find(e => e.name === "steam"); - deepEqual(enginePing.failureReason, { - name: "nserror", - code: Cr.NS_ERROR_UNKNOWN_HOST - }); - } finally { - Service.engineManager.unregister(engine); - yield cleanAndGo(server); - } -}); - -add_identity_test(this, function *test_discarding() { - let helper = track_collections_helper(); - let upd = helper.with_updated_collection; - let telem = get_sync_test_telemetry(); - telem.maxPayloadCount = 2; - telem.submissionInterval = Infinity; - let oldSubmit = telem.submit; - - let server; - try { - - yield configureIdentity({ username: "johndoe" }); - let handlers = { - "/1.1/johndoe/info/collections": helper.handler, - "/1.1/johndoe/storage/crypto/keys": upd("crypto", new ServerWBO("keys").handler()), - "/1.1/johndoe/storage/meta/global": upd("meta", new ServerWBO("global").handler()) - }; - - let collections = ["clients", "bookmarks", "forms", "history", "passwords", "prefs", "tabs"]; - - for (let coll of collections) { - handlers["/1.1/johndoe/storage/" + coll] = upd(coll, new ServerCollection({}, true).handler()); - } - - server = httpd_setup(handlers); - Service.serverURL = server.baseURI; - telem.submit = () => ok(false, "Submitted telemetry ping when we should not have"); - - for (let i = 0; i < 5; ++i) { - Service.sync(); - } - telem.submit = oldSubmit; - telem.submissionInterval = -1; - let ping = yield sync_and_validate_telem(true, true); // with this we've synced 6 times - equal(ping.syncs.length, 2); - equal(ping.discarded, 4); - } finally { - telem.maxPayloadCount = 500; - telem.submissionInterval = -1; - telem.submit = oldSubmit; - if (server) { - yield new Promise(resolve => server.stop(resolve)); - } - } -}) - -add_task(function* test_no_foreign_engines_in_error_ping() { - Service.engineManager.register(BogusEngine); - let engine = Service.engineManager.get("bogus"); - engine.enabled = true; - let store = engine._store; - let server = serverForUsers({"foo": "password"}, { - meta: {global: {engines: {bogus: {version: engine.version, syncID: engine.syncID}}}}, - steam: {} - }); - engine._errToThrow = new Error("Oh no!"); - new SyncTestingInfrastructure(server.server); - try { - let ping = yield sync_and_validate_telem(true); - equal(ping.status.service, SYNC_FAILED_PARTIAL); - ok(ping.engines.every(e => e.name !== "bogus")); - } finally { - Service.engineManager.unregister(engine); - yield cleanAndGo(server); - } -}); - -add_task(function* test_sql_error() { - Service.engineManager.register(SteamEngine); - let engine = Service.engineManager.get("steam"); - engine.enabled = true; - let store = engine._store; - let server = serverForUsers({"foo": "password"}, { - meta: {global: {engines: {steam: {version: engine.version, - syncID: engine.syncID}}}}, - steam: {} - }); - new SyncTestingInfrastructure(server.server); - engine._sync = function() { - // Just grab a DB connection and issue a bogus SQL statement synchronously. - let db = PlacesUtils.history.QueryInterface(Ci.nsPIPlacesDatabase).DBConnection; - Async.querySpinningly(db.createAsyncStatement("select bar from foo")); - }; - try { - let ping = yield sync_and_validate_telem(true); - let enginePing = ping.engines.find(e => e.name === "steam"); - deepEqual(enginePing.failureReason, { name: "sqlerror", code: 1 }); - } finally { - Service.engineManager.unregister(engine); - yield cleanAndGo(server); - } -}); - -add_task(function* test_no_foreign_engines_in_success_ping() { - Service.engineManager.register(BogusEngine); - let engine = Service.engineManager.get("bogus"); - engine.enabled = true; - let store = engine._store; - let server = serverForUsers({"foo": "password"}, { - meta: {global: {engines: {bogus: {version: engine.version, syncID: engine.syncID}}}}, - steam: {} - }); - - new SyncTestingInfrastructure(server.server); - try { - let ping = yield sync_and_validate_telem(); - ok(ping.engines.every(e => e.name !== "bogus")); - } finally { - Service.engineManager.unregister(engine); - yield cleanAndGo(server); - } -}); \ No newline at end of file diff --git a/services/sync/tests/unit/test_utils_catch.js b/services/sync/tests/unit/test_utils_catch.js index 5f50bf7e4..a10e5eb0d 100644 --- a/services/sync/tests/unit/test_utils_catch.js +++ b/services/sync/tests/unit/test_utils_catch.js @@ -8,46 +8,38 @@ function run_test() { catch: Utils.catch, _log: { debug: function(str) { - didThrow = str.search(/^Exception/) == 0; + didThrow = str.search(/^Exception: /) == 0; }, info: function(str) { wasLocked = str.indexOf("Cannot start sync: already syncing?") == 0; } }, - func: function() { - return this.catch(function() { - rightThis = this == obj; - didCall = true; - return 5; - })(); - }, + func: function() this.catch(function() { + rightThis = this == obj; + didCall = true; + return 5; + })(), - throwy: function() { - return this.catch(function() { - rightThis = this == obj; - didCall = true; - throw 10; - })(); - }, + throwy: function() this.catch(function() { + rightThis = this == obj; + didCall = true; + throw 10; + })(), - callbacky: function() { - return this.catch(function() { - rightThis = this == obj; - didCall = true; - throw 10; - }, function(ex) { - wasTen = (ex == 10) - })(); - }, + callbacky: function() this.catch(function() { + rightThis = this == obj; + didCall = true; + throw 10; + }, function(ex) { + wasTen = (ex == 10) + })(), - lockedy: function() { - return this.catch(function() { - rightThis = this == obj; - didCall = true; - throw("Could not acquire lock."); - })(); - } + lockedy: function() this.catch(function() { + rightThis = this == obj; + didCall = true; + throw("Could not acquire lock."); + })() }; _("Make sure a normal call will call and return"); diff --git a/services/sync/tests/unit/test_utils_deferGetSet.js b/services/sync/tests/unit/test_utils_deferGetSet.js index 9d58a9873..55c0fcb0e 100644 --- a/services/sync/tests/unit/test_utils_deferGetSet.js +++ b/services/sync/tests/unit/test_utils_deferGetSet.js @@ -6,12 +6,8 @@ function run_test() { base.prototype = { dst: {}, - get a() { - return "a"; - }, - set b(val) { - this.dst.b = val + "!!!"; - } + get a() "a", + set b(val) this.dst.b = val + "!!!" }; let src = new base(); diff --git a/services/sync/tests/unit/test_utils_deriveKey.js b/services/sync/tests/unit/test_utils_deriveKey.js index 17dd889c7..e205fa9f8 100644 --- a/services/sync/tests/unit/test_utils_deriveKey.js +++ b/services/sync/tests/unit/test_utils_deriveKey.js @@ -1,7 +1,7 @@ Cu.import("resource://services-crypto/WeaveCrypto.js"); Cu.import("resource://services-sync/util.js"); -var cryptoSvc = new WeaveCrypto(); +let cryptoSvc = new WeaveCrypto(); function run_test() { if (this.gczeal) { diff --git a/services/sync/tests/unit/test_utils_lock.js b/services/sync/tests/unit/test_utils_lock.js index d1830787e..fd8a4b1f5 100644 --- a/services/sync/tests/unit/test_utils_lock.js +++ b/services/sync/tests/unit/test_utils_lock.js @@ -27,23 +27,19 @@ function run_test() { this._locked = false; }, - func: function() { - return this._lock("Test utils lock", - function() { - rightThis = this == obj; - didCall = true; - return 5; - })(); - }, + func: function() this._lock("Test utils lock", + function() { + rightThis = this == obj; + didCall = true; + return 5; + })(), - throwy: function() { - return this._lock("Test utils lock throwy", - function() { - rightThis = this == obj; - didCall = true; - this.throwy(); - })(); - } + throwy: function() this._lock("Test utils lock throwy", + function() { + rightThis = this == obj; + didCall = true; + this.throwy(); + })() }; _("Make sure a normal call will call and return"); diff --git a/services/sync/tests/unit/test_utils_notify.js b/services/sync/tests/unit/test_utils_notify.js index 5bd38da5f..c191bbfef 100644 --- a/services/sync/tests/unit/test_utils_notify.js +++ b/services/sync/tests/unit/test_utils_notify.js @@ -9,21 +9,17 @@ function run_test() { trace: function() {} }, - func: function() { - return this.notify("bar", "baz", function() { - rightThis = this == obj; - didCall = true; - return 5; - })(); - }, + func: function() this.notify("bar", "baz", function() { + rightThis = this == obj; + didCall = true; + return 5; + })(), - throwy: function() { - return this.notify("bad", "one", function() { - rightThis = this == obj; - didCall = true; - throw 10; - })(); - } + throwy: function() this.notify("bad", "one", function() { + rightThis = this == obj; + didCall = true; + throw 10; + })() }; let state = 0; diff --git a/services/sync/tests/unit/test_warn_on_truncated_response.js b/services/sync/tests/unit/test_warn_on_truncated_response.js index 1f0d87ba9..a9f070ee4 100644 --- a/services/sync/tests/unit/test_warn_on_truncated_response.js +++ b/services/sync/tests/unit/test_warn_on_truncated_response.js @@ -12,11 +12,11 @@ function run_test() { run_next_test(); } -var BODY = "response body"; +let BODY = "response body"; // contentLength needs to be longer than the response body // length in order to get a mismatch between what is sent in // the response and the content-length header value. -var contentLength = BODY.length + 1; +let contentLength = BODY.length + 1; function contentHandler(request, response) { _("Handling request."); diff --git a/services/sync/tests/unit/xpcshell.ini b/services/sync/tests/unit/xpcshell.ini index 4c0f0e7b7..dc33c0eb2 100644 --- a/services/sync/tests/unit/xpcshell.ini +++ b/services/sync/tests/unit/xpcshell.ini @@ -1,7 +1,8 @@ [DEFAULT] -head = head_appinfo.js ../../../common/tests/unit/head_helpers.js head_helpers.js head_http_server.js head_errorhandler_common.js +head = head_appinfo.js ../../../common/tests/unit/head_helpers.js head_helpers.js head_http_server.js tail = firefox-appdir = browser +skip-if = toolkit == 'gonk' support-files = addon1-search.xml bootstrap1-search.xml @@ -10,10 +11,6 @@ support-files = missing-xpi-search.xml places_v10_from_v11.sqlite rewrite-search.xml - sync_ping_schema.json - systemaddon-search.xml - !/services/common/tests/unit/head_helpers.js - !/toolkit/components/webextensions/test/xpcshell/head_sync.js # The manifest is roughly ordered from low-level to high-level. When making # systemic sweeping changes, this makes it easier to identify errors closer to @@ -39,7 +36,6 @@ support-files = # We have a number of other libraries that are pretty much standalone. [test_addon_utils.js] run-sequentially = Restarts server, can't change pref. -tags = addons [test_httpd_sync_server.js] [test_jpakeclient.js] # Bug 618233: this test produces random failures on Windows 7. @@ -56,7 +52,6 @@ skip-if = os == "win" || os == "android" # Generic Sync types. [test_browserid_identity.js] [test_collection_inc_get.js] -[test_collection_getBatched.js] [test_collections_recovery.js] [test_identity_manager.js] [test_keys.js] @@ -97,7 +92,6 @@ skip-if = os == "mac" || os == "linux" [test_service_sync_remoteSetup.js] # Bug 676978: test hangs on Android (see also testing/xpcshell/xpcshell.ini) skip-if = os == "android" -[test_service_sync_specified.js] [test_service_sync_updateEnabledEngines.js] # Bug 676978: test hangs on Android (see also testing/xpcshell/xpcshell.ini) skip-if = os == "android" @@ -109,8 +103,7 @@ skip-if = os == "mac" || os == "linux" [test_corrupt_keys.js] [test_declined.js] -[test_errorhandler_1.js] -[test_errorhandler_2.js] +[test_errorhandler.js] [test_errorhandler_filelog.js] # Bug 676978: test hangs on Android (see also testing/xpcshell/xpcshell.ini) skip-if = os == "android" @@ -121,6 +114,7 @@ skip-if = os == "android" [test_hmac_error.js] [test_interval_triggers.js] [test_node_reassignment.js] +[test_notifications.js] [test_score_triggers.js] [test_sendcredentials_controller.js] [test_status.js] @@ -136,18 +130,12 @@ skip-if = os == "android" # Finally, we test each engine. [test_addons_engine.js] run-sequentially = Hardcoded port in static files. -tags = addons [test_addons_reconciler.js] -tags = addons [test_addons_store.js] run-sequentially = Hardcoded port in static files. -tags = addons [test_addons_tracker.js] -tags = addons [test_bookmark_batch_fail.js] -[test_bookmark_duping.js] [test_bookmark_engine.js] -[test_bookmark_invalid.js] [test_bookmark_legacy_microsummaries_support.js] [test_bookmark_livemarks.js] [test_bookmark_order.js] @@ -158,13 +146,8 @@ tags = addons # Too many intermittent "ASSERTION: thread pool wasn't shutdown: '!mPool'" (bug 804479) skip-if = debug [test_bookmark_tracker.js] -requesttimeoutfactor = 4 -[test_bookmark_validator.js] [test_clients_engine.js] [test_clients_escape.js] -[test_extension_storage_crypto.js] -[test_extension_storage_engine.js] -[test_extension_storage_tracker.js] [test_forms_store.js] [test_forms_tracker.js] # Too many intermittent "ASSERTION: thread pool wasn't shutdown: '!mPool'" (bug 804479) @@ -176,21 +159,24 @@ skip-if = debug skip-if = debug [test_places_guid_downgrade.js] [test_password_store.js] -[test_password_validator.js] [test_password_tracker.js] # Too many intermittent "ASSERTION: thread pool wasn't shutdown: '!mPool'" (bug 804479) skip-if = debug [test_prefs_store.js] -support-files = prefs_test_prefs_store.js [test_prefs_tracker.js] [test_tab_engine.js] [test_tab_store.js] [test_tab_tracker.js] -[test_warn_on_truncated_response.js] -[test_postqueue.js] +[test_healthreport.js] +skip-if = ! healthreport + +[test_healthreport_migration.js] +skip-if = ! healthreport -# Synced tabs. -[test_syncedtabs.js] +[test_warn_on_truncated_response.js] -[test_telemetry.js] +# FxA migration +[test_block_sync.js] +[test_fxa_migration.js] +[test_fxa_migration_sentinel.js] -- cgit v1.2.3