From 6bf0a5cb5034a7e684dcc3500e841785237ce2dd Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 19:32:43 +0200 Subject: Adding upstream version 1:115.7.0. Signed-off-by: Daniel Baumann --- .../quicksuggest/unit/test_quicksuggest_merino.js | 681 +++++++++++++++++++++ 1 file changed, 681 insertions(+) create mode 100644 browser/components/urlbar/tests/quicksuggest/unit/test_quicksuggest_merino.js (limited to 'browser/components/urlbar/tests/quicksuggest/unit/test_quicksuggest_merino.js') diff --git a/browser/components/urlbar/tests/quicksuggest/unit/test_quicksuggest_merino.js b/browser/components/urlbar/tests/quicksuggest/unit/test_quicksuggest_merino.js new file mode 100644 index 0000000000..4327890a0d --- /dev/null +++ b/browser/components/urlbar/tests/quicksuggest/unit/test_quicksuggest_merino.js @@ -0,0 +1,681 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// Tests Merino integration with UrlbarProviderQuickSuggest. + +"use strict"; + +// relative to `browser.urlbar` +const PREF_DATA_COLLECTION_ENABLED = "quicksuggest.dataCollection.enabled"; +const PREF_MERINO_ENABLED = "merino.enabled"; +const PREF_REMOTE_SETTINGS_ENABLED = "quicksuggest.remoteSettings.enabled"; + +const SEARCH_STRING = "frab"; + +const REMOTE_SETTINGS_RESULTS = [ + { + id: 1, + url: "http://test.com/q=frabbits", + title: "frabbits", + keywords: [SEARCH_STRING], + click_url: "http://click.reporting.test.com/", + impression_url: "http://impression.reporting.test.com/", + advertiser: "TestAdvertiser", + }, +]; + +const EXPECTED_REMOTE_SETTINGS_URLBAR_RESULT = { + type: UrlbarUtils.RESULT_TYPE.URL, + source: UrlbarUtils.RESULT_SOURCE.SEARCH, + heuristic: false, + payload: { + telemetryType: "adm_sponsored", + qsSuggestion: SEARCH_STRING, + title: "frabbits", + url: "http://test.com/q=frabbits", + originalUrl: "http://test.com/q=frabbits", + icon: null, + sponsoredImpressionUrl: "http://impression.reporting.test.com/", + sponsoredClickUrl: "http://click.reporting.test.com/", + sponsoredBlockId: 1, + sponsoredAdvertiser: "TestAdvertiser", + isSponsored: true, + helpUrl: QuickSuggest.HELP_URL, + helpL10n: { + id: UrlbarPrefs.get("resultMenu") + ? "urlbar-result-menu-learn-more-about-firefox-suggest" + : "firefox-suggest-urlbar-learn-more", + }, + isBlockable: UrlbarPrefs.get("quickSuggestBlockingEnabled"), + blockL10n: { + id: UrlbarPrefs.get("resultMenu") + ? "urlbar-result-menu-dismiss-firefox-suggest" + : "firefox-suggest-urlbar-block", + }, + displayUrl: "http://test.com/q=frabbits", + source: "remote-settings", + }, +}; + +const EXPECTED_MERINO_URLBAR_RESULT = { + type: UrlbarUtils.RESULT_TYPE.URL, + source: UrlbarUtils.RESULT_SOURCE.SEARCH, + heuristic: false, + payload: { + telemetryType: "adm_sponsored", + qsSuggestion: "full_keyword", + title: "title", + url: "url", + originalUrl: "url", + icon: null, + sponsoredImpressionUrl: "impression_url", + sponsoredClickUrl: "click_url", + sponsoredBlockId: 1, + sponsoredAdvertiser: "advertiser", + isSponsored: true, + helpUrl: QuickSuggest.HELP_URL, + helpL10n: { + id: UrlbarPrefs.get("resultMenu") + ? "urlbar-result-menu-learn-more-about-firefox-suggest" + : "firefox-suggest-urlbar-learn-more", + }, + isBlockable: UrlbarPrefs.get("quickSuggestBlockingEnabled"), + blockL10n: { + id: UrlbarPrefs.get("resultMenu") + ? "urlbar-result-menu-dismiss-firefox-suggest" + : "firefox-suggest-urlbar-block", + }, + displayUrl: "url", + requestId: "request_id", + source: "merino", + }, +}; + +// `UrlbarProviderQuickSuggest.#merino` is lazily created on the first Merino +// fetch, so it's easiest to create `gClient` lazily too. +XPCOMUtils.defineLazyGetter( + this, + "gClient", + () => UrlbarProviderQuickSuggest._test_merino +); + +add_task(async function init() { + UrlbarPrefs.set("quicksuggest.enabled", true); + UrlbarPrefs.set("suggest.quicksuggest.nonsponsored", true); + UrlbarPrefs.set("suggest.quicksuggest.sponsored", true); + UrlbarPrefs.set("quicksuggest.shouldShowOnboardingDialog", false); + + await MerinoTestUtils.server.start(); + + // Set up the remote settings client with the test data. + await QuickSuggestTestUtils.ensureQuickSuggestInit({ + remoteSettingsResults: [ + { + type: "data", + attachment: REMOTE_SETTINGS_RESULTS, + }, + ], + }); + + Assert.equal( + typeof QuickSuggestRemoteSettings.DEFAULT_SUGGESTION_SCORE, + "number", + "Sanity check: DEFAULT_SUGGESTION_SCORE is defined" + ); +}); + +// Tests with Merino enabled and remote settings disabled. +add_task(async function oneEnabled_merino() { + UrlbarPrefs.set(PREF_MERINO_ENABLED, true); + UrlbarPrefs.set(PREF_REMOTE_SETTINGS_ENABLED, false); + UrlbarPrefs.set(PREF_DATA_COLLECTION_ENABLED, true); + + let histograms = MerinoTestUtils.getAndClearHistograms(); + + // Use a score lower than the remote settings score to make sure the + // suggestion is included regardless. + MerinoTestUtils.server.response.body.suggestions[0].score = + QuickSuggestRemoteSettings.DEFAULT_SUGGESTION_SCORE / 2; + + let context = createContext(SEARCH_STRING, { + providers: [UrlbarProviderQuickSuggest.name], + isPrivate: false, + }); + await check_results({ + context, + matches: [EXPECTED_MERINO_URLBAR_RESULT], + }); + + MerinoTestUtils.checkAndClearHistograms({ + histograms, + response: "success", + latencyRecorded: true, + client: gClient, + }); + + MerinoTestUtils.server.reset(); + gClient.resetSession(); +}); + +// Tests with Merino disabled and remote settings enabled. +add_task(async function oneEnabled_remoteSettings() { + UrlbarPrefs.set(PREF_MERINO_ENABLED, false); + UrlbarPrefs.set(PREF_REMOTE_SETTINGS_ENABLED, true); + UrlbarPrefs.set(PREF_DATA_COLLECTION_ENABLED, true); + + let histograms = MerinoTestUtils.getAndClearHistograms(); + + let context = createContext(SEARCH_STRING, { + providers: [UrlbarProviderQuickSuggest.name], + isPrivate: false, + }); + await check_results({ + context, + matches: [EXPECTED_REMOTE_SETTINGS_URLBAR_RESULT], + }); + + MerinoTestUtils.checkAndClearHistograms({ + histograms, + response: null, + latencyRecorded: false, + client: gClient, + }); +}); + +// Tests with Merino enabled but with data collection disabled. Results should +// not be fetched from Merino in that case. Also tests with remote settings +// enabled. +add_task(async function dataCollectionDisabled() { + UrlbarPrefs.set(PREF_MERINO_ENABLED, true); + UrlbarPrefs.set(PREF_REMOTE_SETTINGS_ENABLED, true); + UrlbarPrefs.set(PREF_DATA_COLLECTION_ENABLED, false); + + let context = createContext(SEARCH_STRING, { + providers: [UrlbarProviderQuickSuggest.name], + isPrivate: false, + }); + await check_results({ + context, + matches: [EXPECTED_REMOTE_SETTINGS_URLBAR_RESULT], + }); +}); + +// When the Merino suggestion has a higher score than the remote settings +// suggestion, the Merino suggestion should be used. +add_task(async function higherScore() { + UrlbarPrefs.set(PREF_MERINO_ENABLED, true); + UrlbarPrefs.set(PREF_REMOTE_SETTINGS_ENABLED, true); + UrlbarPrefs.set(PREF_DATA_COLLECTION_ENABLED, true); + + let histograms = MerinoTestUtils.getAndClearHistograms(); + + MerinoTestUtils.server.response.body.suggestions[0].score = + 2 * QuickSuggestRemoteSettings.DEFAULT_SUGGESTION_SCORE; + + let context = createContext(SEARCH_STRING, { + providers: [UrlbarProviderQuickSuggest.name], + isPrivate: false, + }); + await check_results({ + context, + matches: [EXPECTED_MERINO_URLBAR_RESULT], + }); + + MerinoTestUtils.checkAndClearHistograms({ + histograms, + response: "success", + latencyRecorded: true, + client: gClient, + }); + + MerinoTestUtils.server.reset(); + gClient.resetSession(); +}); + +// When the Merino suggestion has a lower score than the remote settings +// suggestion, the remote settings suggestion should be used. +add_task(async function lowerScore() { + UrlbarPrefs.set(PREF_MERINO_ENABLED, true); + UrlbarPrefs.set(PREF_REMOTE_SETTINGS_ENABLED, true); + UrlbarPrefs.set(PREF_DATA_COLLECTION_ENABLED, true); + + let histograms = MerinoTestUtils.getAndClearHistograms(); + + MerinoTestUtils.server.response.body.suggestions[0].score = + QuickSuggestRemoteSettings.DEFAULT_SUGGESTION_SCORE / 2; + + let context = createContext(SEARCH_STRING, { + providers: [UrlbarProviderQuickSuggest.name], + isPrivate: false, + }); + await check_results({ + context, + matches: [EXPECTED_REMOTE_SETTINGS_URLBAR_RESULT], + }); + + MerinoTestUtils.checkAndClearHistograms({ + histograms, + response: "success", + latencyRecorded: true, + client: gClient, + }); + + MerinoTestUtils.server.reset(); + gClient.resetSession(); +}); + +// When the Merino and remote settings suggestions have the same score, the +// remote settings suggestion should be used. +add_task(async function sameScore() { + UrlbarPrefs.set(PREF_MERINO_ENABLED, true); + UrlbarPrefs.set(PREF_REMOTE_SETTINGS_ENABLED, true); + UrlbarPrefs.set(PREF_DATA_COLLECTION_ENABLED, true); + + let histograms = MerinoTestUtils.getAndClearHistograms(); + + MerinoTestUtils.server.response.body.suggestions[0].score = + QuickSuggestRemoteSettings.DEFAULT_SUGGESTION_SCORE; + + let context = createContext(SEARCH_STRING, { + providers: [UrlbarProviderQuickSuggest.name], + isPrivate: false, + }); + await check_results({ + context, + matches: [EXPECTED_REMOTE_SETTINGS_URLBAR_RESULT], + }); + + MerinoTestUtils.checkAndClearHistograms({ + histograms, + response: "success", + latencyRecorded: true, + client: gClient, + }); + + MerinoTestUtils.server.reset(); + gClient.resetSession(); +}); + +// When the Merino suggestion does not include a score, the remote settings +// suggestion should be used. +add_task(async function noMerinoScore() { + UrlbarPrefs.set(PREF_MERINO_ENABLED, true); + UrlbarPrefs.set(PREF_REMOTE_SETTINGS_ENABLED, true); + UrlbarPrefs.set(PREF_DATA_COLLECTION_ENABLED, true); + + let histograms = MerinoTestUtils.getAndClearHistograms(); + + Assert.equal( + typeof MerinoTestUtils.server.response.body.suggestions[0].score, + "number", + "Sanity check: First suggestion has a score" + ); + delete MerinoTestUtils.server.response.body.suggestions[0].score; + + let context = createContext(SEARCH_STRING, { + providers: [UrlbarProviderQuickSuggest.name], + isPrivate: false, + }); + await check_results({ + context, + matches: [EXPECTED_REMOTE_SETTINGS_URLBAR_RESULT], + }); + + MerinoTestUtils.checkAndClearHistograms({ + histograms, + response: "success", + latencyRecorded: true, + client: gClient, + }); + + MerinoTestUtils.server.reset(); + gClient.resetSession(); +}); + +// When remote settings doesn't return a suggestion but Merino does, the Merino +// suggestion should be used. +add_task(async function noSuggestion_remoteSettings() { + UrlbarPrefs.set(PREF_MERINO_ENABLED, true); + UrlbarPrefs.set(PREF_REMOTE_SETTINGS_ENABLED, true); + UrlbarPrefs.set(PREF_DATA_COLLECTION_ENABLED, true); + + let histograms = MerinoTestUtils.getAndClearHistograms(); + + let context = createContext("this doesn't match remote settings", { + providers: [UrlbarProviderQuickSuggest.name], + isPrivate: false, + }); + await check_results({ + context, + matches: [EXPECTED_MERINO_URLBAR_RESULT], + }); + + MerinoTestUtils.checkAndClearHistograms({ + histograms, + response: "success", + latencyRecorded: true, + client: gClient, + }); + + MerinoTestUtils.server.reset(); + gClient.resetSession(); +}); + +// When Merino doesn't return a suggestion but remote settings does, the remote +// settings suggestion should be used. +add_task(async function noSuggestion_merino() { + UrlbarPrefs.set(PREF_MERINO_ENABLED, true); + UrlbarPrefs.set(PREF_REMOTE_SETTINGS_ENABLED, true); + UrlbarPrefs.set(PREF_DATA_COLLECTION_ENABLED, true); + + let histograms = MerinoTestUtils.getAndClearHistograms(); + + MerinoTestUtils.server.response.body.suggestions = []; + + let context = createContext(SEARCH_STRING, { + providers: [UrlbarProviderQuickSuggest.name], + isPrivate: false, + }); + await check_results({ + context, + matches: [EXPECTED_REMOTE_SETTINGS_URLBAR_RESULT], + }); + + MerinoTestUtils.checkAndClearHistograms({ + histograms, + response: "no_suggestion", + latencyRecorded: true, + client: gClient, + }); + + MerinoTestUtils.server.reset(); + gClient.resetSession(); +}); + +// Tests with both Merino and remote settings disabled. +add_task(async function bothDisabled() { + UrlbarPrefs.set(PREF_MERINO_ENABLED, false); + UrlbarPrefs.set(PREF_REMOTE_SETTINGS_ENABLED, false); + UrlbarPrefs.set(PREF_DATA_COLLECTION_ENABLED, true); + + let histograms = MerinoTestUtils.getAndClearHistograms(); + + let context = createContext(SEARCH_STRING, { + providers: [UrlbarProviderQuickSuggest.name], + isPrivate: false, + }); + await check_results({ context, matches: [] }); + + MerinoTestUtils.checkAndClearHistograms({ + histograms, + response: null, + latencyRecorded: false, + client: gClient, + }); +}); + +// When Merino returns multiple suggestions, the one with the largest score +// should be used. +add_task(async function multipleMerinoSuggestions() { + UrlbarPrefs.set(PREF_MERINO_ENABLED, true); + UrlbarPrefs.set(PREF_REMOTE_SETTINGS_ENABLED, false); + UrlbarPrefs.set(PREF_DATA_COLLECTION_ENABLED, true); + + let histograms = MerinoTestUtils.getAndClearHistograms(); + + MerinoTestUtils.server.response.body.suggestions = [ + { + provider: "adm", + full_keyword: "multipleMerinoSuggestions 0 full_keyword", + title: "multipleMerinoSuggestions 0 title", + url: "multipleMerinoSuggestions 0 url", + icon: "multipleMerinoSuggestions 0 icon", + impression_url: "multipleMerinoSuggestions 0 impression_url", + click_url: "multipleMerinoSuggestions 0 click_url", + block_id: 0, + advertiser: "multipleMerinoSuggestions 0 advertiser", + is_sponsored: true, + score: 0.1, + }, + { + provider: "adm", + full_keyword: "multipleMerinoSuggestions 1 full_keyword", + title: "multipleMerinoSuggestions 1 title", + url: "multipleMerinoSuggestions 1 url", + icon: "multipleMerinoSuggestions 1 icon", + impression_url: "multipleMerinoSuggestions 1 impression_url", + click_url: "multipleMerinoSuggestions 1 click_url", + block_id: 1, + advertiser: "multipleMerinoSuggestions 1 advertiser", + is_sponsored: true, + score: 1, + }, + { + provider: "adm", + full_keyword: "multipleMerinoSuggestions 2 full_keyword", + title: "multipleMerinoSuggestions 2 title", + url: "multipleMerinoSuggestions 2 url", + icon: "multipleMerinoSuggestions 2 icon", + impression_url: "multipleMerinoSuggestions 2 impression_url", + click_url: "multipleMerinoSuggestions 2 click_url", + block_id: 2, + advertiser: "multipleMerinoSuggestions 2 advertiser", + is_sponsored: true, + score: 0.2, + }, + ]; + + let context = createContext("test", { + providers: [UrlbarProviderQuickSuggest.name], + isPrivate: false, + }); + await check_results({ + context, + matches: [ + { + type: UrlbarUtils.RESULT_TYPE.URL, + source: UrlbarUtils.RESULT_SOURCE.SEARCH, + heuristic: false, + payload: { + telemetryType: "adm_sponsored", + qsSuggestion: "multipleMerinoSuggestions 1 full_keyword", + title: "multipleMerinoSuggestions 1 title", + url: "multipleMerinoSuggestions 1 url", + originalUrl: "multipleMerinoSuggestions 1 url", + icon: "multipleMerinoSuggestions 1 icon", + sponsoredImpressionUrl: "multipleMerinoSuggestions 1 impression_url", + sponsoredClickUrl: "multipleMerinoSuggestions 1 click_url", + sponsoredBlockId: 1, + sponsoredAdvertiser: "multipleMerinoSuggestions 1 advertiser", + isSponsored: true, + helpUrl: QuickSuggest.HELP_URL, + helpL10n: { + id: UrlbarPrefs.get("resultMenu") + ? "urlbar-result-menu-learn-more-about-firefox-suggest" + : "firefox-suggest-urlbar-learn-more", + }, + isBlockable: UrlbarPrefs.get("quickSuggestBlockingEnabled"), + blockL10n: { + id: UrlbarPrefs.get("resultMenu") + ? "urlbar-result-menu-dismiss-firefox-suggest" + : "firefox-suggest-urlbar-block", + }, + displayUrl: "multipleMerinoSuggestions 1 url", + requestId: "request_id", + source: "merino", + }, + }, + ], + }); + + MerinoTestUtils.checkAndClearHistograms({ + histograms, + response: "success", + latencyRecorded: true, + client: gClient, + }); + + MerinoTestUtils.server.reset(); + gClient.resetSession(); +}); + +// Timestamp templates in URLs should be replaced with real timestamps. +add_task(async function timestamps() { + UrlbarPrefs.set(PREF_MERINO_ENABLED, true); + UrlbarPrefs.set(PREF_REMOTE_SETTINGS_ENABLED, false); + UrlbarPrefs.set(PREF_DATA_COLLECTION_ENABLED, true); + + // Set up the Merino response with template URLs. + let suggestion = MerinoTestUtils.server.response.body.suggestions[0]; + let { TIMESTAMP_TEMPLATE } = QuickSuggest; + + suggestion.url = `http://example.com/time-${TIMESTAMP_TEMPLATE}`; + suggestion.click_url = `http://example.com/time-${TIMESTAMP_TEMPLATE}-foo`; + + // Do a search. + let context = createContext("test", { + providers: [UrlbarProviderQuickSuggest.name], + isPrivate: false, + }); + let controller = UrlbarTestUtils.newMockController({ + input: { + isPrivate: context.isPrivate, + onFirstResult() { + return false; + }, + getSearchSource() { + return "dummy-search-source"; + }, + window: { + location: { + href: AppConstants.BROWSER_CHROME_URL, + }, + }, + }, + }); + await controller.startQuery(context); + + // Should be one quick suggest result. + Assert.equal(context.results.length, 1, "One result returned"); + let result = context.results[0]; + + QuickSuggestTestUtils.assertTimestampsReplaced(result, { + url: suggestion.click_url, + sponsoredClickUrl: suggestion.click_url, + }); + + MerinoTestUtils.server.reset(); + gClient.resetSession(); +}); + +// When both suggestion types are disabled but data collection is enabled, we +// should still send requests to Merino, and the requests should include an +// empty `providers` to tell Merino not to fetch any suggestions. +add_task(async function suggestedDisabled_dataCollectionEnabled() { + UrlbarPrefs.set(PREF_MERINO_ENABLED, true); + UrlbarPrefs.set(PREF_REMOTE_SETTINGS_ENABLED, false); + UrlbarPrefs.set(PREF_DATA_COLLECTION_ENABLED, true); + UrlbarPrefs.set("suggest.quicksuggest.nonsponsored", false); + UrlbarPrefs.set("suggest.quicksuggest.sponsored", false); + + let histograms = MerinoTestUtils.getAndClearHistograms(); + + let context = createContext("test", { + providers: [UrlbarProviderQuickSuggest.name], + isPrivate: false, + }); + await check_results({ + context, + matches: [], + }); + + // Check that the request is received and includes an empty `providers`. + MerinoTestUtils.server.checkAndClearRequests([ + { + params: { + [MerinoTestUtils.SEARCH_PARAMS.QUERY]: "test", + [MerinoTestUtils.SEARCH_PARAMS.SEQUENCE_NUMBER]: 0, + [MerinoTestUtils.SEARCH_PARAMS.PROVIDERS]: "", + }, + }, + ]); + + MerinoTestUtils.checkAndClearHistograms({ + histograms, + response: "success", + latencyRecorded: true, + client: gClient, + }); + + UrlbarPrefs.set("suggest.quicksuggest.nonsponsored", true); + UrlbarPrefs.set("suggest.quicksuggest.sponsored", true); + gClient.resetSession(); +}); + +// Test whether the blocking for Merino results works. +add_task(async function block() { + UrlbarPrefs.set(PREF_MERINO_ENABLED, true); + UrlbarPrefs.set(PREF_REMOTE_SETTINGS_ENABLED, true); + UrlbarPrefs.set(PREF_DATA_COLLECTION_ENABLED, true); + + for (const suggestion of MerinoTestUtils.server.response.body.suggestions) { + await QuickSuggest.blockedSuggestions.add(suggestion.url); + } + + const context = createContext(SEARCH_STRING, { + providers: [UrlbarProviderQuickSuggest.name], + isPrivate: false, + }); + + await check_results({ + context, + matches: [EXPECTED_REMOTE_SETTINGS_URLBAR_RESULT], + }); + + await QuickSuggest.blockedSuggestions.clear(); + MerinoTestUtils.server.reset(); + gClient.resetSession(); +}); + +// Tests a Merino suggestion that is a best match. +add_task(async function bestMatch() { + UrlbarPrefs.set(PREF_MERINO_ENABLED, true); + UrlbarPrefs.set(PREF_REMOTE_SETTINGS_ENABLED, true); + UrlbarPrefs.set(PREF_DATA_COLLECTION_ENABLED, true); + + // Simply enabling the best match feature should make the mock suggestion a + // best match because the search string length is greater than the required + // best match length. + UrlbarPrefs.set("bestMatch.enabled", true); + UrlbarPrefs.set("suggest.bestmatch", true); + + let expectedResult = { ...EXPECTED_MERINO_URLBAR_RESULT }; + expectedResult.payload = { ...EXPECTED_MERINO_URLBAR_RESULT.payload }; + expectedResult.isBestMatch = true; + delete expectedResult.payload.qsSuggestion; + + await QuickSuggestTestUtils.withConfig({ + config: QuickSuggestTestUtils.BEST_MATCH_CONFIG, + callback: async () => { + let context = createContext(SEARCH_STRING, { + providers: [UrlbarProviderQuickSuggest.name], + isPrivate: false, + }); + await check_results({ + context, + matches: [expectedResult], + }); + + // This isn't necessary since `check_results()` checks `isBestMatch`, but + // check it here explicitly for good measure. + Assert.ok(context.results[0].isBestMatch, "Result is a best match"); + }, + }); + + UrlbarPrefs.clear("bestMatch.enabled"); + UrlbarPrefs.clear("suggest.bestmatch"); + + MerinoTestUtils.server.reset(); + gClient.resetSession(); +}); -- cgit v1.2.3