michael@0: /* Any copyright is dedicated to the Public Domain. michael@0: http://creativecommons.org/publicdomain/zero/1.0/ */ michael@0: michael@0: "use strict"; michael@0: michael@0: Cu.import("resource://gre/modules/Preferences.jsm"); michael@0: Cu.import("resource://services-sync/addonutils.js"); michael@0: Cu.import("resource://services-sync/engines/addons.js"); michael@0: Cu.import("resource://services-sync/service.js"); michael@0: Cu.import("resource://services-sync/util.js"); michael@0: michael@0: const HTTP_PORT = 8888; michael@0: michael@0: let prefs = new Preferences(); michael@0: michael@0: prefs.set("extensions.getAddons.get.url", "http://localhost:8888/search/guid:%IDS%"); michael@0: loadAddonTestFunctions(); michael@0: startupManager(); michael@0: michael@0: Service.engineManager.register(AddonsEngine); michael@0: let engine = Service.engineManager.get("addons"); michael@0: let tracker = engine._tracker; michael@0: let store = engine._store; michael@0: let reconciler = engine._reconciler; michael@0: michael@0: /** michael@0: * Create a AddonsRec for this application with the fields specified. michael@0: * michael@0: * @param id Sync GUID of record michael@0: * @param addonId ID of add-on michael@0: * @param enabled Boolean whether record is enabled michael@0: * @param deleted Boolean whether record was deleted michael@0: */ michael@0: function createRecordForThisApp(id, addonId, enabled, deleted) { michael@0: return { michael@0: id: id, michael@0: addonID: addonId, michael@0: enabled: enabled, michael@0: deleted: !!deleted, michael@0: applicationID: Services.appinfo.ID, michael@0: source: "amo" michael@0: }; michael@0: } michael@0: michael@0: function createAndStartHTTPServer(port) { michael@0: try { michael@0: let server = new HttpServer(); michael@0: michael@0: let bootstrap1XPI = ExtensionsTestPath("/addons/test_bootstrap1_1.xpi"); michael@0: michael@0: server.registerFile("/search/guid:bootstrap1%40tests.mozilla.org", michael@0: do_get_file("bootstrap1-search.xml")); michael@0: server.registerFile("/bootstrap1.xpi", do_get_file(bootstrap1XPI)); michael@0: michael@0: server.registerFile("/search/guid:missing-xpi%40tests.mozilla.org", michael@0: do_get_file("missing-xpi-search.xml")); michael@0: michael@0: server.start(port); michael@0: michael@0: return server; michael@0: } catch (ex) { michael@0: _("Got exception starting HTTP server on port " + port); michael@0: _("Error: " + Utils.exceptionStr(ex)); michael@0: do_throw(ex); michael@0: } michael@0: } michael@0: michael@0: function run_test() { michael@0: initTestLogging("Trace"); michael@0: Log.repository.getLogger("Sync.Engine.Addons").level = Log.Level.Trace; michael@0: Log.repository.getLogger("Sync.AddonsRepository").level = michael@0: Log.Level.Trace; michael@0: michael@0: reconciler.startListening(); michael@0: michael@0: // Don't flush to disk in the middle of an event listener! michael@0: // This causes test hangs on WinXP. michael@0: reconciler._shouldPersist = false; michael@0: michael@0: run_next_test(); michael@0: } michael@0: michael@0: add_test(function test_remove() { michael@0: _("Ensure removing add-ons from deleted records works."); michael@0: michael@0: let addon = installAddon("test_bootstrap1_1"); michael@0: let record = createRecordForThisApp(addon.syncGUID, addon.id, true, true); michael@0: michael@0: let failed = store.applyIncomingBatch([record]); michael@0: do_check_eq(0, failed.length); michael@0: michael@0: let newAddon = getAddonFromAddonManagerByID(addon.id); michael@0: do_check_eq(null, newAddon); michael@0: michael@0: run_next_test(); michael@0: }); michael@0: michael@0: add_test(function test_apply_enabled() { michael@0: _("Ensures that changes to the userEnabled flag apply."); michael@0: michael@0: let addon = installAddon("test_bootstrap1_1"); michael@0: do_check_true(addon.isActive); michael@0: do_check_false(addon.userDisabled); michael@0: michael@0: _("Ensure application of a disable record works as expected."); michael@0: let records = []; michael@0: records.push(createRecordForThisApp(addon.syncGUID, addon.id, false, false)); michael@0: let failed = store.applyIncomingBatch(records); michael@0: do_check_eq(0, failed.length); michael@0: addon = getAddonFromAddonManagerByID(addon.id); michael@0: do_check_true(addon.userDisabled); michael@0: records = []; michael@0: michael@0: _("Ensure enable record works as expected."); michael@0: records.push(createRecordForThisApp(addon.syncGUID, addon.id, true, false)); michael@0: failed = store.applyIncomingBatch(records); michael@0: do_check_eq(0, failed.length); michael@0: addon = getAddonFromAddonManagerByID(addon.id); michael@0: do_check_false(addon.userDisabled); michael@0: records = []; michael@0: michael@0: _("Ensure enabled state updates don't apply if the ignore pref is set."); michael@0: records.push(createRecordForThisApp(addon.syncGUID, addon.id, false, false)); michael@0: Svc.Prefs.set("addons.ignoreUserEnabledChanges", true); michael@0: failed = store.applyIncomingBatch(records); michael@0: do_check_eq(0, failed.length); michael@0: addon = getAddonFromAddonManagerByID(addon.id); michael@0: do_check_false(addon.userDisabled); michael@0: records = []; michael@0: michael@0: uninstallAddon(addon); michael@0: Svc.Prefs.reset("addons.ignoreUserEnabledChanges"); michael@0: run_next_test(); michael@0: }); michael@0: michael@0: add_test(function test_ignore_different_appid() { michael@0: _("Ensure that incoming records with a different application ID are ignored."); michael@0: michael@0: // We test by creating a record that should result in an update. michael@0: let addon = installAddon("test_bootstrap1_1"); michael@0: do_check_false(addon.userDisabled); michael@0: michael@0: let record = createRecordForThisApp(addon.syncGUID, addon.id, false, false); michael@0: record.applicationID = "FAKE_ID"; michael@0: michael@0: let failed = store.applyIncomingBatch([record]); michael@0: do_check_eq(0, failed.length); michael@0: michael@0: let newAddon = getAddonFromAddonManagerByID(addon.id); michael@0: do_check_false(addon.userDisabled); michael@0: michael@0: uninstallAddon(addon); michael@0: michael@0: run_next_test(); michael@0: }); michael@0: michael@0: add_test(function test_ignore_unknown_source() { michael@0: _("Ensure incoming records with unknown source are ignored."); michael@0: michael@0: let addon = installAddon("test_bootstrap1_1"); michael@0: michael@0: let record = createRecordForThisApp(addon.syncGUID, addon.id, false, false); michael@0: record.source = "DUMMY_SOURCE"; michael@0: michael@0: let failed = store.applyIncomingBatch([record]); michael@0: do_check_eq(0, failed.length); michael@0: michael@0: let newAddon = getAddonFromAddonManagerByID(addon.id); michael@0: do_check_false(addon.userDisabled); michael@0: michael@0: uninstallAddon(addon); michael@0: michael@0: run_next_test(); michael@0: }); michael@0: michael@0: add_test(function test_apply_uninstall() { michael@0: _("Ensures that uninstalling an add-on from a record works."); michael@0: michael@0: let addon = installAddon("test_bootstrap1_1"); michael@0: michael@0: let records = []; michael@0: records.push(createRecordForThisApp(addon.syncGUID, addon.id, true, true)); michael@0: let failed = store.applyIncomingBatch(records); michael@0: do_check_eq(0, failed.length); michael@0: michael@0: addon = getAddonFromAddonManagerByID(addon.id); michael@0: do_check_eq(null, addon); michael@0: michael@0: run_next_test(); michael@0: }); michael@0: michael@0: add_test(function test_addon_syncability() { michael@0: _("Ensure isAddonSyncable functions properly."); michael@0: michael@0: Svc.Prefs.set("addons.ignoreRepositoryChecking", true); michael@0: Svc.Prefs.set("addons.trustedSourceHostnames", michael@0: "addons.mozilla.org,other.example.com"); michael@0: michael@0: do_check_false(store.isAddonSyncable(null)); michael@0: michael@0: let addon = installAddon("test_bootstrap1_1"); michael@0: do_check_true(store.isAddonSyncable(addon)); michael@0: michael@0: let dummy = {}; michael@0: const KEYS = ["id", "syncGUID", "type", "scope", "foreignInstall"]; michael@0: for each (let k in KEYS) { michael@0: dummy[k] = addon[k]; michael@0: } michael@0: michael@0: do_check_true(store.isAddonSyncable(dummy)); michael@0: michael@0: dummy.type = "UNSUPPORTED"; michael@0: do_check_false(store.isAddonSyncable(dummy)); michael@0: dummy.type = addon.type; michael@0: michael@0: dummy.scope = 0; michael@0: do_check_false(store.isAddonSyncable(dummy)); michael@0: dummy.scope = addon.scope; michael@0: michael@0: dummy.foreignInstall = true; michael@0: do_check_false(store.isAddonSyncable(dummy)); michael@0: dummy.foreignInstall = false; michael@0: michael@0: uninstallAddon(addon); michael@0: michael@0: do_check_false(store.isSourceURITrusted(null)); michael@0: michael@0: function createURI(s) { michael@0: let service = Components.classes["@mozilla.org/network/io-service;1"] michael@0: .getService(Components.interfaces.nsIIOService); michael@0: return service.newURI(s, null, null); michael@0: } michael@0: michael@0: let trusted = [ michael@0: "https://addons.mozilla.org/foo", michael@0: "https://other.example.com/foo" michael@0: ]; michael@0: michael@0: let untrusted = [ michael@0: "http://addons.mozilla.org/foo", // non-https michael@0: "ftps://addons.mozilla.org/foo", // non-https michael@0: "https://untrusted.example.com/foo", // non-trusted hostname` michael@0: ]; michael@0: michael@0: for each (let uri in trusted) { michael@0: do_check_true(store.isSourceURITrusted(createURI(uri))); michael@0: } michael@0: michael@0: for each (let uri in untrusted) { michael@0: do_check_false(store.isSourceURITrusted(createURI(uri))); michael@0: } michael@0: michael@0: Svc.Prefs.set("addons.trustedSourceHostnames", ""); michael@0: for each (let uri in trusted) { michael@0: do_check_false(store.isSourceURITrusted(createURI(uri))); michael@0: } michael@0: michael@0: Svc.Prefs.set("addons.trustedSourceHostnames", "addons.mozilla.org"); michael@0: do_check_true(store.isSourceURITrusted(createURI("https://addons.mozilla.org/foo"))); michael@0: michael@0: Svc.Prefs.reset("addons.trustedSourceHostnames"); michael@0: michael@0: run_next_test(); michael@0: }); michael@0: michael@0: add_test(function test_ignore_hotfixes() { michael@0: _("Ensure that hotfix extensions are ignored."); michael@0: michael@0: Svc.Prefs.set("addons.ignoreRepositoryChecking", true); michael@0: michael@0: // A hotfix extension is one that has the id the same as the michael@0: // extensions.hotfix.id pref. michael@0: let prefs = new Preferences("extensions."); michael@0: michael@0: let addon = installAddon("test_bootstrap1_1"); michael@0: do_check_true(store.isAddonSyncable(addon)); michael@0: michael@0: let dummy = {}; michael@0: const KEYS = ["id", "syncGUID", "type", "scope", "foreignInstall"]; michael@0: for each (let k in KEYS) { michael@0: dummy[k] = addon[k]; michael@0: } michael@0: michael@0: // Basic sanity check. michael@0: do_check_true(store.isAddonSyncable(dummy)); michael@0: michael@0: prefs.set("hotfix.id", dummy.id); michael@0: do_check_false(store.isAddonSyncable(dummy)); michael@0: michael@0: // Verify that int values don't throw off checking. michael@0: let prefSvc = Cc["@mozilla.org/preferences-service;1"] michael@0: .getService(Ci.nsIPrefService) michael@0: .getBranch("extensions."); michael@0: // Need to delete pref before changing type. michael@0: prefSvc.deleteBranch("hotfix.id"); michael@0: prefSvc.setIntPref("hotfix.id", 0xdeadbeef); michael@0: michael@0: do_check_true(store.isAddonSyncable(dummy)); michael@0: michael@0: uninstallAddon(addon); michael@0: michael@0: Svc.Prefs.reset("addons.ignoreRepositoryChecking"); michael@0: prefs.reset("hotfix.id"); michael@0: michael@0: run_next_test(); michael@0: }); michael@0: michael@0: michael@0: add_test(function test_get_all_ids() { michael@0: _("Ensures that getAllIDs() returns an appropriate set."); michael@0: michael@0: Svc.Prefs.set("addons.ignoreRepositoryChecking", true); michael@0: michael@0: _("Installing two addons."); michael@0: let addon1 = installAddon("test_install1"); michael@0: let addon2 = installAddon("test_bootstrap1_1"); michael@0: michael@0: _("Ensure they're syncable."); michael@0: do_check_true(store.isAddonSyncable(addon1)); michael@0: do_check_true(store.isAddonSyncable(addon2)); michael@0: michael@0: let ids = store.getAllIDs(); michael@0: michael@0: do_check_eq("object", typeof(ids)); michael@0: do_check_eq(2, Object.keys(ids).length); michael@0: do_check_true(addon1.syncGUID in ids); michael@0: do_check_true(addon2.syncGUID in ids); michael@0: michael@0: addon1.install.cancel(); michael@0: uninstallAddon(addon2); michael@0: michael@0: Svc.Prefs.reset("addons.ignoreRepositoryChecking"); michael@0: run_next_test(); michael@0: }); michael@0: michael@0: add_test(function test_change_item_id() { michael@0: _("Ensures that changeItemID() works properly."); michael@0: michael@0: let addon = installAddon("test_bootstrap1_1"); michael@0: michael@0: let oldID = addon.syncGUID; michael@0: let newID = Utils.makeGUID(); michael@0: michael@0: store.changeItemID(oldID, newID); michael@0: michael@0: let newAddon = getAddonFromAddonManagerByID(addon.id); michael@0: do_check_neq(null, newAddon); michael@0: do_check_eq(newID, newAddon.syncGUID); michael@0: michael@0: uninstallAddon(newAddon); michael@0: michael@0: run_next_test(); michael@0: }); michael@0: michael@0: add_test(function test_create() { michael@0: _("Ensure creating/installing an add-on from a record works."); michael@0: michael@0: // Set this so that getInstallFromSearchResult doesn't end up michael@0: // failing the install due to an insecure source URI scheme. michael@0: Svc.Prefs.set("addons.ignoreRepositoryChecking", true); michael@0: let server = createAndStartHTTPServer(HTTP_PORT); michael@0: michael@0: let addon = installAddon("test_bootstrap1_1"); michael@0: let id = addon.id; michael@0: uninstallAddon(addon); michael@0: michael@0: let guid = Utils.makeGUID(); michael@0: let record = createRecordForThisApp(guid, id, true, false); michael@0: michael@0: let failed = store.applyIncomingBatch([record]); michael@0: do_check_eq(0, failed.length); michael@0: michael@0: let newAddon = getAddonFromAddonManagerByID(id); michael@0: do_check_neq(null, newAddon); michael@0: do_check_eq(guid, newAddon.syncGUID); michael@0: do_check_false(newAddon.userDisabled); michael@0: michael@0: uninstallAddon(newAddon); michael@0: michael@0: Svc.Prefs.reset("addons.ignoreRepositoryChecking"); michael@0: server.stop(run_next_test); michael@0: }); michael@0: michael@0: add_test(function test_create_missing_search() { michael@0: _("Ensures that failed add-on searches are handled gracefully."); michael@0: michael@0: let server = createAndStartHTTPServer(HTTP_PORT); michael@0: michael@0: // The handler for this ID is not installed, so a search should 404. michael@0: const id = "missing@tests.mozilla.org"; michael@0: let guid = Utils.makeGUID(); michael@0: let record = createRecordForThisApp(guid, id, true, false); michael@0: michael@0: let failed = store.applyIncomingBatch([record]); michael@0: do_check_eq(1, failed.length); michael@0: do_check_eq(guid, failed[0]); michael@0: michael@0: let addon = getAddonFromAddonManagerByID(id); michael@0: do_check_eq(null, addon); michael@0: michael@0: server.stop(run_next_test); michael@0: }); michael@0: michael@0: add_test(function test_create_bad_install() { michael@0: _("Ensures that add-ons without a valid install are handled gracefully."); michael@0: michael@0: let server = createAndStartHTTPServer(HTTP_PORT); michael@0: michael@0: // The handler returns a search result but the XPI will 404. michael@0: const id = "missing-xpi@tests.mozilla.org"; michael@0: let guid = Utils.makeGUID(); michael@0: let record = createRecordForThisApp(guid, id, true, false); michael@0: michael@0: let failed = store.applyIncomingBatch([record]); michael@0: do_check_eq(1, failed.length); michael@0: do_check_eq(guid, failed[0]); michael@0: michael@0: let addon = getAddonFromAddonManagerByID(id); michael@0: do_check_eq(null, addon); michael@0: michael@0: server.stop(run_next_test); michael@0: }); michael@0: michael@0: add_test(function test_wipe() { michael@0: _("Ensures that wiping causes add-ons to be uninstalled."); michael@0: michael@0: let addon1 = installAddon("test_bootstrap1_1"); michael@0: michael@0: Svc.Prefs.set("addons.ignoreRepositoryChecking", true); michael@0: store.wipe(); michael@0: michael@0: let addon = getAddonFromAddonManagerByID(addon1.id); michael@0: do_check_eq(null, addon); michael@0: michael@0: Svc.Prefs.reset("addons.ignoreRepositoryChecking"); michael@0: michael@0: run_next_test(); michael@0: }); michael@0: michael@0: add_test(function test_wipe_and_install() { michael@0: _("Ensure wipe followed by install works."); michael@0: michael@0: // This tests the reset sync flow where remote data is replaced by local. The michael@0: // receiving client will see a wipe followed by a record which should undo michael@0: // the wipe. michael@0: let installed = installAddon("test_bootstrap1_1"); michael@0: michael@0: let record = createRecordForThisApp(installed.syncGUID, installed.id, true, michael@0: false); michael@0: michael@0: Svc.Prefs.set("addons.ignoreRepositoryChecking", true); michael@0: store.wipe(); michael@0: michael@0: let deleted = getAddonFromAddonManagerByID(installed.id); michael@0: do_check_null(deleted); michael@0: michael@0: // Re-applying the record can require re-fetching the XPI. michael@0: let server = createAndStartHTTPServer(HTTP_PORT); michael@0: michael@0: store.applyIncoming(record); michael@0: michael@0: let fetched = getAddonFromAddonManagerByID(record.addonID); michael@0: do_check_true(!!fetched); michael@0: michael@0: Svc.Prefs.reset("addons.ignoreRepositoryChecking"); michael@0: server.stop(run_next_test); michael@0: }); michael@0: michael@0: add_test(function cleanup() { michael@0: // There's an xpcom-shutdown hook for this, but let's give this a shot. michael@0: reconciler.stopListening(); michael@0: run_next_test(); michael@0: }); michael@0: