tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

test_search_telemetry_config_validation.js (5025B)


      1 /* Any copyright is dedicated to the Public Domain.
      2   http://creativecommons.org/publicdomain/zero/1.0/ */
      3 
      4 "use strict";
      5 
      6 ChromeUtils.defineESModuleGetters(this, {
      7  AppConstants: "resource://gre/modules/AppConstants.sys.mjs",
      8  RemoteSettings: "resource://services-settings/remote-settings.sys.mjs",
      9  TELEMETRY_SETTINGS_KEY:
     10    "moz-src:///browser/components/search/SearchSERPTelemetry.sys.mjs",
     11  JsonSchema: "resource://gre/modules/JsonSchema.sys.mjs",
     12  SearchEngineSelector:
     13    "moz-src:///toolkit/components/search/SearchEngineSelector.sys.mjs",
     14 });
     15 
     16 /**
     17 * Checks to see if a value is an object or not.
     18 *
     19 * @param {*} value
     20 *   The value to check.
     21 * @returns {boolean}
     22 */
     23 function isObject(value) {
     24  return value != null && typeof value == "object" && !Array.isArray(value);
     25 }
     26 
     27 /**
     28 * This function modifies the schema to prevent allowing additional properties
     29 * on objects. This is used to enforce that the schema contains everything that
     30 * we deliver via the search configuration.
     31 *
     32 * These checks are not enabled in-product, as we want to allow older versions
     33 * to keep working if we add new properties for whatever reason.
     34 *
     35 * @param {object} section
     36 *   The section to check to see if an additionalProperties flag should be added.
     37 */
     38 function disallowAdditionalProperties(section) {
     39  // It is generally acceptable for new properties to be added to the
     40  // configuration as older builds will ignore them.
     41  //
     42  // As a result, we only check for new properties on nightly builds, and this
     43  // avoids us having to uplift schema changes. This also helps preserve the
     44  // schemas as documentation of "what was supported in this version".
     45  if (!AppConstants.NIGHTLY_BUILD) {
     46    info("Skipping additional properties validation.");
     47    return;
     48  }
     49 
     50  if (section.type == "object" && (!"additionalProperties") in section) {
     51    section.additionalProperties = false;
     52  }
     53  for (let value of Object.values(section)) {
     54    if (isObject(value)) {
     55      disallowAdditionalProperties(value);
     56    }
     57  }
     58 }
     59 
     60 add_task(async function test_search_telemetry_validates_to_schema() {
     61  let schema = await IOUtils.readJSON(
     62    PathUtils.join(do_get_cwd().path, "search-telemetry-v2-schema.json")
     63  );
     64  disallowAdditionalProperties(schema);
     65 
     66  let data = await RemoteSettings(TELEMETRY_SETTINGS_KEY).get();
     67 
     68  let validator = new JsonSchema.Validator(schema);
     69 
     70  for (let entry of data) {
     71    // Records in Remote Settings contain additional properties independent of
     72    // the schema. Hence, we don't want to validate their presence.
     73    delete entry.schema;
     74    delete entry.id;
     75    delete entry.last_modified;
     76    delete entry.filter_expression;
     77 
     78    let result = validator.validate(entry);
     79    let message = `Should validate ${entry.telemetryId}`;
     80    if (!result.valid) {
     81      message += `:\n${JSON.stringify(result.errors, null, 2)}`;
     82    }
     83    Assert.ok(result.valid, message);
     84  }
     85 });
     86 
     87 add_task(async function test_search_config_codes_in_search_telemetry() {
     88  let searchTelemetry = await RemoteSettings(TELEMETRY_SETTINGS_KEY).get();
     89 
     90  let selector = new SearchEngineSelector(() => {});
     91  let searchConfig = await selector.getEngineConfiguration();
     92 
     93  const telemetryIdToSearchEngineIdMap = new Map([["duckduckgo", "ddg"]]);
     94 
     95  for (let telemetryEntry of searchTelemetry) {
     96    info(`Checking: ${telemetryEntry.telemetryId}`);
     97    let engine;
     98    for (let entry of searchConfig) {
     99      if (entry.recordType != "engine") {
    100        continue;
    101      }
    102      if (
    103        entry.identifier == telemetryEntry.telemetryId ||
    104        entry.identifier ==
    105          telemetryIdToSearchEngineIdMap.get(telemetryEntry.telemetryId)
    106      ) {
    107        engine = entry;
    108      }
    109    }
    110    Assert.ok(
    111      !!engine,
    112      `Should have associated engine data for telemetry id ${telemetryEntry.telemetryId}`
    113    );
    114 
    115    if (engine.base.partnerCode) {
    116      Assert.ok(
    117        telemetryEntry.taggedCodes.includes(engine.base.partnerCode),
    118        `Should have the base partner code ${engine.base.partnerCode} listed in the search telemetry 'taggedCodes'`
    119      );
    120    } else {
    121      Assert.ok(
    122        ["google", "baidu"].includes(telemetryEntry.telemetryId),
    123        "Should only not have a base partner code for Google and Baidu"
    124      );
    125    }
    126 
    127    if (engine.variants) {
    128      for (let variant of engine.variants) {
    129        if ("partnerCode" in variant) {
    130          Assert.ok(
    131            telemetryEntry.taggedCodes.includes(variant.partnerCode),
    132            `Should have the partner code ${variant.partnerCode} listed in the search telemetry 'taggedCodes'`
    133          );
    134        }
    135 
    136        if (variant.subVariants) {
    137          for (let subVariant of variant.subVariants) {
    138            if ("partnerCode" in subVariant) {
    139              Assert.ok(
    140                telemetryEntry.taggedCodes.includes(subVariant.partnerCode),
    141                `Should have the partner code ${subVariant.partnerCode} listed in the search telemetry 'taggedCodes'`
    142              );
    143            }
    144          }
    145        }
    146      }
    147    }
    148  }
    149 });