diff options
Diffstat (limited to 'services/sync/tests/unit/test_addons_store.js')
-rw-r--r-- | services/sync/tests/unit/test_addons_store.js | 539 |
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(); +}); + |