/* Any copyright is dedicated to the Public Domain. * http://creativecommons.org/publicdomain/zero/1.0/ */ // Tests caching in AddonRepository.jsm Components.utils.import("resource://gre/modules/AddonRepository.jsm"); do_load_httpd_js(); let gServer; const PORT = 4444; const BASE_URL = "http://localhost:" + PORT; const PREF_GETADDONS_CACHE_ENABLED = "extensions.getAddons.cache.enabled"; const PREF_GETADDONS_BYIDS = "extensions.getAddons.get.url"; const GETADDONS_RESULTS = BASE_URL + "/data/test_AddonRepository_cache.xml"; const GETADDONS_EMPTY = BASE_URL + "/data/test_AddonRepository_empty.xml"; const GETADDONS_FAILED = BASE_URL + "/data/test_AddonRepository_failed.xml"; const FILE_DATABASE = "addons.sqlite"; const ADDON_NAMES = ["test_AddonRepository_1", "test_AddonRepository_2", "test_AddonRepository_3"]; const ADDON_IDS = ADDON_NAMES.map(function(aName) aName + "@tests.mozilla.org"); const ADDON_FILES = ADDON_NAMES.map(do_get_addon); const PREF_ADDON0_CACHE_ENABLED = "extensions." + ADDON_IDS[0] + ".getAddons.cache.enabled"; const PREF_ADDON1_CACHE_ENABLED = "extensions." + ADDON_IDS[1] + ".getAddons.cache.enabled"; // Properties of an individual add-on that should be checked // Note: size and updateDate are checked separately const ADDON_PROPERTIES = ["id", "type", "name", "version", "creator", "developers", "translators", "contributors", "description", "fullDescription", "developerComments", "eula", "iconURL", "screenshots", "homepageURL", "supportURL", "optionsURL", "aboutURL", "contributionURL", "contributionAmount", "averageRating", "reviewCount", "reviewURL", "totalDownloads", "weeklyDownloads", "dailyUsers", "sourceURI", "repositoryStatus"]; // The size and updateDate properties are annoying to test for XPI add-ons. // However, since we only care about whether the repository value vs. the // XPI value is used, we can just test if the property value matches // the repository value const REPOSITORY_SIZE = 9; const REPOSITORY_UPDATEDATE = 9; // Get the URI of a subfile locating directly in the folder of // the add-on corresponding to the specified id function get_subfile_uri(aId, aFilename) { let file = gProfD.clone(); file.append("extensions"); return do_get_addon_root_uri(file, aId) + aFilename; } // Expected repository add-ons const REPOSITORY_ADDONS = [{ id: ADDON_IDS[0], type: "extension", name: "Repo Add-on 1", version: "2.1", creator: { name: "Repo Add-on 1 - Creator", url: BASE_URL + "/repo/1/creator.html" }, developers: [{ name: "Repo Add-on 1 - First Developer", url: BASE_URL + "/repo/1/firstDeveloper.html" }, { name: "Repo Add-on 1 - Second Developer", url: BASE_URL + "/repo/1/secondDeveloper.html" }], description: "Repo Add-on 1 - Description\nSecond line", fullDescription: "Repo Add-on 1 - Full Description & some extra", developerComments: "Repo Add-on 1\nDeveloper Comments", eula: "Repo Add-on 1 - EULA", iconURL: BASE_URL + "/repo/1/icon.png", homepageURL: BASE_URL + "/repo/1/homepage.html", supportURL: BASE_URL + "/repo/1/support.html", contributionURL: BASE_URL + "/repo/1/meetDevelopers.html", contributionAmount: "$11.11", averageRating: 1, reviewCount: 1111, reviewURL: BASE_URL + "/repo/1/review.html", totalDownloads: 2221, weeklyDownloads: 3331, dailyUsers: 4441, sourceURI: BASE_URL + "/repo/1/install.xpi", repositoryStatus: 4 }, { id: ADDON_IDS[1], type: "theme", name: "Repo Add-on 2", version: "2.2", creator: { name: "Repo Add-on 2 - Creator", url: BASE_URL + "/repo/2/creator.html" }, developers: [{ name: "Repo Add-on 2 - First Developer", url: BASE_URL + "/repo/2/firstDeveloper.html" }, { name: "Repo Add-on 2 - Second Developer", url: BASE_URL + "/repo/2/secondDeveloper.html" }], description: "Repo Add-on 2 - Description", fullDescription: "Repo Add-on 2 - Full Description", developerComments: "Repo Add-on 2 - Developer Comments", eula: "Repo Add-on 2 - EULA", iconURL: BASE_URL + "/repo/2/icon.png", screenshots: [{ url: BASE_URL + "/repo/2/firstFull.png", thumbnailURL: BASE_URL + "/repo/2/firstThumbnail.png", caption: "Repo Add-on 2 - First Caption" } , { url: BASE_URL + "/repo/2/secondFull.png", thumbnailURL: BASE_URL + "/repo/2/secondThumbnail.png", caption: "Repo Add-on 2 - Second Caption" }], homepageURL: BASE_URL + "/repo/2/homepage.html", supportURL: BASE_URL + "/repo/2/support.html", contributionURL: BASE_URL + "/repo/2/meetDevelopers.html", contributionAmount: "$11.12", averageRating: 2, reviewCount: 1112, reviewURL: BASE_URL + "/repo/2/review.html", totalDownloads: 2222, weeklyDownloads: 3332, dailyUsers: 4442, sourceURI: BASE_URL + "/repo/2/install.xpi", repositoryStatus: 9 }, { id: ADDON_IDS[2], name: "Repo Add-on 3", version: "2.3", iconURL: BASE_URL + "/repo/3/icon.png", screenshots: [{ url: BASE_URL + "/repo/3/firstFull.png", thumbnailURL: BASE_URL + "/repo/3/firstThumbnail.png", caption: "Repo Add-on 3 - First Caption" } , { url: BASE_URL + "/repo/3/secondFull.png", thumbnailURL: BASE_URL + "/repo/3/secondThumbnail.png", caption: "Repo Add-on 3 - Second Caption" }] }]; // Expected add-ons when not using cache const WITHOUT_CACHE = [{ id: ADDON_IDS[0], type: "extension", name: "XPI Add-on 1", version: "1.1", creator: { name: "XPI Add-on 1 - Creator" }, developers: [{ name: "XPI Add-on 1 - First Developer" }, { name: "XPI Add-on 1 - Second Developer" }], translators: [{ name: "XPI Add-on 1 - First Translator" }, { name: "XPI Add-on 1 - Second Translator" }], contributors: [{ name: "XPI Add-on 1 - First Contributor" }, { name: "XPI Add-on 1 - Second Contributor" }], description: "XPI Add-on 1 - Description", iconURL: BASE_URL + "/xpi/1/icon.png", homepageURL: BASE_URL + "/xpi/1/homepage.html", optionsURL: BASE_URL + "/xpi/1/options.html", aboutURL: BASE_URL + "/xpi/1/about.html", sourceURI: NetUtil.newURI(ADDON_FILES[0]).spec }, { id: ADDON_IDS[1], type: "theme", name: "XPI Add-on 2", version: "1.2", sourceURI: NetUtil.newURI(ADDON_FILES[1]).spec }, { id: ADDON_IDS[2], type: "theme", name: "XPI Add-on 3", version: "1.3", get iconURL () { return get_subfile_uri(ADDON_IDS[2], "icon.png"); }, screenshots: [{ get url () { return get_subfile_uri(ADDON_IDS[2], "preview.png"); } }], sourceURI: NetUtil.newURI(ADDON_FILES[2]).spec }]; // Expected add-ons when using cache const WITH_CACHE = [{ id: ADDON_IDS[0], type: "extension", name: "XPI Add-on 1", version: "1.1", creator: { name: "Repo Add-on 1 - Creator", url: BASE_URL + "/repo/1/creator.html" }, developers: [{ name: "XPI Add-on 1 - First Developer" }, { name: "XPI Add-on 1 - Second Developer" }], translators: [{ name: "XPI Add-on 1 - First Translator" }, { name: "XPI Add-on 1 - Second Translator" }], contributors: [{ name: "XPI Add-on 1 - First Contributor" }, { name: "XPI Add-on 1 - Second Contributor" }], description: "XPI Add-on 1 - Description", fullDescription: "Repo Add-on 1 - Full Description & some extra", developerComments: "Repo Add-on 1\nDeveloper Comments", eula: "Repo Add-on 1 - EULA", iconURL: BASE_URL + "/xpi/1/icon.png", homepageURL: BASE_URL + "/xpi/1/homepage.html", supportURL: BASE_URL + "/repo/1/support.html", optionsURL: BASE_URL + "/xpi/1/options.html", aboutURL: BASE_URL + "/xpi/1/about.html", contributionURL: BASE_URL + "/repo/1/meetDevelopers.html", contributionAmount: "$11.11", averageRating: 1, reviewCount: 1111, reviewURL: BASE_URL + "/repo/1/review.html", totalDownloads: 2221, weeklyDownloads: 3331, dailyUsers: 4441, sourceURI: NetUtil.newURI(ADDON_FILES[0]).spec, repositoryStatus: 4 }, { id: ADDON_IDS[1], type: "theme", name: "XPI Add-on 2", version: "1.2", creator: { name: "Repo Add-on 2 - Creator", url: BASE_URL + "/repo/2/creator.html" }, developers: [{ name: "Repo Add-on 2 - First Developer", url: BASE_URL + "/repo/2/firstDeveloper.html" }, { name: "Repo Add-on 2 - Second Developer", url: BASE_URL + "/repo/2/secondDeveloper.html" }], description: "Repo Add-on 2 - Description", fullDescription: "Repo Add-on 2 - Full Description", developerComments: "Repo Add-on 2 - Developer Comments", eula: "Repo Add-on 2 - EULA", iconURL: BASE_URL + "/repo/2/icon.png", screenshots: [{ url: BASE_URL + "/repo/2/firstFull.png", thumbnailURL: BASE_URL + "/repo/2/firstThumbnail.png", caption: "Repo Add-on 2 - First Caption" } , { url: BASE_URL + "/repo/2/secondFull.png", thumbnailURL: BASE_URL + "/repo/2/secondThumbnail.png", caption: "Repo Add-on 2 - Second Caption" }], homepageURL: BASE_URL + "/repo/2/homepage.html", supportURL: BASE_URL + "/repo/2/support.html", contributionURL: BASE_URL + "/repo/2/meetDevelopers.html", contributionAmount: "$11.12", averageRating: 2, reviewCount: 1112, reviewURL: BASE_URL + "/repo/2/review.html", totalDownloads: 2222, weeklyDownloads: 3332, dailyUsers: 4442, sourceURI: NetUtil.newURI(ADDON_FILES[1]).spec, repositoryStatus: 9 }, { id: ADDON_IDS[2], type: "theme", name: "XPI Add-on 3", version: "1.3", get iconURL () { return get_subfile_uri(ADDON_IDS[2], "icon.png"); }, screenshots: [{ url: BASE_URL + "/repo/3/firstFull.png", thumbnailURL: BASE_URL + "/repo/3/firstThumbnail.png", caption: "Repo Add-on 3 - First Caption" } , { url: BASE_URL + "/repo/3/secondFull.png", thumbnailURL: BASE_URL + "/repo/3/secondThumbnail.png", caption: "Repo Add-on 3 - Second Caption" }], sourceURI: NetUtil.newURI(ADDON_FILES[2]).spec }]; /* * Trigger an AddonManager background update check * * @param aCallback * Callback to call once the background update is complete */ function trigger_background_update(aCallback) { Services.obs.addObserver({ observe: function(aSubject, aTopic, aData) { Services.obs.removeObserver(this, "addons-background-update-complete"); aCallback(); } }, "addons-background-update-complete", false); gInternalManager.notify(null); } /* * Check whether or not the add-ons database exists * * @param aExpectedExists * Whether or not the database is expected to exist */ function check_database_exists(aExpectedExists) { let file = gProfD.clone(); file.append(FILE_DATABASE); do_check_eq(file.exists(), aExpectedExists); } /* * Check the actual add-on results against the expected add-on results * * @param aActualAddons * The array of actual add-ons to check * @param aExpectedAddons * The array of expected add-ons to check against * @param aFromRepository * An optional boolean representing if the add-ons are from * the repository */ function check_results(aActualAddons, aExpectedAddons, aFromRepository) { aFromRepository = !!aFromRepository; do_check_addons(aActualAddons, aExpectedAddons, ADDON_PROPERTIES); // Separately test size and updateDate (they should only be equal to the // REPOSITORY values if they are from the repository) aActualAddons.forEach(function(aActualAddon) { if (aActualAddon.size) do_check_eq(aActualAddon.size === REPOSITORY_SIZE, aFromRepository); if (aActualAddon.updateDate) { let time = aActualAddon.updateDate.getTime(); do_check_eq(time === 1000 * REPOSITORY_UPDATEDATE, aFromRepository); } }); } /* * Check the add-ons in the cache. This function also tests * AddonRepository.getCachedAddonByID() * * @param aExpectedToFind * An array of booleans representing which REPOSITORY_ADDONS are * expected to be found in the cache * @param aExpectedImmediately * A boolean representing if results from the cache are expected * immediately. Results are not immediate if the cache has not been * initialized yet. * @param aCallback * A callback to call once the checks are complete */ function check_cache(aExpectedToFind, aExpectedImmediately, aCallback) { do_check_eq(aExpectedToFind.length, REPOSITORY_ADDONS.length); let pendingAddons = REPOSITORY_ADDONS.length; let immediatelyFound = true; for (let i = 0; i < REPOSITORY_ADDONS.length; i++) { let expected = aExpectedToFind[i] ? REPOSITORY_ADDONS[i] : null; AddonRepository.getCachedAddonByID(REPOSITORY_ADDONS[i].id, function(aAddon) { do_check_eq(immediatelyFound, aExpectedImmediately); if (expected == null) do_check_eq(aAddon, null); else check_results([aAddon], [expected], true); if (--pendingAddons == 0) aCallback(); }); } immediatelyFound = false; } /* * Check an initialized cache by checking the cache, then restarting the * manager, and checking the cache. This checks that the cache is consistent * across manager restarts. * * @param aExpectedToFind * An array of booleans representing which REPOSITORY_ADDONS are * expected to be found in the cache * @param aCallback * A callback to call once the checks are complete */ function check_initialized_cache(aExpectedToFind, aCallback) { check_cache(aExpectedToFind, true, function() { restartManager(); // If cache is disabled, then expect results immediately let cacheEnabled = Services.prefs.getBoolPref(PREF_GETADDONS_CACHE_ENABLED); check_cache(aExpectedToFind, !cacheEnabled, aCallback); }); } function run_test() { // Setup for test do_test_pending(); createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9"); startupManager(); // Install XPI add-ons installAllFiles(ADDON_FILES, function() { restartManager(); gServer = new nsHttpServer(); gServer.registerDirectory("/data/", do_get_file("data")); gServer.start(PORT); run_test_1(); }); } function end_test() { gServer.stop(do_test_finished); } // Tests AddonRepository.cacheEnabled function run_test_1() { Services.prefs.setBoolPref(PREF_GETADDONS_CACHE_ENABLED, false); do_check_false(AddonRepository.cacheEnabled); Services.prefs.setBoolPref(PREF_GETADDONS_CACHE_ENABLED, true); do_check_true(AddonRepository.cacheEnabled); run_test_2(); } // Tests that the cache and database begin as empty function run_test_2() { check_database_exists(false); check_cache([false, false, false], false, run_test_3); } // Tests repopulateCache when the search fails function run_test_3() { check_database_exists(true); Services.prefs.setBoolPref(PREF_GETADDONS_CACHE_ENABLED, true); Services.prefs.setCharPref(PREF_GETADDONS_BYIDS, GETADDONS_FAILED); AddonRepository.repopulateCache(ADDON_IDS, function() { check_initialized_cache([false, false, false], run_test_4); }); } // Tests repopulateCache when search returns no results function run_test_4() { Services.prefs.setCharPref(PREF_GETADDONS_BYIDS, GETADDONS_EMPTY); AddonRepository.repopulateCache(ADDON_IDS, function() { check_initialized_cache([false, false, false], run_test_5); }); } // Tests repopulateCache when search returns results function run_test_5() { Services.prefs.setCharPref(PREF_GETADDONS_BYIDS, GETADDONS_RESULTS); AddonRepository.repopulateCache(ADDON_IDS, function() { check_initialized_cache([true, true, true], run_test_5_1); }); } // Tests repopulateCache when caching is disabled for a single add-on function run_test_5_1() { Services.prefs.setBoolPref(PREF_ADDON0_CACHE_ENABLED, false); AddonRepository.repopulateCache(ADDON_IDS, function() { // Reset pref for next test Services.prefs.setBoolPref(PREF_ADDON0_CACHE_ENABLED, true); check_initialized_cache([false, true, true], run_test_6); }); } // Tests repopulateCache when caching is disabled function run_test_6() { check_database_exists(true); Services.prefs.setBoolPref(PREF_GETADDONS_CACHE_ENABLED, false); AddonRepository.repopulateCache(ADDON_IDS, function() { // Database should have been deleted check_database_exists(false); Services.prefs.setBoolPref(PREF_GETADDONS_CACHE_ENABLED, true); check_cache([false, false, false], false, run_test_7); }); } // Tests cacheAddons when the search fails function run_test_7() { check_database_exists(true); Services.prefs.setCharPref(PREF_GETADDONS_BYIDS, GETADDONS_FAILED); AddonRepository.cacheAddons(ADDON_IDS, function() { check_initialized_cache([false, false, false], run_test_8); }); } // Tests cacheAddons when the search returns no results function run_test_8() { Services.prefs.setCharPref(PREF_GETADDONS_BYIDS, GETADDONS_EMPTY); AddonRepository.cacheAddons(ADDON_IDS, function() { check_initialized_cache([false, false, false], run_test_9); }); } // Tests cacheAddons for a single add-on when search returns results function run_test_9() { Services.prefs.setCharPref(PREF_GETADDONS_BYIDS, GETADDONS_RESULTS); AddonRepository.cacheAddons([ADDON_IDS[0]], function() { check_initialized_cache([true, false, false], run_test_9_1); }); } // Tests cacheAddons when caching is disabled for a single add-on function run_test_9_1() { Services.prefs.setBoolPref(PREF_ADDON1_CACHE_ENABLED, false); AddonRepository.cacheAddons(ADDON_IDS, function() { // Reset pref for next test Services.prefs.setBoolPref(PREF_ADDON1_CACHE_ENABLED, true); check_initialized_cache([true, false, true], run_test_10); }); } // Tests cacheAddons for multiple add-ons, some already in the cache, function run_test_10() { AddonRepository.cacheAddons(ADDON_IDS, function() { check_initialized_cache([true, true, true], run_test_11); }); } // Tests cacheAddons when caching is disabled function run_test_11() { check_database_exists(true); Services.prefs.setBoolPref(PREF_GETADDONS_CACHE_ENABLED, false); AddonRepository.cacheAddons(ADDON_IDS, function() { // Database deleted for repopulateCache, not cacheAddons check_database_exists(true); Services.prefs.setBoolPref(PREF_GETADDONS_CACHE_ENABLED, true); check_initialized_cache([true, true, true], run_test_12); }); } // Tests that XPI add-ons do not use any of the repository properties if // caching is disabled, even if there are repository properties available function run_test_12() { check_database_exists(true); Services.prefs.setBoolPref(PREF_GETADDONS_CACHE_ENABLED, false); Services.prefs.setCharPref(PREF_GETADDONS_BYIDS, GETADDONS_RESULTS); AddonManager.getAddonsByIDs(ADDON_IDS, function(aAddons) { check_results(aAddons, WITHOUT_CACHE); run_test_13(); }); } // Tests that a background update with caching disabled deletes the add-ons // database, and that XPI add-ons still do not use any of repository properties function run_test_13() { check_database_exists(true); trigger_background_update(function() { // Database should have been deleted check_database_exists(false); AddonManager.getAddonsByIDs(ADDON_IDS, function(aAddons) { check_results(aAddons, WITHOUT_CACHE); run_test_14(); }); }); } // Tests that the XPI add-ons have the correct properties if caching is // enabled but has no information function run_test_14() { Services.prefs.setBoolPref(PREF_GETADDONS_CACHE_ENABLED, true); Services.prefs.setCharPref(PREF_GETADDONS_BYIDS, GETADDONS_EMPTY); trigger_background_update(function() { check_database_exists(true); AddonManager.getAddonsByIDs(ADDON_IDS, function(aAddons) { check_results(aAddons, WITHOUT_CACHE); run_test_15(); }); }); } // Tests that the XPI add-ons correctly use the repository properties when // caching is enabled and the repository information is available function run_test_15() { Services.prefs.setCharPref(PREF_GETADDONS_BYIDS, GETADDONS_RESULTS); trigger_background_update(function() { AddonManager.getAddonsByIDs(ADDON_IDS, function(aAddons) { check_results(aAddons, WITH_CACHE); run_test_16(); }); }); } // Tests that restarting the manager does not change the checked properties // on the XPI add-ons (repository properties still exist and are still properly // used) function run_test_16() { restartManager(); AddonManager.getAddonsByIDs(ADDON_IDS, function(aAddons) { check_results(aAddons, WITH_CACHE); end_test(); }); }