Source code

Revision control

Copy as Markdown

Other Tools

Test Info: Warnings

/* Any copyright is dedicated to the Public Domain.
/**
* Testing search suggestions from SearchSuggestionController.sys.mjs.
*/
"use strict";
const { SearchSuggestionController } = ChromeUtils.importESModule(
"moz-src:///toolkit/components/search/SearchSuggestionController.sys.mjs"
);
const { ObliviousHTTP } = ChromeUtils.importESModule(
"resource://gre/modules/ObliviousHTTP.sys.mjs"
);
const ENGINE_ID = "suggestions-engine-test";
let server = useHttpServer();
server.registerContentType("sjs", "sjs");
const CONFIG = [
{
identifier: ENGINE_ID,
base: {
name: "other",
urls: {
suggestions: {
base: `${gHttpURL}/sjs/searchSuggestions.sjs`,
params: [
{
name: "parameter",
value: "14235",
},
],
searchTermParamName: "q",
},
},
},
},
];
add_setup(async function () {
consoleAllowList = consoleAllowList.concat([
"SearchSuggestionController found an unexpected string value",
]);
Services.fog.initializeFOG();
Services.prefs.setBoolPref("browser.search.suggest.enabled", true);
Services.prefs.setCharPref(
"browser.urlbar.merino.ohttpConfigURL",
);
Services.prefs.setCharPref(
"browser.urlbar.merino.ohttpRelayURL",
);
Services.prefs.setBoolPref("browser.search.suggest.ohttp.featureGate", true);
Services.prefs.setBoolPref("browser.search.suggest.ohttp.enabled", true);
SearchTestUtils.setRemoteSettingsConfig(CONFIG);
await Services.search.init();
SearchSuggestionController.oHTTPEngineId = CONFIG[0].identifier;
sinon.stub(ObliviousHTTP, "getOHTTPConfig").resolves({});
sinon.stub(ObliviousHTTP, "ohttpRequest").callsFake(() => {});
});
async function do_successful_request(controller) {
ObliviousHTTP.ohttpRequest.callsFake(() => {
return {
status: 200,
json: async () =>
Promise.resolve({
suggestions: [
{
title: "",
provider: "google_suggest",
is_sponsored: false,
score: 1,
custom_details: {
google_suggest: {
suggestions: ["oh", ["ohttp"]],
},
},
},
],
}),
ok: true,
};
});
let result = await controller.fetch({
searchString: "oh",
inPrivateBrowsing: false,
engine: Services.search.defaultEngine,
});
Assert.equal(
ObliviousHTTP.ohttpRequest.callCount,
1,
"Should have requested via OHTTP once"
);
Assert.equal(result.term, "oh", "Should have the term matching the query");
Assert.equal(result.local.length, 0, "Should have no local suggestions");
Assert.deepEqual(
result.remote.map(r => r.value),
["ohttp"],
"Should have the expected remote suggestions"
);
ObliviousHTTP.ohttpRequest.resetHistory();
}
async function do_failed_request(controller) {
ObliviousHTTP.ohttpRequest.callsFake(() => {
return {
status: 200,
json: async () =>
Promise.resolve({
suggestions: [],
}),
ok: true,
};
});
let result = await controller.fetch({
searchString: "oh",
inPrivateBrowsing: false,
engine: Services.search.defaultEngine,
});
Assert.equal(
ObliviousHTTP.ohttpRequest.callCount,
1,
"Should have requested via OHTTP once"
);
Assert.equal(result.term, "oh", "Should have the term matching the query");
Assert.equal(result.local.length, 0, "Should have no local suggestions");
Assert.equal(result.remote.length, 0, "Should have no remote suggestions");
ObliviousHTTP.ohttpRequest.resetHistory();
}
async function do_request_expect_fallback_direct(controller) {
ObliviousHTTP.ohttpRequest.callsFake(() => {
return {
status: 200,
json: async () =>
Promise.resolve({
suggestions: [],
}),
ok: true,
};
});
let result = await controller.fetch({
searchString: "mo",
inPrivateBrowsing: false,
engine: Services.search.defaultEngine,
});
Assert.equal(
ObliviousHTTP.ohttpRequest.callCount,
0,
"Should not have requested via OHTTP"
);
Assert.equal(result.term, "mo", "Should have the term matching the query");
Assert.equal(result.local.length, 0, "Should have no local suggestions");
Assert.deepEqual(
result.remote.map(r => r.value),
["Mozilla", "modern", "mom"],
"Should have remote suggestions from searchSuggestions.sjs"
);
}
add_task(async function search_suggestions_fallsback_to_direct_http() {
let controller = new SearchSuggestionController();
info("Initial request via OHTTP should be successful");
await do_successful_request(controller);
await assertTelemetry({ success: 1, failed: 0 });
info("First failed request");
await do_failed_request(controller);
await assertTelemetry({ success: 1, failed: 1 });
info("Second failed request");
await do_failed_request(controller);
await assertTelemetry({ success: 1, failed: 2 });
// Reset fog for easier counting.
Services.fog.testResetFOG();
info("Successful Request should reset the counter");
await do_successful_request(controller);
await assertTelemetry({ success: 1, failed: 0 });
info("Start 5 failed requests");
for (
let i = 0;
i < SearchSuggestionController.MAX_OHTTP_FAILURES_BEFORE_FALLBACK;
i++
) {
info(`Failed request ${i + 1}`);
await do_failed_request(controller);
}
await assertTelemetry({ success: 1, failed: 5 });
// Reset fog for easier counting.
Services.fog.testResetFOG();
info("Request should fallback to direct HTTP");
await do_request_expect_fallback_direct(controller);
await assertTelemetry({ success: 0, failed: 0 });
info("Request should fallback to direct HTTP with longer time in past");
// Subtract an hour.
controller._ohttpLastFailureTimeMs -= 1 * 60 * 60 * 1000;
await do_request_expect_fallback_direct(controller);
await assertTelemetry({ success: 0, failed: 0 });
info("Requests should resume OHTTP after time has expired, but an extra");
info("failed request should not cause fallback straight away.");
controller._ohttpLastFailureTimeMs -= (1 * 60 + 1) * 60 * 1000;
await do_failed_request(controller);
await assertTelemetry({ success: 0, failed: 1 });
// Subtract a second hour and a little bit.
await do_successful_request(controller);
await assertTelemetry({ success: 1, failed: 1 });
});
async function assertTelemetry({ success, failed }) {
Assert.equal(
Glean.searchSuggestionsOhttp.requestCounter
.get(ENGINE_ID, "success")
.testGetValue(),
success ? success : null,
`Should ${success ? "" : "not "}have incremented the successes`
);
for (
let i = 1;
i <= SearchSuggestionController.MAX_OHTTP_FAILURES_BEFORE_FALLBACK;
i++
) {
Assert.equal(
Glean.searchSuggestionsOhttp.requestCounter
.get(ENGINE_ID, "failed" + i)
.testGetValue(),
failed >= i ? 1 : null,
`Should ${success ? "" : "not "}have incremented failed${i}`
);
}
}