Source code

Revision control

Copy as Markdown

Other Tools

Test Info: Warnings

const { AddonTestUtils } = ChromeUtils.importESModule(
);
const { TestUtils } = ChromeUtils.importESModule(
);
const { AddonManager } = ChromeUtils.importESModule(
"resource://gre/modules/AddonManager.sys.mjs"
);
const { LangPackMatcher } = ChromeUtils.importESModule(
"moz-src:///intl/locale/LangPackMatcher.sys.mjs"
);
const { RemoteSettings } = ChromeUtils.importESModule(
);
const TEST_HOST = "example.com";
const MISSING_DETAILS_ERROR =
"Missing addon details, locale/dictionary not installed";
const INVALID_TYPE_ERROR =
"Invalid type provided, locale/dictionary not installed";
const DOWNLOAD_FAILED_ERROR = "Error: Install failed: onDownloadFailed";
const ADDON_IDS = new Set();
const LANGUAGE_PACKS = [
{
guid: "langpack-fr@tests.mozilla.org",
name: {
"en-US": "French language pack",
},
type: "language",
target_locale: "fr",
current_compatible_version: {
version: "1.0",
files: [
{
platform: "all",
url: `http://${TEST_HOST}/addons/langpack-fr.xpi`,
hash: "PLACEHOLDER",
},
],
},
},
{
guid: "langpack-de@tests.mozilla.org",
name: {
"en-US": "German language pack",
},
type: "language",
target_locale: "de",
current_compatible_version: {
version: "2.0",
files: [
{
platform: "all",
url: `http://${TEST_HOST}/addons/langpack-de.xpi`,
hash: "PLACEHOLDER",
},
],
},
},
];
const DICTIONARIES = [
{
guid: "fr-one@tests.mozilla.org",
name: "First French dictionary",
type: "dictionary",
current_version: {
version: "3.0",
files: [
{
platform: "all",
url: `http://${TEST_HOST}/addons/dictionary-fr-one.xpi`,
hash: "PLACEHOLDER",
},
],
},
},
{
guid: "fr-two@tests.mozilla.org",
name: "Second French dictionary",
type: "dictionary",
current_version: {
version: "4.0",
files: [
{
platform: "all",
url: `http://${TEST_HOST}/addons/dictionary-fr-two.xpi`,
hash: "PLACEHOLDER",
},
],
},
},
];
const LANGUAGE_DICTIONARY_MAPPING = [
{
id: "fr",
dictionaries: ["fr-one@tests.mozilla.org", "fr-two@tests.mozilla.org"],
},
{
id: "de",
dictionaries: [],
},
];
let testServer;
async function setupLanguagePackAddon(languagePack) {
const currentVersion = languagePack.current_compatible_version;
const id = languagePack.guid;
const addon = AddonTestUtils.createTempWebExtensionFile({
manifest: {
name: languagePack.name["en-US"],
version: currentVersion.version,
browser_specific_settings: {
gecko: {
id,
},
},
langpack_id: languagePack.target_locale,
languages: {},
},
});
ADDON_IDS.add(id);
currentVersion.files[0].hash = `sha256:${await IOUtils.computeHexDigest(addon.path, "sha256")}`;
testServer.registerFile(new URL(currentVersion.files[0].url).pathname, addon);
return addon;
}
async function setupDictionaryAddon(dictionary) {
const currentVersion = dictionary.current_version;
const id = dictionary.guid;
const idPrefix = id.split("@")[0];
const addon = AddonTestUtils.createTempWebExtensionFile({
manifest: {
name: dictionary.name,
version: currentVersion.version,
browser_specific_settings: {
gecko: {
id,
},
},
dictionaries: {
[idPrefix]: idPrefix + ".dic",
},
},
files: {
[idPrefix + ".aff"]: "",
[idPrefix + ".dic"]: "",
},
});
ADDON_IDS.add(id);
currentVersion.files[0].hash = `sha256:${await IOUtils.computeHexDigest(addon.path, "sha256")}`;
testServer.registerFile(new URL(currentVersion.files[0].url).pathname, addon);
return addon;
}
async function testInstallAddon(
addonURL,
addonDetails,
expectedError,
description
) {
const descriptionPrefix = `[${description}]: `;
const expectedResult = !expectedError;
// Run the test case.
const stopListening = TestUtils.listenForConsoleMessages();
const result = await LangPackMatcher.mockable._installAddon(
addonURL,
addonDetails
);
const messages = await stopListening();
// Check the result and error message.
Assert.equal(result, expectedResult, descriptionPrefix + "Correct result");
if (expectedError) {
Assert.ok(
messages.some(
message =>
message.level === "error" && message.arguments[0] == expectedError
),
descriptionPrefix + "Error message shown"
);
}
// Check the add-on install.
if (addonDetails?.id) {
const addon = await AddonManager.getAddonByID(addonDetails.id);
Assert.equal(
!!addon,
expectedResult,
descriptionPrefix + "Add-on installed"
);
if (addon) {
for (const key of ["id", "type", "version"]) {
Assert.equal(addon[key], addonDetails[key], descriptionPrefix + key);
}
await addon.uninstall();
}
}
}
async function testInstallLangPack(
langPack,
expectedInstalledAddonIDs,
description
) {
const descriptionPrefix = `[${description}]: `;
const expectedResult = !!expectedInstalledAddonIDs.length;
const expectedInstallsFinished = Promise.all(
expectedInstalledAddonIDs.map(id =>
AddonTestUtils.promiseAddonEvent("onInstalled", addon => addon.id === id)
)
);
const result = await LangPackMatcher.mockable.installLangPack(langPack);
Assert.equal(result, expectedResult, descriptionPrefix + "Correct result");
// Dictionaries are installed after the installLangPack result is returned,
// so take care to wait for them to finish.
await expectedInstallsFinished;
const installedAddons = (
await AddonManager.getAddonsByIDs(Array.from(ADDON_IDS))
).filter(addon => addon);
const installedAddonIDs = installedAddons.map(addon => addon.id).sort();
Assert.deepEqual(
installedAddonIDs,
expectedInstalledAddonIDs.sort(),
descriptionPrefix + "Correct add-ons installed"
);
for (const addon of installedAddons) {
await addon.uninstall();
}
}
add_setup(async function setup() {
AddonTestUtils.init(this);
AddonTestUtils.overrideCertDB();
AddonTestUtils.createAppInfo(
"xpcshell@tests.mozilla.org",
"XPCShell",
"1",
"1"
);
// Set up the testing HTTP server.
testServer = AddonTestUtils.createHttpServer({ hosts: [TEST_HOST] });
testServer.registerDirectory("/data/", do_get_file("data"));
// Set up the language packs and dictionaries endpoints.
AddonTestUtils.registerJSON(testServer, "/langpacks.json", {
results: LANGUAGE_PACKS,
});
// Note: Skip handling the "?guid=%IDS%&lang=%LOCALE%" filtering, since
// `getAddonsByIDs()` filters results by the requested ID anyway.
AddonTestUtils.registerJSON(testServer, "/dictionaries.json", {
results: DICTIONARIES,
next: null,
});
// Use the dictionary endpoint.
Services.prefs.setStringPref(
"extensions.getAddons.get.url",
`http://${TEST_HOST}/dictionaries.json`
);
// Set up the language pack to dictionary mapping.
const dictionaryClient = RemoteSettings("language-dictionaries");
await dictionaryClient.db.importChanges(
{},
Date.now(),
LANGUAGE_DICTIONARY_MAPPING,
{ clear: true }
);
// Create the language pack and dictionary addons.
await Promise.all([
...LANGUAGE_PACKS.map(setupLanguagePackAddon),
...DICTIONARIES.map(setupDictionaryAddon),
]);
await AddonTestUtils.promiseStartupManager();
});
// Test _installAddon installs locales/dictionaries correctly, but does not
// install non-language add-ons, or add-ons with incorrect or missing details.
add_task(async function test_installAddon_details_validated() {
// Correct French language pack details.
const {
guid: id,
current_compatible_version: {
version,
files: [{ hash, url }],
},
} = LANGUAGE_PACKS[0];
const type = "locale";
await testInstallAddon(url, {}, MISSING_DETAILS_ERROR, "No add-on details");
await testInstallAddon(
url,
{ version, type },
MISSING_DETAILS_ERROR,
"Missing add-on ID"
);
await testInstallAddon(
url,
{ id, type },
MISSING_DETAILS_ERROR,
"Missing add-on version"
);
await testInstallAddon(
url,
{ id, version },
MISSING_DETAILS_ERROR,
"Missing add-on type"
);
await testInstallAddon(
url,
{ id, version, type: "extension" },
INVALID_TYPE_ERROR,
"Non-language add-on type"
);
await testInstallAddon(
url,
{ id, version: "2.0", type },
DOWNLOAD_FAILED_ERROR,
"Wrong version"
);
await testInstallAddon(
url,
{ id, version, type: "dictionary" },
DOWNLOAD_FAILED_ERROR,
"Wrong type"
);
await testInstallAddon(
url,
{ id: "wrong", version, type },
DOWNLOAD_FAILED_ERROR,
"Wrong ID"
);
await testInstallAddon(
url,
{ id, version, type, hash: "wrong" },
DOWNLOAD_FAILED_ERROR,
"Wrong hash"
);
await testInstallAddon(url, { id, version, type }, null, "Valid add-on");
await testInstallAddon(
url,
{ id, version, type, hash },
null,
"Valid add-on, with hash"
);
});
// Test _installAddon installs add-ons correctly, but only when the served XPI's
// details actually match the advertised details for the locale/dictionary.
add_task(async function test_installAddon_xpi_details_validated() {
// Correct French dictionary details.
const frenchDictionary = DICTIONARIES[0];
const {
guid: id,
current_version: {
version,
files: [{ url }],
},
type,
} = frenchDictionary;
// The install should work initially.
await testInstallAddon(url, { id, version, type }, null, "Valid add-on");
// Install should fail when the XPI details don't match.
await setupDictionaryAddon({
...frenchDictionary,
guid: "wrong@mozilla.org",
});
await testInstallAddon(
url,
{ id, version, type },
DOWNLOAD_FAILED_ERROR,
"Wrong ID in XPI"
);
await setupDictionaryAddon({
...frenchDictionary,
current_version: {
...frenchDictionary.current_version,
version: "99.0",
},
});
await testInstallAddon(
url,
{ id, version, type },
DOWNLOAD_FAILED_ERROR,
"Wrong version in XPI"
);
await setupLanguagePackAddon({
...LANGUAGE_PACKS[0],
guid: id,
current_compatible_version: frenchDictionary.current_version,
});
await testInstallAddon(
url,
{ id, version, type },
DOWNLOAD_FAILED_ERROR,
"Wrong type in XPI"
);
// Install should work again after the XPI is fixed.
await setupDictionaryAddon(frenchDictionary);
await testInstallAddon(
url,
{ id, version, type },
null,
"Valid add-on, restored"
);
});
// Test that installLangPack fails correctly where there is no matching locale
// available, or when the details are incorrect, or are missing.
add_task(async function test_installLangPack_failure() {
await testInstallLangPack(
{
url: `http://${TEST_HOST}/addons/langpack-wrong.xpi`,
id: "wrong@tests.mozilla.org",
version: "1.0",
target_locale: "wrong",
},
[],
"No matching language pack"
);
await testInstallLangPack(
{
url: `http://${TEST_HOST}/addons/dictionary-fr-one.xpi`,
id: "fr-one@tests.mozilla.org",
version: "3.0",
target_locale: "fr",
},
[],
"Wrong type (dictionary)"
);
await testInstallLangPack(
{
url: `http://${TEST_HOST}/addons/langpack-de.xpi`,
id: "langpack-de@tests.mozilla.org",
version: "1.0",
target_locale: "de",
},
[],
"Wrong version"
);
await testInstallLangPack(
{
url: `http://${TEST_HOST}/addons/langpack-de.xpi`,
id: "wrong@tests.mozilla.org",
version: "2.0",
target_locale: "de",
},
[],
"Wrong ID"
);
await testInstallLangPack(
{
url: `http://${TEST_HOST}/addons/langpack-de.xpi`,
version: "2.0",
target_locale: "de",
},
[],
"Missing ID"
);
await testInstallLangPack(
{
url: `http://${TEST_HOST}/addons/langpack-de.xpi`,
id: "langpack-de@tests.mozilla.org",
target_locale: "de",
},
[],
"Missing version"
);
await testInstallLangPack(
{
url: `http://${TEST_HOST}/addons/langpack-de.xpi`,
id: "langpack-de@tests.mozilla.org",
version: "2.0",
target_locale: "de",
hash: "wrong",
},
[],
"Wrong hash"
);
});
// Test that installLangPack works correctly when installing valid language
// packs and dictionaries.
add_task(async function test_installLangPack_success() {
await testInstallLangPack(
{
url: `http://${TEST_HOST}/addons/langpack-de.xpi`,
id: "langpack-de@tests.mozilla.org",
version: "2.0",
target_locale: "de",
},
["langpack-de@tests.mozilla.org"],
"Language pack with no dictionaries"
);
await testInstallLangPack(
{
url: `http://${TEST_HOST}/addons/langpack-de.xpi`,
id: "langpack-de@tests.mozilla.org",
version: "2.0",
target_locale: "de",
hash: LANGUAGE_PACKS[1].current_compatible_version.files[0].hash,
},
["langpack-de@tests.mozilla.org"],
"Language pack with no dictionaries (with hash)"
);
await testInstallLangPack(
{
url: `http://${TEST_HOST}/addons/langpack-fr.xpi`,
id: "langpack-fr@tests.mozilla.org",
version: "1.0",
target_locale: "fr",
},
[
"langpack-fr@tests.mozilla.org",
"fr-one@tests.mozilla.org",
"fr-two@tests.mozilla.org",
],
"Language pack with two dictionaries"
);
});