summaryrefslogtreecommitdiffstats
path: root/toolkit/components/search/tests/xpcshell/searchconfigs/test_searchconfig_validates.js
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/components/search/tests/xpcshell/searchconfigs/test_searchconfig_validates.js')
-rw-r--r--toolkit/components/search/tests/xpcshell/searchconfigs/test_searchconfig_validates.js209
1 files changed, 209 insertions, 0 deletions
diff --git a/toolkit/components/search/tests/xpcshell/searchconfigs/test_searchconfig_validates.js b/toolkit/components/search/tests/xpcshell/searchconfigs/test_searchconfig_validates.js
new file mode 100644
index 0000000000..51e71ff573
--- /dev/null
+++ b/toolkit/components/search/tests/xpcshell/searchconfigs/test_searchconfig_validates.js
@@ -0,0 +1,209 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+ChromeUtils.defineESModuleGetters(this, {
+ JsonSchema: "resource://gre/modules/JsonSchema.sys.mjs",
+ SearchEngineSelectorOld:
+ "resource://gre/modules/SearchEngineSelectorOld.sys.mjs",
+});
+
+/**
+ * Checks to see if a value is an object or not.
+ *
+ * @param {*} value
+ * The value to check.
+ * @returns {boolean}
+ */
+function isObject(value) {
+ return value != null && typeof value == "object" && !Array.isArray(value);
+}
+
+/**
+ * This function modifies the schema to prevent allowing additional properties
+ * on objects. This is used to enforce that the schema contains everything that
+ * we deliver via the search configuration.
+ *
+ * These checks are not enabled in-product, as we want to allow older versions
+ * to keep working if we add new properties for whatever reason.
+ *
+ * @param {object} section
+ * The section to check to see if an additionalProperties flag should be added.
+ */
+function disallowAdditionalProperties(section) {
+ // It is generally acceptable for new properties to be added to the
+ // configuration as older builds will ignore them.
+ //
+ // As a result, we only check for new properties on nightly builds, and this
+ // avoids us having to uplift schema changes. This also helps preserve the
+ // schemas as documentation of "what was supported in this version".
+ if (!AppConstants.NIGHTLY_BUILD) {
+ return;
+ }
+
+ // If the section is a `oneOf` section, avoid the additionalProperties check.
+ // Otherwise, the validator expects all properties of any `oneOf` item to be
+ // present.
+ if (isObject(section)) {
+ if (section.properties && !("recordType" in section.properties)) {
+ section.additionalProperties = false;
+ }
+ if ("then" in section) {
+ section.then.additionalProperties = false;
+ }
+ }
+
+ for (let value of Object.values(section)) {
+ if (isObject(value)) {
+ disallowAdditionalProperties(value);
+ } else if (Array.isArray(value)) {
+ for (let item of value) {
+ disallowAdditionalProperties(item);
+ }
+ }
+ }
+}
+
+let searchConfigSchemaV1;
+let searchConfigSchema;
+
+add_setup(async function () {
+ searchConfigSchemaV1 = await IOUtils.readJSON(
+ PathUtils.join(do_get_cwd().path, "search-config-schema.json")
+ );
+ searchConfigSchema = await IOUtils.readJSON(
+ PathUtils.join(do_get_cwd().path, "search-config-v2-schema.json")
+ );
+});
+
+async function checkSearchConfigValidates(schema, searchConfig) {
+ disallowAdditionalProperties(schema);
+ let validator = new JsonSchema.Validator(schema);
+
+ for (let entry of searchConfig) {
+ // Records in Remote Settings contain additional properties independent of
+ // the schema. Hence, we don't want to validate their presence.
+ delete entry.schema;
+ delete entry.id;
+ delete entry.last_modified;
+
+ let result = validator.validate(entry);
+ // entry.webExtension.id supports search-config v1.
+ let message = `Should validate ${
+ entry.identifier ?? entry.recordType ?? entry.webExtension.id
+ }`;
+ if (!result.valid) {
+ message += `:\n${JSON.stringify(result.errors, null, 2)}`;
+ }
+ Assert.ok(result.valid, message);
+
+ // All engine objects should have the base URL defined for each entry in
+ // entry.base.urls.
+ // Unfortunately this is difficult to enforce in the schema as it would
+ // need a `required` field that works across multiple levels.
+ if (entry.recordType == "engine") {
+ for (let urlEntry of Object.values(entry.base.urls)) {
+ Assert.ok(
+ urlEntry.base,
+ "Should have a base url for every URL defined on the top-level base object."
+ );
+ }
+ }
+ }
+}
+
+async function checkSearchConfigOverrideValidates(
+ schema,
+ searchConfigOverride
+) {
+ let validator = new JsonSchema.Validator(schema);
+
+ for (let entry of searchConfigOverride) {
+ // Records in Remote Settings contain additional properties independent of
+ // the schema. Hence, we don't want to validate their presence.
+ delete entry.schema;
+ delete entry.id;
+ delete entry.last_modified;
+
+ let result = validator.validate(entry);
+
+ let message = `Should validate ${entry.identifier ?? entry.telemetryId}`;
+ if (!result.valid) {
+ message += `:\n${JSON.stringify(result.errors, null, 2)}`;
+ }
+ Assert.ok(result.valid, message);
+ }
+}
+
+add_task(async function test_search_config_validates_to_schema_v1() {
+ let selector = new SearchEngineSelectorOld(() => {});
+ let searchConfig = await selector.getEngineConfiguration();
+
+ await checkSearchConfigValidates(searchConfigSchemaV1, searchConfig);
+});
+
+add_task(async function test_ui_schema_valid_v1() {
+ let uiSchema = await IOUtils.readJSON(
+ PathUtils.join(do_get_cwd().path, "search-config-ui-schema.json")
+ );
+
+ await checkUISchemaValid(searchConfigSchemaV1, uiSchema);
+});
+
+add_task(async function test_search_config_override_validates_to_schema_v1() {
+ let selector = new SearchEngineSelectorOld(() => {});
+ let searchConfigOverrides = await selector.getEngineConfigurationOverrides();
+ let overrideSchema = await IOUtils.readJSON(
+ PathUtils.join(do_get_cwd().path, "search-config-overrides-schema.json")
+ );
+
+ await checkSearchConfigOverrideValidates(
+ overrideSchema,
+ searchConfigOverrides
+ );
+});
+
+add_task(
+ { skip_if: () => !SearchUtils.newSearchConfigEnabled },
+ async function test_search_config_validates_to_schema() {
+ delete SearchUtils.newSearchConfigEnabled;
+ SearchUtils.newSearchConfigEnabled = true;
+
+ let selector = new SearchEngineSelector(() => {});
+ let searchConfig = await selector.getEngineConfiguration();
+
+ await checkSearchConfigValidates(searchConfigSchema, searchConfig);
+ }
+);
+
+add_task(
+ { skip_if: () => !SearchUtils.newSearchConfigEnabled },
+ async function test_ui_schema_valid() {
+ let uiSchema = await IOUtils.readJSON(
+ PathUtils.join(do_get_cwd().path, "search-config-v2-ui-schema.json")
+ );
+
+ await checkUISchemaValid(searchConfigSchema, uiSchema);
+ }
+);
+
+add_task(
+ { skip_if: () => !SearchUtils.newSearchConfigEnabled },
+ async function test_search_config_override_validates_to_schema() {
+ let selector = new SearchEngineSelector(() => {});
+ let searchConfigOverrides =
+ await selector.getEngineConfigurationOverrides();
+ let overrideSchema = await IOUtils.readJSON(
+ PathUtils.join(
+ do_get_cwd().path,
+ "search-config-overrides-v2-schema.json"
+ )
+ );
+
+ await checkSearchConfigOverrideValidates(
+ overrideSchema,
+ searchConfigOverrides
+ );
+ }
+);