summaryrefslogtreecommitdiffstats
path: root/services/sync/tests/unit/test_addons_store.js
diff options
context:
space:
mode:
Diffstat (limited to 'services/sync/tests/unit/test_addons_store.js')
-rw-r--r--services/sync/tests/unit/test_addons_store.js539
1 files changed, 539 insertions, 0 deletions
diff --git a/services/sync/tests/unit/test_addons_store.js b/services/sync/tests/unit/test_addons_store.js
new file mode 100644
index 000000000..b52cfab31
--- /dev/null
+++ b/services/sync/tests/unit/test_addons_store.js
@@ -0,0 +1,539 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"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();
+
+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;
+
+/**
+ * Create a AddonsRec for this application with the fields specified.
+ *
+ * @param id Sync GUID of record
+ * @param addonId ID of add-on
+ * @param enabled Boolean whether record is enabled
+ * @param deleted Boolean whether record was deleted
+ */
+function createRecordForThisApp(id, addonId, enabled, deleted) {
+ return {
+ id: id,
+ addonID: addonId,
+ enabled: enabled,
+ deleted: !!deleted,
+ applicationID: Services.appinfo.ID,
+ source: "amo"
+ };
+}
+
+function createAndStartHTTPServer(port) {
+ try {
+ let server = new HttpServer();
+
+ let bootstrap1XPI = ExtensionsTestPath("/addons/test_bootstrap1_1.xpi");
+
+ server.registerFile("/search/guid:bootstrap1%40tests.mozilla.org",
+ do_get_file("bootstrap1-search.xml"));
+ server.registerFile("/bootstrap1.xpi", do_get_file(bootstrap1XPI));
+
+ 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));
+ do_throw(ex);
+ }
+}
+
+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;
+
+ reconciler.startListening();
+
+ // Don't flush to disk in the middle of an event listener!
+ // This causes test hangs on WinXP.
+ reconciler._shouldPersist = false;
+
+ run_next_test();
+}
+
+add_test(function test_remove() {
+ _("Ensure removing add-ons from deleted records works.");
+
+ let addon = installAddon("test_bootstrap1_1");
+ let record = createRecordForThisApp(addon.syncGUID, addon.id, true, true);
+
+ let failed = store.applyIncomingBatch([record]);
+ do_check_eq(0, failed.length);
+
+ let newAddon = getAddonFromAddonManagerByID(addon.id);
+ do_check_eq(null, newAddon);
+
+ run_next_test();
+});
+
+add_test(function test_apply_enabled() {
+ _("Ensures that changes to the userEnabled flag apply.");
+
+ let addon = installAddon("test_bootstrap1_1");
+ do_check_true(addon.isActive);
+ do_check_false(addon.userDisabled);
+
+ _("Ensure application of a disable record works as expected.");
+ let records = [];
+ records.push(createRecordForThisApp(addon.syncGUID, addon.id, false, false));
+ let failed = store.applyIncomingBatch(records);
+ do_check_eq(0, failed.length);
+ addon = getAddonFromAddonManagerByID(addon.id);
+ do_check_true(addon.userDisabled);
+ records = [];
+
+ _("Ensure enable record works as expected.");
+ records.push(createRecordForThisApp(addon.syncGUID, addon.id, true, false));
+ failed = store.applyIncomingBatch(records);
+ do_check_eq(0, failed.length);
+ addon = getAddonFromAddonManagerByID(addon.id);
+ do_check_false(addon.userDisabled);
+ records = [];
+
+ _("Ensure enabled state updates don't apply if the ignore pref is set.");
+ records.push(createRecordForThisApp(addon.syncGUID, addon.id, false, false));
+ Svc.Prefs.set("addons.ignoreUserEnabledChanges", true);
+ failed = store.applyIncomingBatch(records);
+ do_check_eq(0, failed.length);
+ addon = getAddonFromAddonManagerByID(addon.id);
+ do_check_false(addon.userDisabled);
+ records = [];
+
+ uninstallAddon(addon);
+ Svc.Prefs.reset("addons.ignoreUserEnabledChanges");
+ run_next_test();
+});
+
+add_test(function test_ignore_different_appid() {
+ _("Ensure that incoming records with a different application ID are ignored.");
+
+ // We test by creating a record that should result in an update.
+ let addon = installAddon("test_bootstrap1_1");
+ do_check_false(addon.userDisabled);
+
+ let record = createRecordForThisApp(addon.syncGUID, addon.id, false, false);
+ record.applicationID = "FAKE_ID";
+
+ let failed = store.applyIncomingBatch([record]);
+ do_check_eq(0, failed.length);
+
+ let newAddon = getAddonFromAddonManagerByID(addon.id);
+ do_check_false(addon.userDisabled);
+
+ uninstallAddon(addon);
+
+ run_next_test();
+});
+
+add_test(function test_ignore_unknown_source() {
+ _("Ensure incoming records with unknown source are ignored.");
+
+ let addon = installAddon("test_bootstrap1_1");
+
+ let record = createRecordForThisApp(addon.syncGUID, addon.id, false, false);
+ record.source = "DUMMY_SOURCE";
+
+ let failed = store.applyIncomingBatch([record]);
+ do_check_eq(0, failed.length);
+
+ let newAddon = getAddonFromAddonManagerByID(addon.id);
+ do_check_false(addon.userDisabled);
+
+ uninstallAddon(addon);
+
+ run_next_test();
+});
+
+add_test(function test_apply_uninstall() {
+ _("Ensures that uninstalling an add-on from a record works.");
+
+ let addon = installAddon("test_bootstrap1_1");
+
+ let records = [];
+ records.push(createRecordForThisApp(addon.syncGUID, addon.id, true, true));
+ let failed = store.applyIncomingBatch(records);
+ do_check_eq(0, failed.length);
+
+ addon = getAddonFromAddonManagerByID(addon.id);
+ do_check_eq(null, addon);
+
+ run_next_test();
+});
+
+add_test(function test_addon_syncability() {
+ _("Ensure isAddonSyncable functions properly.");
+
+ Svc.Prefs.set("addons.trustedSourceHostnames",
+ "addons.mozilla.org,other.example.com");
+
+ do_check_false(store.isAddonSyncable(null));
+
+ let addon = installAddon("test_bootstrap1_1");
+ do_check_true(store.isAddonSyncable(addon));
+
+ let dummy = {};
+ const KEYS = ["id", "syncGUID", "type", "scope", "foreignInstall", "isSyncable"];
+ for (let k of KEYS) {
+ dummy[k] = addon[k];
+ }
+
+ do_check_true(store.isAddonSyncable(dummy));
+
+ dummy.type = "UNSUPPORTED";
+ do_check_false(store.isAddonSyncable(dummy));
+ dummy.type = addon.type;
+
+ dummy.scope = 0;
+ 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;
+
+ uninstallAddon(addon);
+
+ do_check_false(store.isSourceURITrusted(null));
+
+ function createURI(s) {
+ let service = Components.classes["@mozilla.org/network/io-service;1"]
+ .getService(Components.interfaces.nsIIOService);
+ return service.newURI(s, null, null);
+ }
+
+ let trusted = [
+ "https://addons.mozilla.org/foo",
+ "https://other.example.com/foo"
+ ];
+
+ let untrusted = [
+ "http://addons.mozilla.org/foo", // non-https
+ "ftps://addons.mozilla.org/foo", // non-https
+ "https://untrusted.example.com/foo", // non-trusted hostname`
+ ];
+
+ for (let uri of trusted) {
+ do_check_true(store.isSourceURITrusted(createURI(uri)));
+ }
+
+ for (let uri of untrusted) {
+ do_check_false(store.isSourceURITrusted(createURI(uri)));
+ }
+
+ Svc.Prefs.set("addons.trustedSourceHostnames", "");
+ for (let uri of trusted) {
+ do_check_false(store.isSourceURITrusted(createURI(uri)));
+ }
+
+ Svc.Prefs.set("addons.trustedSourceHostnames", "addons.mozilla.org");
+ do_check_true(store.isSourceURITrusted(createURI("https://addons.mozilla.org/foo")));
+
+ Svc.Prefs.reset("addons.trustedSourceHostnames");
+
+ run_next_test();
+});
+
+add_test(function test_ignore_hotfixes() {
+ _("Ensure that hotfix extensions are ignored.");
+
+ // A hotfix extension is one that has the id the same as the
+ // extensions.hotfix.id pref.
+ let prefs = new Preferences("extensions.");
+
+ let addon = installAddon("test_bootstrap1_1");
+ do_check_true(store.isAddonSyncable(addon));
+
+ let dummy = {};
+ const KEYS = ["id", "syncGUID", "type", "scope", "foreignInstall", "isSyncable"];
+ for (let k of KEYS) {
+ dummy[k] = addon[k];
+ }
+
+ // Basic sanity check.
+ do_check_true(store.isAddonSyncable(dummy));
+
+ prefs.set("hotfix.id", dummy.id);
+ do_check_false(store.isAddonSyncable(dummy));
+
+ // Verify that int values don't throw off checking.
+ let prefSvc = Cc["@mozilla.org/preferences-service;1"]
+ .getService(Ci.nsIPrefService)
+ .getBranch("extensions.");
+ // Need to delete pref before changing type.
+ prefSvc.deleteBranch("hotfix.id");
+ prefSvc.setIntPref("hotfix.id", 0xdeadbeef);
+
+ do_check_true(store.isAddonSyncable(dummy));
+
+ uninstallAddon(addon);
+
+ prefs.reset("hotfix.id");
+
+ run_next_test();
+});
+
+
+add_test(function test_get_all_ids() {
+ _("Ensures that getAllIDs() returns an appropriate set.");
+
+ _("Installing two addons.");
+ let addon1 = installAddon("test_install1");
+ let addon2 = installAddon("test_bootstrap1_1");
+
+ _("Ensure they're syncable.");
+ do_check_true(store.isAddonSyncable(addon1));
+ do_check_true(store.isAddonSyncable(addon2));
+
+ let ids = store.getAllIDs();
+
+ do_check_eq("object", typeof(ids));
+ do_check_eq(2, Object.keys(ids).length);
+ do_check_true(addon1.syncGUID in ids);
+ do_check_true(addon2.syncGUID in ids);
+
+ addon1.install.cancel();
+ uninstallAddon(addon2);
+
+ run_next_test();
+});
+
+add_test(function test_change_item_id() {
+ _("Ensures that changeItemID() works properly.");
+
+ let addon = installAddon("test_bootstrap1_1");
+
+ let oldID = addon.syncGUID;
+ let newID = Utils.makeGUID();
+
+ store.changeItemID(oldID, newID);
+
+ let newAddon = getAddonFromAddonManagerByID(addon.id);
+ do_check_neq(null, newAddon);
+ do_check_eq(newID, newAddon.syncGUID);
+
+ uninstallAddon(newAddon);
+
+ run_next_test();
+});
+
+add_test(function test_create() {
+ _("Ensure creating/installing an add-on from a record works.");
+
+ let server = createAndStartHTTPServer(HTTP_PORT);
+
+ let addon = installAddon("test_bootstrap1_1");
+ let id = addon.id;
+ uninstallAddon(addon);
+
+ let guid = Utils.makeGUID();
+ let record = createRecordForThisApp(guid, id, true, false);
+
+ let failed = store.applyIncomingBatch([record]);
+ do_check_eq(0, failed.length);
+
+ let newAddon = getAddonFromAddonManagerByID(id);
+ do_check_neq(null, newAddon);
+ do_check_eq(guid, newAddon.syncGUID);
+ do_check_false(newAddon.userDisabled);
+
+ uninstallAddon(newAddon);
+
+ server.stop(run_next_test);
+});
+
+add_test(function test_create_missing_search() {
+ _("Ensures that failed add-on searches are handled gracefully.");
+
+ let server = createAndStartHTTPServer(HTTP_PORT);
+
+ // The handler for this ID is not installed, so a search should 404.
+ const id = "missing@tests.mozilla.org";
+ let guid = Utils.makeGUID();
+ let record = createRecordForThisApp(guid, id, true, false);
+
+ let failed = store.applyIncomingBatch([record]);
+ do_check_eq(1, failed.length);
+ do_check_eq(guid, failed[0]);
+
+ let addon = getAddonFromAddonManagerByID(id);
+ do_check_eq(null, addon);
+
+ server.stop(run_next_test);
+});
+
+add_test(function test_create_bad_install() {
+ _("Ensures that add-ons without a valid install are handled gracefully.");
+
+ let server = createAndStartHTTPServer(HTTP_PORT);
+
+ // The handler returns a search result but the XPI will 404.
+ const id = "missing-xpi@tests.mozilla.org";
+ let guid = Utils.makeGUID();
+ 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);
+
+ let addon = getAddonFromAddonManagerByID(id);
+ do_check_eq(null, addon);
+
+ 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");
+
+ store.wipe();
+
+ let addon = getAddonFromAddonManagerByID(addon1.id);
+ do_check_eq(null, addon);
+
+ run_next_test();
+});
+
+add_test(function test_wipe_and_install() {
+ _("Ensure wipe followed by install works.");
+
+ // This tests the reset sync flow where remote data is replaced by local. The
+ // receiving client will see a wipe followed by a record which should undo
+ // the wipe.
+ let installed = installAddon("test_bootstrap1_1");
+
+ let record = createRecordForThisApp(installed.syncGUID, installed.id, true,
+ false);
+
+ store.wipe();
+
+ let deleted = getAddonFromAddonManagerByID(installed.id);
+ do_check_null(deleted);
+
+ // Re-applying the record can require re-fetching the XPI.
+ let server = createAndStartHTTPServer(HTTP_PORT);
+
+ store.applyIncoming(record);
+
+ let fetched = getAddonFromAddonManagerByID(record.addonID);
+ do_check_true(!!fetched);
+
+ server.stop(run_next_test);
+});
+
+add_test(function cleanup() {
+ // There's an xpcom-shutdown hook for this, but let's give this a shot.
+ reconciler.stopListening();
+ run_next_test();
+});
+