Skip to content

Commit

Permalink
Bug 1736684 - Part 2: Add test coverage for the search-detection buil…
Browse files Browse the repository at this point in the history
…t-in add-on (server side). r=rpl

- `test_redirect_{final,two_hops,three_hops}` correspond to SSR #1
- `test_no_event_when_search_engine_not_used` corresponds to SSR #2
- `test_redirect_chain_does_not_start_on_first_request` corresponds to SSR #3
- `test_two_extensions_reported` corresponds to SSR #4

Differential Revision: https://phabricator.services.mozilla.com/D129176
  • Loading branch information
willdurand committed Oct 25, 2021
1 parent 9aff821 commit 21c2aee
Show file tree
Hide file tree
Showing 4 changed files with 297 additions and 3 deletions.
3 changes: 3 additions & 0 deletions browser/extensions/search-detection/tests/browser/browser.ini
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
[DEFAULT]
support-files =
redirect.sjs

[browser_client_side_redirection.js]
[browser_extension_loaded.js]
[browser_server_side_redirection.js]
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
/* 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/. */
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */

"use strict";

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,260 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */

"use strict";

const { AddonTestUtils } = ChromeUtils.import(
"resource://testing-common/AddonTestUtils.jsm"
);

const { TelemetryTestUtils } = ChromeUtils.import(
"resource://testing-common/TelemetryTestUtils.jsm"
);

AddonTestUtils.initMochitest(this);

const TELEMETRY_EVENTS_FILTERS = {
category: "addonsSearchDetection",
method: "etld_change",
};

// The search-detection built-in add-on registers dynamic events.
const TELEMETRY_TEST_UTILS_OPTIONS = { clear: true, process: "dynamic" };

const REDIRECT_SJS =
"browser/browser/extensions/search-detection/tests/browser/redirect.sjs?q={searchTerms}";
// This URL will redirect to `example.net`, which is different than
// `*.example.com`. That will be the final URL of a redirect chain:
// www.example.com -> example.net
const SEARCH_URL_WWW = `https://www.example.com/${REDIRECT_SJS}`;
// This URL will redirect to `www.example.com`, which will create a redirect
// chain with two hops:
// test2.example.com -> www.example.com -> example.net
const SEARCH_URL_TEST2 = `https://test2.example.com/${REDIRECT_SJS}`;
// This URL will redirect to `test2.example.com`, which will create a redirect
// chain with three hops:
// test1.example.com -> test2.example.com -> www.example.com -> example.net
const SEARCH_URL_TEST1 = `https://test1.example.com/${REDIRECT_SJS}`;

const TEST_SEARCH_ENGINE_ADDON_ID = "some@addon-id";
const TEST_SEARCH_ENGINE_ADDON_VERSION = "4.5.6";

const testServerSideRedirect = async ({
searchURL,
expectedEvents,
tabURL,
}) => {
Services.telemetry.clearEvents();

const searchEngineName = "test search engine";
// Load a default search engine because the add-on we are testing here
// monitors the search engines.
const searchEngine = ExtensionTestUtils.loadExtension({
manifest: {
version: TEST_SEARCH_ENGINE_ADDON_VERSION,
browser_specific_settings: {
gecko: { id: TEST_SEARCH_ENGINE_ADDON_ID },
},
chrome_settings_overrides: {
search_provider: {
name: searchEngineName,
keyword: "test",
search_url: searchURL,
},
},
},
useAddonManager: "temporary",
});

await searchEngine.startup();
ok(
Services.search.getEngineByName(searchEngineName),
"test search engine registered"
);
await AddonTestUtils.waitForSearchProviderStartup(searchEngine);

// Simulate a search (with the test search engine) by navigating to it.
const url = tabURL || searchURL.replace("{searchTerms}", "some terms");
await BrowserTestUtils.withNewTab("about:blank", async browser => {
// Wait for the tab to be fully loaded.
let loaded = BrowserTestUtils.browserLoaded(browser);
BrowserTestUtils.loadURI(browser, url);
await loaded;
});

await searchEngine.unload();
ok(
!Services.search.getEngineByName(searchEngineName),
"test search engine unregistered"
);

TelemetryTestUtils.assertEvents(
expectedEvents,
TELEMETRY_EVENTS_FILTERS,
TELEMETRY_TEST_UTILS_OPTIONS
);
};

add_task(function test_redirect_final() {
return testServerSideRedirect({
// www.example.com -> example.net
searchURL: SEARCH_URL_WWW,
expectedEvents: [
{
object: "other",
value: "server",
extra: {
addonId: TEST_SEARCH_ENGINE_ADDON_ID,
addonVersion: TEST_SEARCH_ENGINE_ADDON_VERSION,
from: "example.com",
to: "example.net",
},
},
],
});
});

