summaryrefslogtreecommitdiffstats
path: root/toolkit/components/translations/tests/browser/browser_translations_remote_settings.js
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/components/translations/tests/browser/browser_translations_remote_settings.js')
-rw-r--r--toolkit/components/translations/tests/browser/browser_translations_remote_settings.js303
1 files changed, 303 insertions, 0 deletions
diff --git a/toolkit/components/translations/tests/browser/browser_translations_remote_settings.js b/toolkit/components/translations/tests/browser/browser_translations_remote_settings.js
new file mode 100644
index 0000000000..50987babdf
--- /dev/null
+++ b/toolkit/components/translations/tests/browser/browser_translations_remote_settings.js
@@ -0,0 +1,303 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * @typedef {import("../translations").RemoteSettingsClient} RemoteSettingsClient
+ * @typedef {import("../../translations").TranslationModelRecord} TranslationModelRecord
+ */
+
+const { RemoteSettings } = ChromeUtils.importESModule(
+ "resource://services-settings/remote-settings.sys.mjs"
+);
+
+// The full Firefox version string.
+const firefoxFullVersion = AppConstants.MOZ_APP_VERSION_DISPLAY;
+
+// The Firefox major version string (i.e. the first set of digits).
+const firefoxMajorVersion = firefoxFullVersion.match(/\d+/);
+
+// The Firefox "AlphaZero" version string.
+// This is a version that is less than even the latest Nightly
+// which is of the form `${firefoxMajorVersion}.a1`.
+const firefoxAlphaZeroVersion = `${firefoxMajorVersion}.a0`;
+
+/**
+ * Creates a local RemoteSettingsClient for use within tests.
+ *
+ * @param {string} mockedKey
+ * @returns {RemoteSettingsClient}
+ */
+async function createRemoteSettingsClient(mockedKey) {
+ const client = RemoteSettings(mockedKey);
+ await client.db.clear();
+ await client.db.importChanges({}, Date.now());
+ return client;
+}
+
+// The following test ensures the capabilities of `filter_expression` in remote settings
+// to successfully discriminate against the Firefox version when retrieving records.
+//
+// This is used when making major breaking changes that would require particular records
+// to only show up in certain versions of Firefox, such as actual code changes that no
+// longer allow compatibility with given records.
+//
+// Some examples might be:
+//
+// - Modifying a wasm.js file that is no longer compatible with a previous wasm binary record.
+// In such a case, the old binary record would need to be shipped in versions with the old
+// wasm.js file, and the new binary record would need to be shipped in version with the new
+// wasm.js file.
+//
+// - Switching to a different library for translation or language detection.
+// Using a different library would not only mean major changes to the code, but it would
+// certainly mean that the records for the old libraries are no longer compatible.
+// We will need to ship those records only in older versions of Firefox that utilize the old
+// libraries, and ship new records in the new versions of Firefox.
+add_task(async function test_filter_current_firefox_version() {
+ // Create a new RemoteSettingsClient just for this test.
+ let client = await createRemoteSettingsClient(
+ "test_filter_current_firefox_version"
+ );
+
+ // Create a list of records that are expected to pass the filter_expression and be
+ // successfully retrieved from the remote settings client.
+ const expectedPresentRecords = [
+ {
+ name: `undefined filter expression`,
+ filter_expression: undefined,
+ },
+ {
+ name: `null filter expression`,
+ filter_expression: null,
+ },
+ {
+ name: `empty filter expression`,
+ filter_expression: ``,
+ },
+ {
+ name: `env.version == ${firefoxFullVersion}`,
+ filter_expression: `env.version|versionCompare('${firefoxFullVersion}') == 0`,
+ },
+ {
+ name: `env.version > ${firefoxAlphaZeroVersion}`,
+ filter_expression: `env.version|versionCompare('${firefoxAlphaZeroVersion}') > 0`,
+ },
+ ];
+ for (let record of expectedPresentRecords) {
+ client.db.create(record);
+ }
+
+ // Create a list of records that are expected fo fail the filter_expression and be
+ // absent when we retrieve the records from the remote settings client.
+ const expectedAbsentRecords = [
+ {
+ name: `env.version < 1`,
+ filter_expression: `env.version|versionCompare('1') < 0`,
+ },
+ ];
+ for (let record of expectedAbsentRecords) {
+ client.db.create(record);
+ }
+
+ const retrievedRecords = await client.get();
+
+ // Ensure that each record that is expected to be present exists in the retrieved records.
+ for (let expectedPresentRecord of expectedPresentRecords) {
+ is(
+ retrievedRecords.some(
+ record => record.name == expectedPresentRecord.name
+ ),
+ true,
+ `The following record was expected to be present but was not found: ${expectedPresentRecord.name}\n`
+ );
+ }
+
+ // Ensure that each record that is expected to be absent does not exist in the retrieved records.
+ for (let expectedAbsentRecord of expectedAbsentRecords) {
+ is(
+ retrievedRecords.some(record => record.name == expectedAbsentRecord.name),
+ false,
+ `The following record was expected to be absent but was found: ${expectedAbsentRecord.name}\n`
+ );
+ }
+
+ // Ensure that the length of the retrieved records is exactly the length of the records expected to be present.
+ is(
+ retrievedRecords.length,
+ expectedPresentRecords.length,
+ `Expected ${expectedPresentRecords.length} items but got ${retrievedRecords.length} items\n`
+ );
+});
+
+// The following test ensures that we are able to always retrieve the maximum
+// compatible version of records. These are for version changes that do not
+// require shipping different records based on a particular Firefox version,
+// but rather for changes to model content or wasm runtimes that are fully
+// compatible with the existing source code.
+add_task(async function test_get_records_with_multiple_versions() {
+ // Create a new RemoteSettingsClient just for this test.
+ let client = await createRemoteSettingsClient(
+ "test_get_translation_model_records"
+ );
+
+ const lookupKey = record =>
+ `${record.name}${TranslationsParent.languagePairKey(
+ record.fromLang,
+ record.toLang
+ )}`;
+
+ // A mapping of each record name to its max version.
+ const maxVersionMap = {};
+
+ // Create a list of records that are all version 1.0
+ /** @type {TranslationModelRecord[]} */
+ const versionOneRecords = [
+ {
+ id: crypto.randomUUID(),
+ name: "qualityModel.enes.bin",
+ fromLang: "en",
+ toLang: "es",
+ fileType: "qualityModel",
+ version: "1.0",
+ },
+ {
+ id: crypto.randomUUID(),
+ name: "vocab.esen.spm",
+ fromLang: "en",
+ toLang: "es",
+ fileType: "vocab",
+ version: "1.0",
+ },
+ {
+ id: crypto.randomUUID(),
+ name: "vocab.esen.spm",
+ fromLang: "es",
+ toLang: "en",
+ fileType: "vocab",
+ version: "1.0",
+ },
+ {
+ id: crypto.randomUUID(),
+ name: "lex.50.50.enes.s2t.bin",
+ fromLang: "en",
+ toLang: "es",
+ fileType: "lex",
+ version: "1.0",
+ },
+ {
+ id: crypto.randomUUID(),
+ name: "model.enes.intgemm.alphas.bin",
+ fromLang: "en",
+ toLang: "es",
+ fileType: "model",
+ version: "1.0",
+ },
+ {
+ id: crypto.randomUUID(),
+ name: "vocab.deen.spm",
+ fromLang: "en",
+ toLang: "de",
+ fileType: "vocab",
+ version: "1.0",
+ },
+ {
+ id: crypto.randomUUID(),
+ name: "lex.50.50.ende.s2t.bin",
+ fromLang: "en",
+ toLang: "de",
+ fileType: "lex",
+ version: "1.0",
+ },
+ {
+ id: crypto.randomUUID(),
+ name: "model.ende.intgemm.alphas.bin",
+ fromLang: "en",
+ toLang: "de",
+ fileType: "model",
+ version: "1.0",
+ },
+ ];
+ versionOneRecords.reduce((map, record) => {
+ map[lookupKey(record)] = record.version;
+ return map;
+ }, maxVersionMap);
+ for (const record of versionOneRecords) {
+ client.db.create(record);
+ }
+
+ // Create a list of records that are identical to some of the above, but with higher version numbers.
+ const higherVersionRecords = [
+ {
+ id: crypto.randomUUID(),
+ name: "qualityModel.enes.bin",
+ fromLang: "en",
+ toLang: "es",
+ fileType: "qualityModel",
+ version: "1.1",
+ },
+ {
+ id: crypto.randomUUID(),
+ name: "qualityModel.enes.bin",
+ fromLang: "en",
+ toLang: "es",
+ fileType: "qualityModel",
+ version: "1.2",
+ },
+ {
+ id: crypto.randomUUID(),
+ name: "vocab.esen.spm",
+ fromLang: "en",
+ toLang: "es",
+ fileType: "vocab",
+ version: "1.1",
+ },
+ ];
+ higherVersionRecords.reduce((map, record) => {
+ const key = lookupKey(record);
+ if (record.version > map[key]) {
+ map[key] = record.version;
+ }
+ return map;
+ }, maxVersionMap);
+ for (const record of higherVersionRecords) {
+ client.db.create(record);
+ }
+
+ TranslationsParent.mockTranslationsEngine(
+ client,
+ await createTranslationsWasmRemoteClient()
+ );
+
+ const retrievedRecords = await TranslationsParent.getMaxVersionRecords(
+ client,
+ { lookupKey, majorVersion: 1 }
+ );
+
+ for (const record of retrievedRecords) {
+ is(
+ lookupKey(record) in maxVersionMap,
+ true,
+ `Expected record ${record.name} to be contained in the nameToVersionMap, but found none\n`
+ );
+ is(
+ record.version,
+ maxVersionMap[lookupKey(record)],
+ `Expected record ${record.name} to be version ${
+ maxVersionMap[lookupKey(record)]
+ }, but found version ${record.version}\n`
+ );
+ }
+
+ const expectedSize = Object.keys(maxVersionMap).length;
+ is(
+ retrievedRecords.length,
+ expectedSize,
+ `Expected retrieved records to be the same size as the name-to-version map (
+ ${expectedSize}
+ ), but found ${retrievedRecords.length}\n`
+ );
+
+ TranslationsParent.unmockTranslationsEngine();
+});