diff options
Diffstat (limited to 'browser/components/search/test/unit/test_search_telemetry_config_validation.js')
-rw-r--r-- | browser/components/search/test/unit/test_search_telemetry_config_validation.js | 137 |
1 files changed, 137 insertions, 0 deletions
diff --git a/browser/components/search/test/unit/test_search_telemetry_config_validation.js b/browser/components/search/test/unit/test_search_telemetry_config_validation.js new file mode 100644 index 0000000000..8897b1e7c7 --- /dev/null +++ b/browser/components/search/test/unit/test_search_telemetry_config_validation.js @@ -0,0 +1,137 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +ChromeUtils.defineESModuleGetters(this, { + AppConstants: "resource://gre/modules/AppConstants.sys.mjs", + RemoteSettings: "resource://services-settings/remote-settings.sys.mjs", + TELEMETRY_SETTINGS_KEY: "resource:///modules/SearchSERPTelemetry.sys.mjs", + JsonSchema: "resource://gre/modules/JsonSchema.sys.mjs", + SearchEngineSelector: "resource://gre/modules/SearchEngineSelector.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) { + info("Skipping additional properties validation."); + return; + } + + if (section.type == "object") { + section.additionalProperties = false; + } + for (let value of Object.values(section)) { + if (isObject(value)) { + disallowAdditionalProperties(value); + } + } +} + +add_task(async function test_search_telemetry_validates_to_schema() { + let schema = await IOUtils.readJSON( + PathUtils.join(do_get_cwd().path, "search-telemetry-schema.json") + ); + disallowAdditionalProperties(schema); + + let data = await RemoteSettings(TELEMETRY_SETTINGS_KEY).get(); + + let validator = new JsonSchema.Validator(schema); + + for (let entry of data) { + // 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; + delete entry.filter_expression; + + let result = validator.validate(entry); + let message = `Should validate ${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_codes_in_search_telemetry() { + let searchTelemetry = await RemoteSettings(TELEMETRY_SETTINGS_KEY).get(); + + let selector = new SearchEngineSelector(() => {}); + let searchConfig = await selector.getEngineConfiguration(); + + const telemetryIdToSearchEngineIdMap = new Map([["duckduckgo", "ddg"]]); + + for (let telemetryEntry of searchTelemetry) { + info(`Checking: ${telemetryEntry.telemetryId}`); + let engine; + for (let entry of searchConfig) { + if (entry.recordType != "engine") { + continue; + } + if ( + entry.identifier == telemetryEntry.telemetryId || + entry.identifier == + telemetryIdToSearchEngineIdMap.get(telemetryEntry.telemetryId) + ) { + engine = entry; + } + } + Assert.ok( + !!engine, + `Should have associated engine data for telemetry id ${telemetryEntry.telemetryId}` + ); + + if (engine.base.partnerCode) { + Assert.ok( + telemetryEntry.taggedCodes.includes(engine.base.partnerCode), + `Should have the base partner code ${engine.base.partnerCode} listed in the search telemetry 'taggedCodes'` + ); + } else { + Assert.equal( + telemetryEntry.telemetryId, + "baidu", + "Should only not have a base partner code for Baidu" + ); + } + + if (engine.variants) { + for (let variant of engine.variants) { + if ("partnerCode" in variant) { + Assert.ok( + telemetryEntry.taggedCodes.includes(variant.partnerCode), + `Should have the partner code ${variant.partnerCode} listed in the search telemetry 'taggedCodes'` + ); + } + } + } + } +}); |