add_task(function test_redirect_two_hops() {
return testServerSideRedirect({
// test2.example.com -> www.example.com -> example.net
searchURL: SEARCH_URL_TEST2,
expectedEvents: [
{
object: "other",
value: "server",
extra: {
addonId: TEST_SEARCH_ENGINE_ADDON_ID,
addonVersion: TEST_SEARCH_ENGINE_ADDON_VERSION,
from: "example.com",
to: "example.net",
},
},
],
});
});

add_task(function test_redirect_three_hops() {
return testServerSideRedirect({
// test1.example.com -> test2.example.com -> www.example.com -> example.net
searchURL: SEARCH_URL_TEST1,
expectedEvents: [
{
object: "other",
value: "server",
extra: {
addonId: TEST_SEARCH_ENGINE_ADDON_ID,
addonVersion: TEST_SEARCH_ENGINE_ADDON_VERSION,
from: "example.com",
to: "example.net",
},
},
],
});
});

add_task(function test_no_event_when_search_engine_not_used() {
return testServerSideRedirect({
// www.example.com -> example.net
searchURL: SEARCH_URL_WWW,
// We do not expect any events because the user is not using the search
// engine that was registered.
tabURL: "http://mochi.test:8888/search?q=foobar",
expectedEvents: [],
});
});

add_task(function test_redirect_chain_does_not_start_on_first_request() {
return testServerSideRedirect({
// www.example.com -> example.net
searchURL: SEARCH_URL_WWW,
// User first navigates to an URL that isn't monitored and will be
// redirected to another URL that is monitored.
tabURL: `http://mochi.test:8888/browser/browser/extensions/search-detection/tests/browser/redirect.sjs?q={searchTerms}`,
expectedEvents: [
{
object: "other",
value: "server",
extra: {
addonId: TEST_SEARCH_ENGINE_ADDON_ID,
addonVersion: TEST_SEARCH_ENGINE_ADDON_VERSION,
// We expect this and not `mochi.test` because we do not monitor
// `mochi.test`, only `example.com`, which is coming from the search
// engine registered in the test setup.
from: "example.com",
to: "example.net",
},
},
],
});
});

add_task(async function test_two_extensions_reported() {
Services.telemetry.clearEvents();

const searchEngines = [];
for (const [addonId, addonVersion, isDefault] of [
["1-addon@guid", "1.2", false],
["2-addon@guid", "3.4", true],
]) {
const searchEngine = ExtensionTestUtils.loadExtension({
manifest: {
version: addonVersion,
browser_specific_settings: {
gecko: { id: addonId },
},
chrome_settings_overrides: {
search_provider: {
is_default: isDefault,
name: `test search engine - ${addonId}`,
keyword: "test",
search_url: `${SEARCH_URL_WWW}&id=${addonId}`,
},
},
},
useAddonManager: "temporary",
});

await searchEngine.startup();
await AddonTestUtils.waitForSearchProviderStartup(searchEngine);

searchEngines.push(searchEngine);
}

// Simulate a search by navigating to it.
const url = SEARCH_URL_WWW.replace("{searchTerms}", "some terms");
await BrowserTestUtils.withNewTab("about:blank", async browser => {
// Wait for the tab to be fully loaded.
let loaded = BrowserTestUtils.browserLoaded(browser);
BrowserTestUtils.loadURI(browser, url);
await loaded;
});

await Promise.all(searchEngines.map(engine => engine.unload()));

TelemetryTestUtils.assertEvents(
[
{
object: "other",
value: "server",
extra: {
addonId: "1-addon@guid",
addonVersion: "1.2",
from: "example.com",
to: "example.net",
},
},
{
object: "other",
value: "server",
extra: {
addonId: "2-addon@guid",
addonVersion: "3.4",
from: "example.com",
to: "example.net",
},
},
],
TELEMETRY_EVENTS_FILTERS,
TELEMETRY_TEST_UTILS_OPTIONS
);
});
32 changes: 32 additions & 0 deletions browser/extensions/search-detection/tests/browser/redirect.sjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
const REDIRECT_SJS =
"browser/browser/extensions/search-detection/tests/browser/redirect.sjs";

// This handler is used to create redirect chains with multiple sub-domains,
// and the next hop is defined by the current `host`.
function handleRequest(request, response) {
let newLocation;

// test1.example.com -> test2.example.com -> www.example.com -> example.net
switch (request.host) {
case "test1.example.com":
newLocation = `https://test2.example.com/${REDIRECT_SJS}`;
break;
case "test2.example.com":
newLocation = `https://www.example.com/${REDIRECT_SJS}`;
break;
case "www.example.com":
newLocation = "https://example.net/";
break;
// We redirect `mochi.test` to `www` in
// `test_redirect_chain_does_not_start_on_first_request()`.
case "mochi.test":
newLocation = `https://www.example.com/${REDIRECT_SJS}`;
break;
default:
// Redirect to a different website in case of unexpected events.
newLocation = "https://mozilla.org/";
}

response.setStatusLine(request.httpVersion, 302, "Found");
response.setHeader("Location", newLocation);
}

0 comments on commit 21c2aee

Please sign in to comment.