diff options
Diffstat (limited to 'toolkit/components/search/tests/xpcshell/test_settings.js')
-rw-r--r-- | toolkit/components/search/tests/xpcshell/test_settings.js | 616 |
1 files changed, 616 insertions, 0 deletions
diff --git a/toolkit/components/search/tests/xpcshell/test_settings.js b/toolkit/components/search/tests/xpcshell/test_settings.js new file mode 100644 index 0000000000..3be00f460e --- /dev/null +++ b/toolkit/components/search/tests/xpcshell/test_settings.js @@ -0,0 +1,616 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/* + * Test initializing from the search settings. + */ + +"use strict"; + +const legacyUseSavedOrderPrefName = + SearchUtils.BROWSER_SEARCH_PREF + "useDBForOrder"; + +var settingsTemplate; + +/** + * Test reading from search.json.mozlz4 + */ +add_task(async function setup() { + Services.prefs + .getDefaultBranch(SearchUtils.BROWSER_SEARCH_PREF + "param.") + .setCharPref("test", "expected"); + + await SearchTestUtils.useTestEngines("data1"); + await AddonTestUtils.promiseStartupManager(); + await Services.search.init(); +}); + +async function loadSettingsFile(settingsFile, setVersion, setHashes) { + settingsTemplate = await readJSONFile(do_get_file(settingsFile)); + if (setVersion) { + settingsTemplate.version = SearchUtils.SETTINGS_VERSION; + } + + if (setHashes) { + settingsTemplate.metaData.hash = SearchUtils.getVerificationHash( + settingsTemplate.metaData.current + ); + settingsTemplate.metaData.privateHash = SearchUtils.getVerificationHash( + settingsTemplate.metaData.private + ); + } + + delete settingsTemplate.visibleDefaultEngines; + + await promiseSaveSettingsData(settingsTemplate); +} + +/** + * Start the search service and confirm the engine properties match the expected values. + * + * @param {string} settingsFile + * The path to the settings file to use. + * @param {boolean} setVersion + * True if to set the version in the copied settings file. + * @param {boolean} expectedUseDBValue + * The value expected for the `useSavedOrder` metadata attribute. + */ +async function checkLoadSettingProperties( + settingsFile, + setVersion, + expectedUseDBValue +) { + info("init search service"); + let ss = Services.search.wrappedJSObject; + + await loadSettingsFile(settingsFile, setVersion); + + const settingsFileWritten = promiseAfterSettings(); + + await ss.reset(); + await Services.search.init(); + + await settingsFileWritten; + + let engines = await ss.getEngines(); + + Assert.equal( + engines[0].name, + "engine1", + "Should have loaded the correct first engine" + ); + Assert.equal(engines[0].alias, "testAlias", "Should have set the alias"); + Assert.equal(engines[0].hidden, false, "Should have not hidden the engine"); + Assert.equal(engines[0].id, "engine1@search.mozilla.orgdefault"); + + Assert.equal( + engines[1].name, + "engine2", + "Should have loaded the correct second engine" + ); + Assert.equal(engines[1].alias, "", "Should have not set the alias"); + Assert.equal(engines[1].hidden, true, "Should have hidden the engine"); + Assert.equal(engines[1].id, "engine2@search.mozilla.orgdefault"); + + // The extra engine is the second in the list. + isSubObjectOf(EXPECTED_ENGINE.engine, engines[2]); + Assert.ok(engines[2].id, "test-addon-id@mozilla.orgdefault"); + + let engineFromSS = ss.getEngineByName(EXPECTED_ENGINE.engine.name); + Assert.ok(!!engineFromSS); + isSubObjectOf(EXPECTED_ENGINE.engine, engineFromSS); + + Assert.equal( + engineFromSS.getSubmission("foo").uri.spec, + "http://www.google.com/search?q=foo", + "Should have the correct URL with no mozparams" + ); + + Assert.equal( + ss._settings.getMetaDataAttribute("useSavedOrder"), + expectedUseDBValue, + "Should have set the useSavedOrder metadata correctly." + ); + + let migratedSettingsFile = await promiseSettingsData(); + + Assert.equal( + migratedSettingsFile.engines[0].id, + "engine1@search.mozilla.orgdefault" + ); + + removeSettingsFile(); +} + +add_task(async function test_legacy_setting_engine_properties() { + Services.prefs.setBoolPref(legacyUseSavedOrderPrefName, true); + + let legacySettings = await readJSONFile( + do_get_file("data/search-legacy.json") + ); + + // Assert the engine ids have not been migrated yet + for (let engine of legacySettings.engines) { + Assert.ok(!("id" in engine)); + } + Assert.ok(!("defaultEngineId" in legacySettings.metaData)); + Assert.ok(!("privateDefaultEngineId" in legacySettings.metaData)); + + await checkLoadSettingProperties("data/search-legacy.json", false, true); + + Assert.ok( + !Services.prefs.prefHasUserValue(legacyUseSavedOrderPrefName), + "Should have cleared the legacy pref." + ); +}); + +add_task( + async function test_legacy_setting_migration_with_undefined_metaData_current_and_private() { + let ss = Services.search.wrappedJSObject; + + await loadSettingsFile("data/search-legacy.json", false); + const settingsFileWritten = promiseAfterSettings(); + + await ss.reset(); + await Services.search.init(); + + await settingsFileWritten; + + let migratedSettingsFile = await promiseSettingsData(); + + Assert.equal( + migratedSettingsFile.metaData.defaultEngineId, + "", + "When there is no metaData.current attribute in settings file, the migration should set the defaultEngineId to an empty string." + ); + Assert.equal( + migratedSettingsFile.metaData.privateDefaultEngineId, + "", + "When there is no metaData.private attribute in settings file, the migration should set the privateDefaultEngineId to an empty string." + ); + + removeSettingsFile(); + } +); + +add_task( + async function test_legacy_setting_migration_with_correct_metaData_current_and_private_hashes() { + let ss = Services.search.wrappedJSObject; + + await loadSettingsFile( + "data/search-legacy-correct-default-engine-hashes.json", + false, + true + ); + const settingsFileWritten = promiseAfterSettings(); + + await ss.reset(); + await Services.search.init(); + + await settingsFileWritten; + + let migratedSettingsFile = await promiseSettingsData(); + + Assert.equal( + migratedSettingsFile.metaData.defaultEngineId, + "engine2@search.mozilla.orgdefault", + "When the metaData.current and associated hash are correct, the migration should set the defaultEngineId to the engine id." + ); + Assert.equal( + migratedSettingsFile.metaData.privateDefaultEngineId, + "engine2@search.mozilla.orgdefault", + "When the metaData.private and associated hash are correct, the migration should set the privateDefaultEngineId to the private engine id." + ); + + removeSettingsFile(); + } +); + +add_task( + async function test_legacy_setting_migration_with_incorrect_metaData_current_and_private_hashes_app_provided() { + let ss = Services.search.wrappedJSObject; + + // Here we are testing correct migration for the case that a user has set + // their default engine to an application provided engine (but not the app + // default). + // + // In this case we should ignore invalid hashes for the default engines, + // and allow the select default to remain. This covers the case where + // a user has copied a profile from a different directory. + // See SearchService._getEngineDefault for more details. + + await loadSettingsFile( + "data/search-legacy-wrong-default-engine-hashes.json", + false, + false + ); + const settingsFileWritten = promiseAfterSettings(); + + await ss.reset(); + await Services.search.init(); + + await settingsFileWritten; + + let migratedSettingsFile = await promiseSettingsData(); + + Assert.equal( + migratedSettingsFile.metaData.defaultEngineId, + "engine2@search.mozilla.orgdefault", + "Should ignore invalid metaData.hash when the default engine is application provided." + ); + Assert.equal( + Services.search.defaultEngine.name, + "engine2", + "Should have the correct engine set as default" + ); + + Assert.equal( + migratedSettingsFile.metaData.privateDefaultEngineId, + "engine2@search.mozilla.orgdefault", + "Should ignore invalid metaData.privateHash when the default private engine is application provided." + ); + Assert.equal( + Services.search.defaultPrivateEngine.name, + "engine2", + "Should have the correct engine set as default private" + ); + + removeSettingsFile(); + } +); + +add_task( + async function test_legacy_setting_migration_with_incorrect_metaData_current_and_private_hashes_third_party() { + let ss = Services.search.wrappedJSObject; + + // This test is checking that if the user has set a third-party engine as + // default, and the verification hash is invalid, then we do not copy + // the default engine setting. + + await loadSettingsFile( + "data/search-legacy-wrong-third-party-engine-hashes.json", + false, + false + ); + const settingsFileWritten = promiseAfterSettings(); + + await ss.reset(); + await Services.search.init(); + + await settingsFileWritten; + + let migratedSettingsFile = await promiseSettingsData(); + + Assert.equal( + migratedSettingsFile.metaData.defaultEngineId, + "", + "Should reset the default engine when metaData.hash is invalid and the engine is not application provided." + ); + Assert.equal( + Services.search.defaultEngine.name, + "engine1", + "Should have reset the default engine" + ); + + Assert.equal( + migratedSettingsFile.metaData.privateDefaultEngineId, + "", + "Should reset the default engine when metaData.privateHash is invalid and the engine is not application provided." + ); + Assert.equal( + Services.search.defaultPrivateEngine.name, + "engine1", + "Should have reset the default private engine" + ); + + removeSettingsFile(); + } +); + +add_task(async function test_current_setting_engine_properties() { + await checkLoadSettingProperties("data/search.json", true, false); +}); + +add_task(async function test_settings_metadata_properties() { + let ss = Services.search.wrappedJSObject; + + await loadSettingsFile("data/search.json"); + + const settingsFileWritten = promiseAfterSettings(); + await ss.reset(); + await Services.search.init(); + + await settingsFileWritten; + + let metaDataProperties = [ + "locale", + "region", + "channel", + "experiment", + "distroID", + ]; + + for (let name of metaDataProperties) { + Assert.notEqual( + ss._settings.getMetaDataAttribute(`${name}`), + undefined, + `Search settings should have ${name} property defined.` + ); + } + + removeSettingsFile(); +}); + +add_task(async function test_settings_write_when_settings_changed() { + let ss = Services.search.wrappedJSObject; + await loadSettingsFile("data/search.json"); + + const settingsFileWritten = promiseAfterSettings(); + await ss.reset(); + await Services.search.init(); + await settingsFileWritten; + + Assert.ok( + ss._settings.isCurrentAndCachedSettingsEqual(), + "Settings and cached settings should be the same after search service initializaiton." + ); + + const settingsFileWritten2 = promiseAfterSettings(); + ss._settings.setMetaDataAttribute("value", "test"); + + Assert.ok( + !ss._settings.isCurrentAndCachedSettingsEqual(), + "Settings should differ from cached settings after a new attribute is set." + ); + + await settingsFileWritten2; + info("Settings write complete"); + + Assert.ok( + ss._settings.isCurrentAndCachedSettingsEqual(), + "Settings and cached settings should be the same after new attribte on settings is written." + ); + + removeSettingsFile(); +}); + +add_task(async function test_set_and_get_engine_metadata_attribute() { + let ss = Services.search.wrappedJSObject; + await loadSettingsFile("data/search.json"); + + const settingsFileWritten = promiseAfterSettings(); + await ss.reset(); + await Services.search.init(); + await settingsFileWritten; + + let engines = await ss.getEngines(); + const settingsFileWritten2 = promiseAfterSettings(); + ss._settings.setEngineMetaDataAttribute(engines[0].name, "value", "test"); + await settingsFileWritten2; + + Assert.equal( + "test", + ss._settings.getEngineMetaDataAttribute(engines[0].name, "value"), + `${engines[0].name}'s metadata property "value" should be set as "test" after calling getEngineMetaDataAttribute.` + ); + + let userSettings = await ss._settings.get(); + let engine = userSettings.engines.find(e => e._name == engines[0].name); + + Assert.equal( + "test", + engine._metaData.value, + `${engines[0].name}'s metadata property "value" should be set as "test" from settings file.` + ); + + removeSettingsFile(); +}); + +add_task( + async function test_settings_write_prevented_when_settings_unchanged() { + let ss = Services.search.wrappedJSObject; + await loadSettingsFile("data/search.json"); + + const settingsFileWritten = promiseAfterSettings(); + await ss.reset(); + await Services.search.init(); + await settingsFileWritten; + + Assert.ok( + ss._settings.isCurrentAndCachedSettingsEqual(), + "Settings and cached settings should be the same after search service initializaiton." + ); + + // Update settings. + const settingsFileWritten2 = promiseAfterSettings(); + ss._settings.setMetaDataAttribute("value", "test"); + + Assert.ok( + !ss._settings.isCurrentAndCachedSettingsEqual(), + "Settings should differ from cached settings after a new attribute is set." + ); + await settingsFileWritten2; + + // Set the same attribute as before to ensure there was no change. + // Settings write should be prevented. + let promiseWritePrevented = SearchTestUtils.promiseSearchNotification( + "write-prevented-when-settings-unchanged" + ); + ss._settings.setMetaDataAttribute("value", "test"); + + Assert.ok( + ss._settings.isCurrentAndCachedSettingsEqual(), + "Settings and cached settings should be the same." + ); + await promiseWritePrevented; + + removeSettingsFile(); + } +); + +/** + * Test that the JSON settings written in the profile is correct. + */ +add_task(async function test_settings_write() { + let ss = Services.search.wrappedJSObject; + info("test settings writing"); + + await loadSettingsFile("data/search.json"); + + const settingsFileWritten = promiseAfterSettings(); + await ss.reset(); + await Services.search.init(); + await settingsFileWritten; + + let settingsData = await promiseSettingsData(); + + // Remove buildID and locale, as they are no longer used. + delete settingsTemplate.buildID; + delete settingsTemplate.locale; + + for (let engine of settingsTemplate.engines) { + // Remove _shortName from the settings template, as it is no longer supported, + // but older settings used to have it, so we keep it in the template as an + // example. + if ("_shortName" in engine) { + delete engine._shortName; + } + if ("_urls" in engine) { + // Only app-provided engines support purpose & mozparams, others do not, + // so filter them out of the expected template. + for (let urls of engine._urls) { + urls.params = urls.params.filter(p => !p.purpose && !p.mozparam); + // resultDomain is also no longer supported. + if ("resultDomain" in urls) { + delete urls.resultDomain; + } + } + } + // Remove queryCharset, if it is the same as the default, as we don't save + // it in that case. + if (engine?.queryCharset == SearchUtils.DEFAULT_QUERY_CHARSET) { + delete engine.queryCharset; + } + } + + // Note: the file is copied with an old version number, which should have + // been updated on write. + settingsTemplate.version = SearchUtils.SETTINGS_VERSION; + + isSubObjectOf(settingsTemplate, settingsData, (prop, value) => { + if (prop != "_iconURL" && prop != "{}") { + return false; + } + // Skip items that are to do with icons for extensions, as we can't + // control the uuid. + return value.startsWith("moz-extension://"); + }); +}); + +async function settings_write_check(disableFn) { + let ss = Services.search.wrappedJSObject; + + sinon.stub(ss._settings, "_write").returns(Promise.resolve()); + + // Simulate the search service being initialized. + disableFn(true); + + ss._settings.setMetaDataAttribute("value", "test"); + + Assert.ok( + ss._settings._write.notCalled, + "Should not have attempted to _write" + ); + + // Wait for two periods of the normal delay to ensure we still do not write. + await new Promise(r => + // eslint-disable-next-line mozilla/no-arbitrary-setTimeout + setTimeout(r, SearchSettings.SETTNGS_INVALIDATION_DELAY * 2) + ); + + Assert.ok( + ss._settings._write.notCalled, + "Should not have attempted to _write" + ); + + disableFn(false); + + await TestUtils.waitForCondition( + () => ss._settings._write.calledOnce, + "Should attempt to write the settings." + ); + + sinon.restore(); +} + +add_task(async function test_settings_write_prevented_during_init() { + await settings_write_check(disable => { + let status = disable ? "success" : "failed"; + Services.search.wrappedJSObject.forceInitializationStatusForTests(status); + }); +}); + +add_task(async function test_settings_write_prevented_during_reload() { + await settings_write_check( + disable => (Services.search.wrappedJSObject._reloadingEngines = disable) + ); +}); + +var EXPECTED_ENGINE = { + engine: { + name: "Test search engine", + alias: "", + description: "A test search engine (based on Google search)", + searchForm: "http://www.google.com/", + wrappedJSObject: { + _extensionID: "test-addon-id@mozilla.org", + _iconURL: + "data:image/png;base64,AAABAAEAEBAAAAEAGABoAwAAFgAAACgAAAAQAAA" + + "AIAAAAAEAGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADs9Pt8xetPtu9F" + + "sfFNtu%2BTzvb2%2B%2Fne4dFJeBw0egA%2FfAJAfAA8ewBBegAAAAD%2B%2F" + + "Ptft98Mp%2BwWsfAVsvEbs%2FQeqvF8xO7%2F%2F%2F63yqkxdgM7gwE%2Fgg" + + "M%2BfQA%2BegBDeQDe7PIbotgQufcMufEPtfIPsvAbs%2FQvq%2Bfz%2Bf%2F" + + "%2B%2B%2FZKhR05hgBBhQI8hgBAgAI9ewD0%2B%2Fg3pswAtO8Cxf4Kw%2FsJ" + + "vvYAqupKsNv%2B%2Fv7%2F%2FP5VkSU0iQA7jQA9hgBDgQU%2BfQH%2F%2Ff%" + + "2FQ6fM4sM4KsN8AteMCruIqqdbZ7PH8%2Fv%2Fg6Nc%2Fhg05kAA8jAM9iQI%" + + "2BhQA%2BgQDQu6b97uv%2F%2F%2F7V8Pqw3eiWz97q8%2Ff%2F%2F%2F%2F7%" + + "2FPptpkkqjQE4kwA7kAA5iwI8iAA8hQCOSSKdXjiyflbAkG7u2s%2F%2B%2F%" + + "2F39%2F%2F7r8utrqEYtjQE8lgA7kwA7kwA9jwA9igA9hACiWSekVRyeSgiYS" + + "BHx6N%2F%2B%2Fv7k7OFRmiYtlAA5lwI7lwI4lAA7kgI9jwE9iwI4iQCoVhWc" + + "TxCmb0K%2BooT8%2Fv%2F7%2F%2F%2FJ2r8fdwI1mwA3mQA3mgA8lAE8lAE4j" + + "wA9iwE%2BhwGfXifWvqz%2B%2Ff%2F58u%2Fev6Dt4tr%2B%2F%2F2ZuIUsgg" + + "A7mgM6mAM3lgA5lgA6kQE%2FkwBChwHt4dv%2F%2F%2F728ei1bCi7VAC5XQ7" + + "kz7n%2F%2F%2F6bsZkgcB03lQA9lgM7kwA2iQktZToPK4r9%2F%2F%2F9%2F%" + + "2F%2FSqYK5UwDKZAS9WALIkFn%2B%2F%2F3%2F%2BP8oKccGGcIRJrERILYFE" + + "MwAAuEAAdX%2F%2Ff7%2F%2FP%2B%2BfDvGXQLIZgLEWgLOjlf7%2F%2F%2F%" + + "2F%2F%2F9QU90EAPQAAf8DAP0AAfMAAOUDAtr%2F%2F%2F%2F7%2B%2Fu2bCT" + + "IYwDPZgDBWQDSr4P%2F%2Fv%2F%2F%2FP5GRuABAPkAA%2FwBAfkDAPAAAesA" + + "AN%2F%2F%2B%2Fz%2F%2F%2F64g1C5VwDMYwK8Yg7y5tz8%2Fv%2FV1PYKDOc" + + "AAP0DAf4AAf0AAfYEAOwAAuAAAAD%2F%2FPvi28ymXyChTATRrIb8%2F%2F3v" + + "8fk6P8MAAdUCAvoAAP0CAP0AAfYAAO4AAACAAQAAAAAAAAAAAAAAAAAAAAAAA" + + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAQAA", + _urls: [ + { + type: "application/x-suggestions+json", + method: "GET", + template: + "http://suggestqueries.google.com/complete/search?output=firefox&client=firefox" + + "&hl={moz:locale}&q={searchTerms}", + params: "", + }, + { + type: "text/html", + method: "GET", + template: "http://www.google.com/search", + params: [ + { + name: "q", + value: "{searchTerms}", + purpose: undefined, + }, + ], + }, + ], + }, + }, +}; |