/* Any copyright is dedicated to the Public Domain. http://creativecommons.org/publicdomain/zero/1.0/ */ "use strict"; const { AddonsReconciler, CHANGE_INSTALLED, CHANGE_UNINSTALLED } = ChromeUtils.importESModule( "resource://services-sync/addonsreconciler.sys.mjs" ); const { AddonsEngine } = ChromeUtils.importESModule( "resource://services-sync/engines/addons.sys.mjs" ); const { Service } = ChromeUtils.importESModule( "resource://services-sync/service.sys.mjs" ); AddonTestUtils.init(this); AddonTestUtils.createAppInfo( "xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9.2" ); AddonTestUtils.overrideCertDB(); const ADDON_ID = "addon1@tests.mozilla.org"; const XPI = AddonTestUtils.createTempWebExtensionFile({ manifest: { name: "Test 1", description: "Test Description", browser_specific_settings: { gecko: { id: ADDON_ID } }, }, }); function makeAddonsReconciler() { const log = Service.engineManager.get("addons")._log; const queueCaller = Async.asyncQueueCaller(log); return new AddonsReconciler(queueCaller); } add_task(async function setup() { await AddonTestUtils.promiseStartupManager(); Svc.Prefs.set("engine.addons", true); await Service.engineManager.register(AddonsEngine); }); add_task(async function test_defaults() { _("Ensure new objects have reasonable defaults."); let reconciler = makeAddonsReconciler(); await reconciler.ensureStateLoaded(); Assert.ok(!reconciler._listening); Assert.equal("object", typeof reconciler.addons); Assert.equal(0, Object.keys(reconciler.addons).length); Assert.equal(0, reconciler._changes.length); Assert.equal(0, reconciler._listeners.length); }); add_task(async function test_load_state_empty_file() { _("Ensure loading from a missing file results in defaults being set."); let reconciler = makeAddonsReconciler(); await reconciler.ensureStateLoaded(); let loaded = await reconciler.loadState(); Assert.ok(!loaded); Assert.equal("object", typeof reconciler.addons); Assert.equal(0, Object.keys(reconciler.addons).length); Assert.equal(0, reconciler._changes.length); }); add_task(async function test_install_detection() { _("Ensure that add-on installation results in appropriate side-effects."); let reconciler = makeAddonsReconciler(); await reconciler.ensureStateLoaded(); reconciler.startListening(); let before = new Date(); let addon = await installAddon(XPI); let after = new Date(); Assert.equal(1, Object.keys(reconciler.addons).length); Assert.ok(addon.id in reconciler.addons); let record = reconciler.addons[ADDON_ID]; const KEYS = [ "id", "guid", "enabled", "installed", "modified", "type", "scope", "foreignInstall", ]; for (let key of KEYS) { Assert.ok(key in record); Assert.notEqual(null, record[key]); } Assert.equal(addon.id, record.id); Assert.equal(addon.syncGUID, record.guid); Assert.ok(record.enabled); Assert.ok(record.installed); Assert.ok(record.modified >= before && record.modified <= after); Assert.equal("extension", record.type); Assert.ok(!record.foreignInstall); Assert.equal(1, reconciler._changes.length); let change = reconciler._changes[0]; Assert.ok(change[0] >= before && change[1] <= after); Assert.equal(CHANGE_INSTALLED, change[1]); Assert.equal(addon.id, change[2]); await uninstallAddon(addon); }); add_task(async function test_uninstall_detection() { _("Ensure that add-on uninstallation results in appropriate side-effects."); let reconciler = makeAddonsReconciler(); await reconciler.ensureStateLoaded(); reconciler.startListening(); reconciler._addons = {}; reconciler._changes = []; let addon = await installAddon(XPI); let id = addon.id; reconciler._changes = []; await uninstallAddon(addon, reconciler); Assert.equal(1, Object.keys(reconciler.addons).length); Assert.ok(id in reconciler.addons); let record = reconciler.addons[id]; Assert.ok(!record.installed); Assert.equal(1, reconciler._changes.length); let change = reconciler._changes[0]; Assert.equal(CHANGE_UNINSTALLED, change[1]); Assert.equal(id, change[2]); }); add_task(async function test_load_state_future_version() { _("Ensure loading a file from a future version results in no data loaded."); const FILENAME = "TEST_LOAD_STATE_FUTURE_VERSION"; let reconciler = makeAddonsReconciler(); await reconciler.ensureStateLoaded(); // First we populate our new file. let state = { version: 100, addons: { foo: {} }, changes: [[1, 1, "foo"]] }; // jsonSave() expects an object with ._log, so we give it a reconciler // instance. await Utils.jsonSave(FILENAME, reconciler, state); let loaded = await reconciler.loadState(FILENAME); Assert.ok(!loaded); Assert.equal("object", typeof reconciler.addons); Assert.equal(0, Object.keys(reconciler.addons).length); Assert.equal(0, reconciler._changes.length); }); add_task(async function test_prune_changes_before_date() { _("Ensure that old changes are pruned properly."); let reconciler = makeAddonsReconciler(); await reconciler.ensureStateLoaded(); reconciler._changes = []; let now = new Date(); const HOUR_MS = 1000 * 60 * 60; _("Ensure pruning an empty changes array works."); reconciler.pruneChangesBeforeDate(now); Assert.equal(0, reconciler._changes.length); let old = new Date(now.getTime() - HOUR_MS); let young = new Date(now.getTime() - 1000); reconciler._changes.push([old, CHANGE_INSTALLED, "foo"]); reconciler._changes.push([young, CHANGE_INSTALLED, "bar"]); Assert.equal(2, reconciler._changes.length); _("Ensure pruning with an old time won't delete anything."); let threshold = new Date(old.getTime() - 1); reconciler.pruneChangesBeforeDate(threshold); Assert.equal(2, reconciler._changes.length); _("Ensure pruning a single item works."); threshold = new Date(young.getTime() - 1000); reconciler.pruneChangesBeforeDate(threshold); Assert.equal(1, reconciler._changes.length); Assert.notEqual(undefined, reconciler._changes[0]); Assert.equal(young, reconciler._changes[0][0]); Assert.equal("bar", reconciler._changes[0][2]); _("Ensure pruning all changes works."); reconciler._changes.push([old, CHANGE_INSTALLED, "foo"]); reconciler.pruneChangesBeforeDate(now); Assert.equal(0, reconciler._changes.length); });