diff options
Diffstat (limited to 'toolkit/components/search/tests/xpcshell')
19 files changed, 1014 insertions, 82 deletions
diff --git a/toolkit/components/search/tests/xpcshell/data/search-config-v2.json b/toolkit/components/search/tests/xpcshell/data/search-config-v2.json index 569e16dfe4..bca7cc3bdf 100644 --- a/toolkit/components/search/tests/xpcshell/data/search-config-v2.json +++ b/toolkit/components/search/tests/xpcshell/data/search-config-v2.json @@ -112,6 +112,7 @@ "recordType": "engine", "identifier": "engine-resourceicon", "base": { + "classification": "general", "name": "engine-resourceicon", "urls": { "search": { @@ -151,6 +152,7 @@ "recordType": "engine", "identifier": "engine-reordered", "base": { + "classification": "general", "name": "Test search engine (Reordered)", "urls": { "search": { diff --git a/toolkit/components/search/tests/xpcshell/data1/search-config-v2.json b/toolkit/components/search/tests/xpcshell/data1/search-config-v2.json index 98bdfa26ff..ac5f7f77cd 100644 --- a/toolkit/components/search/tests/xpcshell/data1/search-config-v2.json +++ b/toolkit/components/search/tests/xpcshell/data1/search-config-v2.json @@ -9,7 +9,7 @@ "searchTermParamName": "q" } }, - "classification": "unknown" + "classification": "general" }, "variants": [{ "environment": { "allRegionsAndLocales": true } }], "identifier": "engine1", @@ -24,7 +24,7 @@ "searchTermParamName": "q" } }, - "classification": "unknown" + "classification": "general" }, "variants": [{ "environment": { "allRegionsAndLocales": true } }], "identifier": "engine2", @@ -39,7 +39,7 @@ "searchTermParamName": "q" } }, - "classification": "unknown" + "classification": "general" }, "variants": [ { @@ -58,7 +58,7 @@ "searchTermParamName": "q" } }, - "classification": "unknown" + "classification": "general" }, "variants": [ { diff --git a/toolkit/components/search/tests/xpcshell/head_search.js b/toolkit/components/search/tests/xpcshell/head_search.js index 1c0504e277..72bc90185c 100644 --- a/toolkit/components/search/tests/xpcshell/head_search.js +++ b/toolkit/components/search/tests/xpcshell/head_search.js @@ -307,6 +307,101 @@ async function setupRemoteSettings() { } /** + * Reads the specified file from the data directory and returns its contents as + * an Uint8Array. + * + * @param {string} filename + * The name of the file to read. + * @returns {Promise<Uint8Array>} + * The contents of the file in an Uint8Array. + */ +async function getFileDataBuffer(filename) { + return IOUtils.read(PathUtils.join(do_get_cwd().path, "data", filename)); +} + +/** + * Creates a mock attachment record for use in remote settings related tests. + * + * @param {object} item + * An object containing the details of the attachment. + * @param {string} item.filename + * The name of the attachmnet file in the data directory. + * @param {string[]} item.engineIdentifiers + * The engine identifiers for the attachment. + * @param {number} item.imageSize + * The size of the image. + * @param {string} [item.id] + * The ID to use for the record. If not provided, a new UUID will be generated. + * @param {number} [item.lastModified] + * The last modified time for the record. Defaults to the current time. + */ +async function mockRecordWithAttachment({ + filename, + engineIdentifiers, + imageSize, + id = Services.uuid.generateUUID().toString(), + lastModified = Date.now(), +}) { + let buffer = await getFileDataBuffer(filename); + + // Generate a hash. + let hasher = Cc["@mozilla.org/security/hash;1"].createInstance( + Ci.nsICryptoHash + ); + hasher.init(Ci.nsICryptoHash.SHA256); + hasher.update(buffer, buffer.length); + + let hash = hasher.finish(false); + hash = Array.from(hash, (_, i) => + ("0" + hash.charCodeAt(i).toString(16)).slice(-2) + ).join(""); + + let record = { + id, + engineIdentifiers, + imageSize, + attachment: { + hash, + location: `${filename}`, + filename, + size: buffer.byteLength, + mimetype: "application/json", + }, + last_modified: lastModified, + }; + + let attachment = { + record, + blob: new Blob([buffer]), + }; + + return { record, attachment }; +} + +/** + * Inserts an attachment record into the remote settings collection. + * + * @param {RemoteSettingsClient} client + * The remote settings client to use. + * @param {object} item + * An object containing the details of the attachment - see mockRecordWithAttachment. + * @param {boolean} [addAttachmentToCache] + * Whether to add the attachment file to the cache. Defaults to true. + */ +async function insertRecordIntoCollection( + client, + item, + addAttachmentToCache = true +) { + let { record, attachment } = await mockRecordWithAttachment(item); + await client.db.create(record); + if (addAttachmentToCache) { + await client.attachments.cacheImpl.set(record.id, attachment); + } + await client.db.importChanges({}, record.last_modified); +} + +/** * Helper function that sets up a server and respnds to region * fetch requests. * diff --git a/toolkit/components/search/tests/xpcshell/searchconfigs/head_searchconfig.js b/toolkit/components/search/tests/xpcshell/searchconfigs/head_searchconfig.js index 72c4d4f04f..c58ac1c25b 100644 --- a/toolkit/components/search/tests/xpcshell/searchconfigs/head_searchconfig.js +++ b/toolkit/components/search/tests/xpcshell/searchconfigs/head_searchconfig.js @@ -115,7 +115,7 @@ class SearchConfigTest { async setup(version = "42.0") { if (SearchUtils.newSearchConfigEnabled) { updateAppInfo({ - name: "XPCShell", + name: "firefox", ID: "xpcshell@tests.mozilla.org", version, platformVersion: version, diff --git a/toolkit/components/search/tests/xpcshell/searchconfigs/test_qwant.js b/toolkit/components/search/tests/xpcshell/searchconfigs/test_qwant.js index 4024385729..eab28a7ba9 100644 --- a/toolkit/components/search/tests/xpcshell/searchconfigs/test_qwant.js +++ b/toolkit/components/search/tests/xpcshell/searchconfigs/test_qwant.js @@ -14,6 +14,9 @@ const test = new SearchConfigTest({ { locales: ["fr"], }, + { + regions: ["be", "ch", "es", "fr", "it", "nl"], + }, ], }, details: [ @@ -28,9 +31,30 @@ const test = new SearchConfigTest({ }); add_setup(async function () { - await test.setup(); + if (SearchUtils.newSearchConfigEnabled) { + await test.setup(); + } else { + await test.setup("124.0"); + } }); add_task(async function test_searchConfig_qwant() { await test.run(); }); + +add_task( + { skip_if: () => SearchUtils.newSearchConfigEnabled }, + async function test_searchConfig_qwant_pre124() { + const version = "123.0"; + AddonTestUtils.createAppInfo( + "xpcshell@tests.mozilla.org", + "XPCShell", + version, + version + ); + // For pre-124, Qwant is not available in the extra regions. + test._config.available.included.pop(); + + await test.run(); + } +); diff --git a/toolkit/components/search/tests/xpcshell/searchconfigs/test_searchconfig_validates.js b/toolkit/components/search/tests/xpcshell/searchconfigs/test_searchconfig_validates.js index 86686b62f7..f727d60719 100644 --- a/toolkit/components/search/tests/xpcshell/searchconfigs/test_searchconfig_validates.js +++ b/toolkit/components/search/tests/xpcshell/searchconfigs/test_searchconfig_validates.js @@ -172,6 +172,32 @@ add_task( add_task( { skip_if: () => !SearchUtils.newSearchConfigEnabled }, + async function test_search_config_valid_partner_codes() { + delete SearchUtils.newSearchConfigEnabled; + SearchUtils.newSearchConfigEnabled = true; + + let selector = new SearchEngineSelector(() => {}); + + for (let entry of await selector.getEngineConfiguration()) { + if (entry.recordType == "engine") { + for (let variant of entry.variants) { + if ( + "partnerCode" in variant && + "distributions" in variant.environment + ) { + Assert.ok( + variant.telemetrySuffix, + `${entry.identifier} should have a telemetrySuffix when a distribution is specified with a partnerCode.` + ); + } + } + } + } + } +); + +add_task( + { skip_if: () => !SearchUtils.newSearchConfigEnabled }, async function test_search_config_override_validates_to_schema() { let selector = new SearchEngineSelector(() => {}); diff --git a/toolkit/components/search/tests/xpcshell/test_appProvided_engine.js b/toolkit/components/search/tests/xpcshell/test_appProvided_engine.js new file mode 100644 index 0000000000..56297a9d2c --- /dev/null +++ b/toolkit/components/search/tests/xpcshell/test_appProvided_engine.js @@ -0,0 +1,182 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Tests that ensure application provided engines have all base fields set up + * correctly from the search configuration. + */ + +"use strict"; + +let CONFIG = [ + { + identifier: "testEngine", + recordType: "engine", + base: { + aliases: ["testEngine1", "testEngine2"], + charset: "EUC-JP", + classification: "general", + name: "testEngine name", + partnerCode: "pc", + urls: { + search: { + base: "https://example.com/1", + // Method defaults to GET + params: [ + { name: "partnerCode", value: "{partnerCode}" }, + { name: "starbase", value: "Regula I" }, + { name: "experiment", value: "Genesis" }, + { + name: "accessPoint", + searchAccessPoint: { + addressbar: "addressbar", + contextmenu: "contextmenu", + homepage: "homepage", + newtab: "newtab", + searchbar: "searchbar", + }, + }, + ], + searchTermParamName: "search", + }, + suggestions: { + base: "https://example.com/2", + method: "POST", + searchTermParamName: "suggestions", + }, + trending: { + base: "https://example.com/3", + searchTermParamName: "trending", + }, + }, + }, + variants: [{ environment: { allRegionsAndLocales: true } }], + }, + { + identifier: "testOtherValuesEngine", + recordType: "engine", + base: { + classification: "unknown", + name: "testOtherValuesEngine name", + urls: { + search: { + base: "https://example.com/1", + searchTermParamName: "search", + }, + }, + }, + variants: [{ environment: { allRegionsAndLocales: true } }], + }, + { + recordType: "defaultEngines", + globalDefault: "engine_no_initial_icon", + specificDefaults: [], + }, + { + recordType: "engineOrders", + orders: [], + }, +]; + +add_setup(async function () { + await SearchTestUtils.useTestEngines("simple-engines", null, CONFIG); + await Services.search.init(); +}); + +add_task(async function test_engine_with_all_params_set() { + let engine = Services.search.getEngineById( + "testEngine@search.mozilla.orgdefault" + ); + Assert.ok(engine, "Should have found the engine"); + + Assert.equal( + engine.name, + "testEngine name", + "Should have the correct engine name" + ); + Assert.deepEqual( + engine.aliases, + ["@testEngine1", "@testEngine2"], + "Should have the correct aliases" + ); + Assert.ok( + engine.isGeneralPurposeEngine, + "Should be a general purpose engine" + ); + Assert.equal( + engine.wrappedJSObject.queryCharset, + "EUC-JP", + "Should have the correct encoding" + ); + + let submission = engine.getSubmission("test"); + Assert.equal( + submission.uri.spec, + "https://example.com/1?partnerCode=pc&starbase=Regula%20I&experiment=Genesis&accessPoint=searchbar&search=test", + "Should have the correct search URL" + ); + Assert.ok(!submission.postData, "Should not have postData for a GET url"); + + let suggestSubmission = engine.getSubmission( + "test", + SearchUtils.URL_TYPE.SUGGEST_JSON + ); + Assert.equal( + suggestSubmission.uri.spec, + "https://example.com/2", + "Should have the correct suggestion URL" + ); + Assert.equal( + suggestSubmission.postData.data.data, + "suggestions=test", + "Should have the correct postData for a POST URL" + ); + + let trendingSubmission = engine.getSubmission( + "test", + SearchUtils.URL_TYPE.TRENDING_JSON + ); + Assert.equal( + trendingSubmission.uri.spec, + "https://example.com/3?trending=test" + ); + Assert.ok(!submission.postData, "Should not have postData for a GET url"); +}); + +add_task(async function test_engine_with_some_params_set() { + let engine = Services.search.getEngineById( + "testOtherValuesEngine@search.mozilla.orgdefault" + ); + Assert.ok(engine, "Should have found the engine"); + + Assert.equal( + engine.name, + "testOtherValuesEngine name", + "Should have the correct engine name" + ); + Assert.deepEqual(engine.aliases, [], "Should have no aliases"); + Assert.ok( + !engine.isGeneralPurposeEngine, + "Should not be a general purpose engine" + ); + Assert.equal( + engine.wrappedJSObject.queryCharset, + "UTF-8", + "Should default to UTF-8 charset" + ); + Assert.equal( + engine.getSubmission("test").uri.spec, + "https://example.com/1?search=test", + "Should have the correct search URL" + ); + Assert.equal( + engine.getSubmission("test", SearchUtils.URL_TYPE.SUGGEST_JSON), + null, + "Should not have a suggestions URL" + ); + Assert.equal( + engine.getSubmission("test", SearchUtils.URL_TYPE.TRENDING_JSON), + null, + "Should not have a trending URL" + ); +}); diff --git a/toolkit/components/search/tests/xpcshell/test_appProvided_icons.js b/toolkit/components/search/tests/xpcshell/test_appProvided_icons.js index e4d8033993..f6cc8b2415 100644 --- a/toolkit/components/search/tests/xpcshell/test_appProvided_icons.js +++ b/toolkit/components/search/tests/xpcshell/test_appProvided_icons.js @@ -53,7 +53,11 @@ let TESTS = [ icons: [ { filename: "remoteIcon.ico", - engineIdentifiers: ["engine_non_default_sized_icon"], + engineIdentifiers: [ + // This also tests multiple engine idenifiers works. + "enterprise_shuttle", + "engine_non_default_sized_icon", + ], imageSize: 32, }, ], @@ -77,64 +81,6 @@ let TESTS = [ }, ]; -async function getFileDataBuffer(filename) { - let data = await IOUtils.read( - PathUtils.join(do_get_cwd().path, "data", filename) - ); - return new TextEncoder().encode(data).buffer; -} - -async function mockRecordWithAttachment({ - filename, - engineIdentifiers, - imageSize, -}) { - let buffer = await getFileDataBuffer(filename); - - let stream = Cc["@mozilla.org/io/arraybuffer-input-stream;1"].createInstance( - Ci.nsIArrayBufferInputStream - ); - stream.setData(buffer, 0, buffer.byteLength); - - // Generate a hash. - let hasher = Cc["@mozilla.org/security/hash;1"].createInstance( - Ci.nsICryptoHash - ); - hasher.init(Ci.nsICryptoHash.SHA256); - hasher.updateFromStream(stream, -1); - let hash = hasher.finish(false); - hash = Array.from(hash, (_, i) => - ("0" + hash.charCodeAt(i).toString(16)).slice(-2) - ).join(""); - - let record = { - id: Services.uuid.generateUUID().toString(), - engineIdentifiers, - imageSize, - attachment: { - hash, - location: `main-workspace/search-config-icons/${filename}`, - filename, - size: buffer.byteLength, - mimetype: "application/json", - }, - }; - - let attachment = { - record, - blob: new Blob([buffer]), - }; - - return { record, attachment }; -} - -async function insertRecordIntoCollection(client, db, item) { - let { record, attachment } = await mockRecordWithAttachment(item); - await db.create(record); - await client.attachments.cacheImpl.set(record.id, attachment); - await db.importChanges({}, Date.now()); -} - add_setup(async function () { let client = RemoteSettings("search-config-icons"); let db = client.db; @@ -159,10 +105,7 @@ add_setup(async function () { if ("icons" in test) { for (let icon of test.icons) { - await insertRecordIntoCollection(client, db, { - ...icon, - id: test.engineId, - }); + await insertRecordIntoCollection(client, { ...icon }); } } } diff --git a/toolkit/components/search/tests/xpcshell/test_appProvided_icons_updates.js b/toolkit/components/search/tests/xpcshell/test_appProvided_icons_updates.js new file mode 100644 index 0000000000..4159b9f0fb --- /dev/null +++ b/toolkit/components/search/tests/xpcshell/test_appProvided_icons_updates.js @@ -0,0 +1,324 @@ +/* Any copyright is dedicated to the Public Domain. +https://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Tests to ensure that icons for application provided engines are correctly + * updated from remote settings. + */ + +"use strict"; + +// A skeleton configuration that gets filled in from TESTS during `add_setup`. +let CONFIG = [ + { + identifier: "engine_no_initial_icon", + recordType: "engine", + base: { + name: "engine_no_initial_icon name", + urls: { + search: { + base: "https://example.com/1", + searchTermParamName: "q", + }, + }, + }, + variants: [{ environment: { allRegionsAndLocales: true } }], + }, + { + identifier: "engine_icon_updates", + recordType: "engine", + base: { + name: "engine_icon_updates name", + urls: { + search: { + base: "https://example.com/2", + searchTermParamName: "q", + }, + }, + }, + variants: [{ environment: { allRegionsAndLocales: true } }], + }, + { + identifier: "engine_icon_not_local", + recordType: "engine", + base: { + name: "engine_icon_not_local name", + urls: { + search: { + base: "https://example.com/3", + searchTermParamName: "q", + }, + }, + }, + variants: [{ environment: { allRegionsAndLocales: true } }], + }, + { + identifier: "engine_icon_out_of_date", + recordType: "engine", + base: { + name: "engine_icon_out_of_date name", + urls: { + search: { + base: "https://example.com/4", + searchTermParamName: "q", + }, + }, + }, + variants: [{ environment: { allRegionsAndLocales: true } }], + }, + { + recordType: "defaultEngines", + globalDefault: "engine_no_initial_icon", + specificDefaults: [], + }, + { + recordType: "engineOrders", + orders: [], + }, +]; + +async function assertIconMatches(actualIconData, expectedIcon) { + let expectedBuffer = new Uint8Array(await getFileDataBuffer(expectedIcon)); + + Assert.equal( + actualIconData.length, + expectedBuffer.length, + "Should have received matching buffer lengths for the expected icon" + ); + Assert.ok( + actualIconData.every((value, index) => value === expectedBuffer[index]), + "Should have received matching data for the expected icon" + ); +} + +async function assertEngineIcon(engineName, expectedIcon) { + let engine = Services.search.getEngineByName(engineName); + let engineIconURL = await engine.getIconURL(16); + + if (expectedIcon) { + Assert.notEqual( + engineIconURL, + null, + "Should have an icon URL for the engine." + ); + + let response = await fetch(engineIconURL); + let buffer = new Uint8Array(await response.arrayBuffer()); + + await assertIconMatches(buffer, expectedIcon); + } else { + Assert.equal( + engineIconURL, + null, + "Should not have an icon URL for the engine." + ); + } +} + +let originalIconId = Services.uuid.generateUUID().toString(); +let client; + +add_setup(async function setup() { + useHttpServer(); + SearchTestUtils.useMockIdleService(); + + client = RemoteSettings("search-config-icons"); + await client.db.clear(); + + sinon.stub(client.attachments, "_baseAttachmentsURL").returns(gDataUrl); + + // Add some initial records and attachments into the remote settings collection. + await insertRecordIntoCollection(client, { + id: originalIconId, + filename: "remoteIcon.ico", + // This uses a wildcard match to test the icon is still applied correctly. + engineIdentifiers: ["engine_icon_upd*"], + imageSize: 16, + }); + // This attachment is not cached, so we don't have it locally. + await insertRecordIntoCollection( + client, + { + id: Services.uuid.generateUUID().toString(), + filename: "bigIcon.ico", + engineIdentifiers: [ + // This also tests multiple engine idenifiers works. + "enterprise", + "next_generation", + "engine_icon_not_local", + ], + imageSize: 16, + }, + false + ); + + // Add a record that is out of date, and update it with a newer one, but don't + // cache the attachment for the new one. + let outOfDateRecordId = Services.uuid.generateUUID().toString(); + await insertRecordIntoCollection( + client, + { + id: outOfDateRecordId, + filename: "remoteIcon.ico", + engineIdentifiers: ["engine_icon_out_of_date"], + imageSize: 16, + // 10 minutes ago. + lastModified: Date.now() - 600000, + }, + true + ); + let { record } = await mockRecordWithAttachment({ + id: outOfDateRecordId, + filename: "bigIcon.ico", + engineIdentifiers: ["engine_icon_out_of_date"], + imageSize: 16, + }); + await client.db.update(record); + await client.db.importChanges({}, record.lastModified); + + await SearchTestUtils.useTestEngines("simple-engines", null, CONFIG); + await Services.search.init(); + + // Testing that an icon is not local generates a `Could not find {id}...` + // message. + consoleAllowList.push("Could not find"); +}); + +add_task(async function test_icon_added_unknown_engine() { + // If the engine is unknown, and this is a new icon, we should still download + // the icon, in case the engine is added to the configuration later. + let newIconId = Services.uuid.generateUUID().toString(); + + let mock = await mockRecordWithAttachment({ + id: newIconId, + filename: "bigIcon.ico", + engineIdentifiers: ["engine_unknown"], + imageSize: 16, + }); + await client.db.update(mock.record, Date.now()); + + await client.emit("sync", { + data: { + current: [mock.record], + created: [mock.record], + updated: [], + deleted: [], + }, + }); + + SearchTestUtils.idleService._fireObservers("idle"); + + let icon; + await TestUtils.waitForCondition(async () => { + try { + icon = await client.attachments.get(mock.record); + } catch (ex) { + // Do nothing. + } + return !!icon; + }, "Should have loaded the icon into the attachments store."); + + await assertIconMatches(new Uint8Array(icon.buffer), "bigIcon.ico"); +}); + +add_task(async function test_icon_added_existing_engine() { + // If the engine is unknown, and this is a new icon, we should still download + // it, in case the engine is added to the configuration later. + let newIconId = Services.uuid.generateUUID().toString(); + + let mock = await mockRecordWithAttachment({ + id: newIconId, + filename: "bigIcon.ico", + engineIdentifiers: ["engine_no_initial_icon"], + imageSize: 16, + }); + await client.db.update(mock.record, Date.now()); + + let promiseEngineUpdated = SearchTestUtils.promiseSearchNotification( + SearchUtils.MODIFIED_TYPE.CHANGED, + SearchUtils.TOPIC_ENGINE_MODIFIED + ); + + await client.emit("sync", { + data: { + current: [mock.record], + created: [mock.record], + updated: [], + deleted: [], + }, + }); + + SearchTestUtils.idleService._fireObservers("idle"); + + await promiseEngineUpdated; + await assertEngineIcon("engine_no_initial_icon name", "bigIcon.ico"); +}); + +add_task(async function test_icon_updated() { + // Test that when an update for an engine icon is received, the engine is + // correctly updated. + + // Check the engine has the expected icon to start with. + await assertEngineIcon("engine_icon_updates name", "remoteIcon.ico"); + + // Update the icon for the engine. + let mock = await mockRecordWithAttachment({ + id: originalIconId, + filename: "bigIcon.ico", + engineIdentifiers: ["engine_icon_upd*"], + imageSize: 16, + }); + await client.db.update(mock.record, Date.now()); + + let promiseEngineUpdated = SearchTestUtils.promiseSearchNotification( + SearchUtils.MODIFIED_TYPE.CHANGED, + SearchUtils.TOPIC_ENGINE_MODIFIED + ); + + await client.emit("sync", { + data: { + current: [mock.record], + created: [], + updated: [{ new: mock.record }], + deleted: [], + }, + }); + SearchTestUtils.idleService._fireObservers("idle"); + + await promiseEngineUpdated; + await assertEngineIcon("engine_icon_updates name", "bigIcon.ico"); +}); + +add_task(async function test_icon_not_local() { + // Tests that a download is queued and triggered when the icon for an engine + // is not in either the local dump nor the cache. + + await assertEngineIcon("engine_icon_not_local name", null); + + // A download should have been queued, so fire idle to trigger it. + let promiseEngineUpdated = SearchTestUtils.promiseSearchNotification( + SearchUtils.MODIFIED_TYPE.CHANGED, + SearchUtils.TOPIC_ENGINE_MODIFIED + ); + SearchTestUtils.idleService._fireObservers("idle"); + await promiseEngineUpdated; + + await assertEngineIcon("engine_icon_not_local name", "bigIcon.ico"); +}); + +add_task(async function test_icon_out_of_date() { + // Tests that a download is queued and triggered when the icon for an engine + // is not in either the local dump nor the cache. + + await assertEngineIcon("engine_icon_out_of_date name", "remoteIcon.ico"); + + // A download should have been queued, so fire idle to trigger it. + let promiseEngineUpdated = SearchTestUtils.promiseSearchNotification( + SearchUtils.MODIFIED_TYPE.CHANGED, + SearchUtils.TOPIC_ENGINE_MODIFIED + ); + SearchTestUtils.idleService._fireObservers("idle"); + await promiseEngineUpdated; + + await assertEngineIcon("engine_icon_out_of_date name", "bigIcon.ico"); +}); diff --git a/toolkit/components/search/tests/xpcshell/test_defaultEngine_fallback.js b/toolkit/components/search/tests/xpcshell/test_defaultEngine_fallback.js index 12cb6568e7..2f269cc016 100644 --- a/toolkit/components/search/tests/xpcshell/test_defaultEngine_fallback.js +++ b/toolkit/components/search/tests/xpcshell/test_defaultEngine_fallback.js @@ -17,6 +17,15 @@ let appDefault; let appPrivateDefault; +async function getSearchConfig() { + let workDir = Services.dirsvc.get("CurWorkD", Ci.nsIFile); + let configFileName = + "file://" + PathUtils.join(workDir.path, "data", "search-config-v2.json"); + + let response = await fetch(configFileName); + return response.json(); +} + add_setup(async function () { useHttpServer(); await SearchTestUtils.useTestEngines(); @@ -292,10 +301,33 @@ add_task(async function test_default_fallback_remove_default_no_visible() { add_task( async function test_default_fallback_remove_default_no_visible_or_general() { - // Reset. Services.search.restoreDefaultEngines(); - Services.search.defaultEngine = Services.search.defaultPrivateEngine = - appPrivateDefault; + + // For this test, we need to change any general search engines to unknown, + // so that we can test what happens in the unlikely event that there are no + // general search engines. + if (SearchUtils.newSearchConfigEnabled) { + let searchConfig = await getSearchConfig(); + for (let entry of searchConfig.data) { + if ( + entry.recordType == "engine" && + entry.base.classification == "general" + ) { + entry.base.classification = "unknown"; + } + } + const settings = await RemoteSettings(SearchUtils.SETTINGS_KEY); + settings.get.returns(searchConfig.data); + Services.search.wrappedJSObject.reset(); + await Services.search.init(); + + appPrivateDefault = await Services.search.getDefaultPrivate(); + + Services.search.defaultEngine = appPrivateDefault; + } else { + Services.search.defaultEngine = Services.search.defaultPrivateEngine = + appPrivateDefault; + } // Remove all but the default engine. let visibleEngines = await Services.search.getVisibleEngines(); @@ -310,7 +342,9 @@ add_task( "Should only have one visible engine" ); - SearchUtils.GENERAL_SEARCH_ENGINE_IDS.clear(); + if (!SearchUtils.newSearchConfigEnabled) { + SearchUtils.GENERAL_SEARCH_ENGINE_IDS.clear(); + } const observer = new SearchObserver( [ diff --git a/toolkit/components/search/tests/xpcshell/test_engine_selector_environment.js b/toolkit/components/search/tests/xpcshell/test_engine_selector_environment.js index bf56984cde..401392b955 100644 --- a/toolkit/components/search/tests/xpcshell/test_engine_selector_environment.js +++ b/toolkit/components/search/tests/xpcshell/test_engine_selector_environment.js @@ -388,6 +388,55 @@ const CONFIG_VERSIONS = [ }, ]; +const CONFIG_DEVICE_TYPE_LAYOUT = [ + { + recordType: "engine", + identifier: "engine-no-device-type", + base: {}, + variants: [ + { + environment: { + allRegionsAndLocales: true, + }, + }, + ], + }, + { + recordType: "engine", + identifier: "engine-single-device-type", + base: {}, + variants: [ + { + environment: { + allRegionsAndLocales: true, + deviceType: ["tablet"], + }, + }, + ], + }, + { + recordType: "engine", + identifier: "engine-multiple-device-type", + base: {}, + variants: [ + { + environment: { + allRegionsAndLocales: true, + deviceType: ["tablet", "smartphone"], + }, + }, + ], + }, + { + recordType: "defaultEngines", + specificDefaults: [], + }, + { + recordType: "engineOrders", + orders: [], + }, +]; + const engineSelector = new SearchEngineSelector(); let settings; let settingOverrides; @@ -793,3 +842,15 @@ add_task(async function test_engine_selector_does_not_match_optional_engines() { "Should match engines where optional flag is false or undefined" ); }); + +add_task(async function test_engine_selector_match_device_type() { + await assertActualEnginesEqualsExpected( + CONFIG_DEVICE_TYPE_LAYOUT, + { + locale: "en-CA", + region: "CA", + }, + ["engine-no-device-type"], + "Should only match engines with no device type." + ); +}); diff --git a/toolkit/components/search/tests/xpcshell/test_engine_selector_subvariants.js b/toolkit/components/search/tests/xpcshell/test_engine_selector_subvariants.js new file mode 100644 index 0000000000..ed61362ca6 --- /dev/null +++ b/toolkit/components/search/tests/xpcshell/test_engine_selector_subvariants.js @@ -0,0 +1,142 @@ +/* Any copyright is dedicated to the Public Domain. +https://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const CONFIG = [ + { + recordType: "engine", + identifier: "engine-1", + base: {}, + variants: [ + { + environment: { + allRegionsAndLocales: true, + }, + partnerCode: "variant-partner-code", + subVariants: [ + { + environment: { regions: ["CA", "FR"] }, + telemetrySuffix: "subvariant-telemetry", + }, + { + environment: { regions: ["GB", "FR"] }, + partnerCode: "subvariant-partner-code", + }, + ], + }, + ], + }, + { + recordType: "defaultEngines", + specificDefaults: [], + }, + { + recordType: "engineOrders", + orders: [], + }, +]; + +const engineSelector = new SearchEngineSelector(); +let settings; +let configStub; + +/** + * This function asserts if the actual engines returned equals the expected + * engines. + * + * @param {object} config + * A fake search config containing engines. + * @param {object} userEnv + * A fake user's environment including locale and region, experiment, etc. + * @param {Array} expectedEngines + * The array of expected engines to be returned from the fake config. + * @param {string} message + * The assertion message. + */ +async function assertActualEnginesEqualsExpected( + config, + userEnv, + expectedEngines, + message +) { + engineSelector._configuration = null; + configStub.returns(config); + let { engines } = await engineSelector.fetchEngineConfiguration(userEnv); + + Assert.deepEqual(engines, expectedEngines, message); +} + +add_setup(async function () { + settings = await RemoteSettings(SearchUtils.NEW_SETTINGS_KEY); + configStub = sinon.stub(settings, "get"); +}); + +add_task(async function test_no_subvariants_match() { + await assertActualEnginesEqualsExpected( + CONFIG, + { + locale: "fi", + region: "FI", + }, + [ + { + identifier: "engine-1", + partnerCode: "variant-partner-code", + }, + ], + "Should match no subvariants." + ); +}); + +add_task(async function test_matching_subvariant_with_properties() { + await assertActualEnginesEqualsExpected( + CONFIG, + { + locale: "en-GB", + region: "GB", + }, + [ + { + identifier: "engine-1", + partnerCode: "subvariant-partner-code", + }, + ], + "Should match subvariant with subvariant properties." + ); +}); + +add_task(async function test_matching_variant_and_subvariant_with_properties() { + await assertActualEnginesEqualsExpected( + CONFIG, + { + locale: "en-CA", + region: "CA", + }, + [ + { + identifier: "engine-1", + partnerCode: "variant-partner-code", + telemetrySuffix: "subvariant-telemetry", + }, + ], + "Should match subvariant with subvariant properties." + ); +}); + +add_task(async function test_matching_two_subvariant_with_properties() { + await assertActualEnginesEqualsExpected( + CONFIG, + { + locale: "fr", + region: "FR", + }, + [ + { + identifier: "engine-1", + partnerCode: "subvariant-partner-code", + }, + ], + "Should match the last subvariant with subvariant properties." + ); +}); diff --git a/toolkit/components/search/tests/xpcshell/test_engine_selector_variants.js b/toolkit/components/search/tests/xpcshell/test_engine_selector_variants.js index 0fd57f2094..51a7f0de09 100644 --- a/toolkit/components/search/tests/xpcshell/test_engine_selector_variants.js +++ b/toolkit/components/search/tests/xpcshell/test_engine_selector_variants.js @@ -122,7 +122,7 @@ add_task(async function test_no_variants_match() { ); }); -add_task(async function test_match_and_apply_all_variants() { +add_task(async function test_match_and_apply_last_variants() { await assertActualEnginesEqualsExpected( CONFIG, { @@ -133,11 +133,10 @@ add_task(async function test_match_and_apply_all_variants() { { identifier: "engine-1", urls: { search: { params: [{ name: "partner-code", value: "foo" }] } }, - telemetrySuffix: "telemetry", searchTermParamName: "search-param", }, ], - "Should match all variants and apply each variant property cumulatively." + "Should match and apply last variant." ); }); diff --git a/toolkit/components/search/tests/xpcshell/test_searchSuggest.js b/toolkit/components/search/tests/xpcshell/test_searchSuggest.js index 4a30eb741a..d24970534f 100644 --- a/toolkit/components/search/tests/xpcshell/test_searchSuggest.js +++ b/toolkit/components/search/tests/xpcshell/test_searchSuggest.js @@ -3,7 +3,7 @@ /* eslint-disable mozilla/no-arbitrary-setTimeout */ /** - * Testing search suggestions from SearchSuggestionController.jsm. + * Testing search suggestions from SearchSuggestionController.sys.mjs. */ "use strict"; @@ -23,7 +23,7 @@ const SEARCH_TELEMETRY_LATENCY = "SEARCH_SUGGESTIONS_LATENCY_MS"; // We must make sure the FormHistoryStartup component is // initialized in order for it to respond to FormHistory -// requests from nsFormAutoComplete.js. +// requests from FormHistoryAutoComplete.sys.mjs. var formHistoryStartup = Cc[ "@mozilla.org/satchel/form-history-startup;1" ].getService(Ci.nsIObserver); diff --git a/toolkit/components/search/tests/xpcshell/test_searchSuggest_cookies.js b/toolkit/components/search/tests/xpcshell/test_searchSuggest_cookies.js index 042c74d86a..bb4dda9e3f 100644 --- a/toolkit/components/search/tests/xpcshell/test_searchSuggest_cookies.js +++ b/toolkit/components/search/tests/xpcshell/test_searchSuggest_cookies.js @@ -2,7 +2,7 @@ * http://creativecommons.org/publicdomain/zero/1.0/ */ /** - * Test that search suggestions from SearchSuggestionController.jsm don't store + * Test that search suggestions from SearchSuggestionController.sys.mjs don't store * cookies. */ @@ -14,7 +14,7 @@ const { SearchSuggestionController } = ChromeUtils.importESModule( // We must make sure the FormHistoryStartup component is // initialized in order for it to respond to FormHistory -// requests from nsFormAutoComplete.js. +// requests from FormHistoryAutoComplete.sys.mjs. var formHistoryStartup = Cc[ "@mozilla.org/satchel/form-history-startup;1" ].getService(Ci.nsIObserver); diff --git a/toolkit/components/search/tests/xpcshell/test_searchSuggest_private.js b/toolkit/components/search/tests/xpcshell/test_searchSuggest_private.js index 063f3ada49..b7750c0f30 100644 --- a/toolkit/components/search/tests/xpcshell/test_searchSuggest_private.js +++ b/toolkit/components/search/tests/xpcshell/test_searchSuggest_private.js @@ -2,7 +2,7 @@ * http://creativecommons.org/publicdomain/zero/1.0/ */ /** - * Test that search suggestions from SearchSuggestionController.jsm operate + * Test that search suggestions from SearchSuggestionController.sys.mjs operate * correctly in private mode. */ diff --git a/toolkit/components/search/tests/xpcshell/test_search_config_v2_nimbus.js b/toolkit/components/search/tests/xpcshell/test_search_config_v2_nimbus.js new file mode 100644 index 0000000000..56746a614f --- /dev/null +++ b/toolkit/components/search/tests/xpcshell/test_search_config_v2_nimbus.js @@ -0,0 +1,85 @@ +/* Any copyright is dedicated to the Public Domain. +https://creativecommons.org/publicdomain/zero/1.0/ */ + +/* Test to verify search-config-v2 preference is correctly toggled via a Nimbus + variable. */ + +"use strict"; + +ChromeUtils.defineESModuleGetters(this, { + SearchTestUtils: "resource://testing-common/SearchTestUtils.sys.mjs", + ExperimentAPI: "resource://nimbus/ExperimentAPI.sys.mjs", + ExperimentFakes: "resource://testing-common/NimbusTestUtils.sys.mjs", + ExperimentManager: "resource://nimbus/lib/ExperimentManager.sys.mjs", + AppProvidedSearchEngine: + "resource://gre/modules/AppProvidedSearchEngine.sys.mjs", +}); + +add_task(async function test_nimbus_experiment_enabled() { + Assert.equal( + Services.prefs.getBoolPref("browser.search.newSearchConfig.enabled"), + false, + "newSearchConfig.enabled PREF should initially be false." + ); + + await ExperimentManager.onStartup(); + await ExperimentAPI.ready(); + + let doExperimentCleanup = await ExperimentFakes.enrollWithFeatureConfig( + { + featureId: "search", + value: { + newSearchConfigEnabled: true, + }, + }, + { isRollout: true } + ); + + Assert.equal( + Services.prefs.getBoolPref("browser.search.newSearchConfig.enabled"), + true, + "After toggling the Nimbus variable, the current value of newSearchConfig.enabled PREF should be true." + ); + + Assert.equal( + SearchUtils.newSearchConfigEnabled, + true, + "After toggling the Nimbus variable, newSearchConfig.enabled should be cached as true." + ); + + await AddonTestUtils.promiseStartupManager(); + await Services.search.init(); + await SearchTestUtils.useTestEngines(); + + let { engines: engines2 } = + await Services.search.wrappedJSObject._fetchEngineSelectorEngines(); + + Assert.ok( + engines2.some(engine => engine.identifier), + "Engines in the search-config-v2 format should have an identifier." + ); + + let appProvidedEngines = + await Services.search.wrappedJSObject.getAppProvidedEngines(); + + Assert.ok( + appProvidedEngines.every( + engine => engine instanceof AppProvidedSearchEngine + ), + "All application provided engines for search-config-v2 should be instances of AppProvidedSearchEngine." + ); + + await doExperimentCleanup(); + + Assert.equal( + Services.prefs.getBoolPref("browser.search.newSearchConfig.enabled"), + false, + "After experiment unenrollment, the newSearchConfig.enabled should be false." + ); + + Assert.equal( + SearchUtils.newSearchConfigEnabled, + true, + "After experiment unenrollment, newSearchConfig.enabled should be cached as true." + ); +}); diff --git a/toolkit/components/search/tests/xpcshell/test_settings_persist.js b/toolkit/components/search/tests/xpcshell/test_settings_persist.js index 5c2cbd85c4..e3310a1fa2 100644 --- a/toolkit/components/search/tests/xpcshell/test_settings_persist.js +++ b/toolkit/components/search/tests/xpcshell/test_settings_persist.js @@ -81,7 +81,7 @@ add_setup(async function () { registerCleanupFunction(AddonTestUtils.promiseShutdownManager); await AddonTestUtils.promiseStartupManager(); // This is only needed as otherwise events will not be properly notified - // due to https://searchfox.org/mozilla-central/source/toolkit/components/search/SearchUtils.jsm#186 + // due to https://searchfox.org/mozilla-central/rev/5f0a7ca8968ac5cef8846e1d970ef178b8b76dcc/toolkit/components/search/SearchSettings.sys.mjs#41-42 let settingsFileWritten = promiseAfterSettings(); await Services.search.init(false); Services.search.wrappedJSObject._removeObservers(); diff --git a/toolkit/components/search/tests/xpcshell/xpcshell.toml b/toolkit/components/search/tests/xpcshell/xpcshell.toml index 7dd023cbec..899ac2d711 100644 --- a/toolkit/components/search/tests/xpcshell/xpcshell.toml +++ b/toolkit/components/search/tests/xpcshell/xpcshell.toml @@ -78,9 +78,18 @@ support-files = [ ["test_appDefaultEngine.js"] +["test_appProvided_engine.js"] +prefs = ["browser.search.newSearchConfig.enabled=true"] +support-files = [ + "../../schema/search-config-v2-schema.json", +] + ["test_appProvided_icons.js"] prefs = ["browser.search.newSearchConfig.enabled=true"] +["test_appProvided_icons_updates.js"] +prefs = ["browser.search.newSearchConfig.enabled=true"] + ["test_async.js"] ["test_config_engine_params.js"] @@ -128,6 +137,8 @@ tags = "remotesettings searchmain" ["test_engine_selector_environment.js"] +["test_engine_selector_subvariants.js"] + ["test_engine_selector_variants.js"] ["test_engine_set_alias.js"] @@ -254,6 +265,10 @@ support-files = [ ["test_searchUrlDomain.js"] +["test_search_config_v2_nimbus.js"] +prefs = ["browser.search.newSearchConfig.enabled=false"] +skip-if = ["appname == 'thunderbird'"] # Test relies on normandy. + ["test_selectedEngine.js"] ["test_sendSubmissionURL.js"] |