tor-browser

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

test_NewTabContentPing.js (11100B)


      1 /* Any copyright is dedicated to the Public Domain.
      2   https://creativecommons.org/publicdomain/zero/1.0/ */
      3 
      4 "use strict";
      5 
      6 ChromeUtils.defineESModuleGetters(this, {
      7  NewTabContentPing: "resource://newtab/lib/NewTabContentPing.sys.mjs",
      8  sinon: "resource://testing-common/Sinon.sys.mjs",
      9 });
     10 
     11 const MAX_SUBMISSION_DELAY = Services.prefs.getIntPref(
     12  "browser.newtabpage.activity-stream.telemetry.privatePing.maxSubmissionDelayMs",
     13  5000
     14 );
     15 
     16 add_setup(() => {
     17  do_get_profile();
     18  Services.fog.initializeFOG();
     19 });
     20 
     21 /**
     22 * Tests that the recordEvent method will cause a delayed ping submission to be
     23 * scheduled, and that the right extra fields are stripped from events.
     24 */
     25 add_task(async function test_recordEvent_sanitizes_and_buffers() {
     26  let ping = new NewTabContentPing();
     27  ping.test_only_resetAllStats();
     28 
     29  // These fields are expected to be stripped before they get recorded in the
     30  // event.
     31  let sanitizedFields = {
     32    newtab_visit_id: "some visit id",
     33    tile_id: "some tile id",
     34    matches_selected_topic: "not-set",
     35    recommended_at: "1748877997039",
     36    received_rank: 0,
     37    event_source: "card",
     38    layout_name: "card-layout",
     39  };
     40 
     41  // These fields are expected to survive the sanitization.
     42  let expectedFields = {
     43    section: "business",
     44    section_position: "2",
     45    position: "12",
     46    selected_topics: "",
     47    corpus_item_id: "7fc404a1-74ec-450b-8eef-4f52b45ec510",
     48    topic: "business",
     49    format: "medium-card",
     50    scheduled_corpus_item_id: "40f9ba69-1288-4778-8cfa-937df633819c",
     51    is_sponsored: "false",
     52    is_section_followed: "false",
     53  };
     54 
     55  ping.recordEvent("click", {
     56    // These should be sanitized out.
     57    ...sanitizedFields,
     58    ...expectedFields,
     59  });
     60 
     61  let extraMetrics = {
     62    utcOffset: "1",
     63    experimentBranch: "some-branch",
     64  };
     65  ping.scheduleSubmission(extraMetrics);
     66 
     67  await GleanPings.newtabContent.testSubmission(
     68    () => {
     69      // Test callback
     70      let [clickEvent] = Glean.newtabContent.click.testGetValue();
     71      Assert.ok(clickEvent, "Found click event.");
     72      for (let fieldName of Object.keys(sanitizedFields)) {
     73        Assert.equal(
     74          clickEvent.extra[fieldName],
     75          undefined,
     76          `Should not have gotten sanitized extra field: ${fieldName}`
     77        );
     78      }
     79      for (let fieldName of Object.keys(expectedFields)) {
     80        Assert.equal(
     81          clickEvent.extra[fieldName],
     82          expectedFields[fieldName],
     83          `Should have recorded expected extra field: ${fieldName}`
     84        );
     85      }
     86 
     87      for (let metricName of Object.keys(extraMetrics)) {
     88        Assert.equal(
     89          Glean.newtabContent[metricName].testGetValue(),
     90          extraMetrics[metricName],
     91          `Should have recorded metric: ${metricName}`
     92        );
     93      }
     94    },
     95    async () => {
     96      // Submit Callback
     97      let delay = await ping.testOnlyForceFlush();
     98      Assert.greater(delay, 1000, "Picked a random value greater than 1000");
     99      Assert.less(
    100        delay,
    101        MAX_SUBMISSION_DELAY,
    102        "Picked a random value less than MAX_SUBMISSION_DELAY"
    103      );
    104    }
    105  );
    106 });
    107 
    108 /**
    109 * Tests that the recordEvent caps the maximum number of events
    110 */
    111 add_task(async function test_recordEventDaily_caps_events() {
    112  const MAX_EVENTS = 2;
    113 
    114  let ping = new NewTabContentPing();
    115  ping.setMaxEventsPerDay(MAX_EVENTS);
    116  ping.test_only_resetAllStats();
    117 
    118  // These fields are expected to survive the sanitization.
    119  let expectedFields = {
    120    section: "business",
    121    corpus_item_id: "7fc404a1-74ec-450b-8eef-4f52b45ec510",
    122    topic: "business",
    123  };
    124 
    125  ping.recordEvent("click", {
    126    ...expectedFields,
    127  });
    128 
    129  ping.recordEvent("impression", {
    130    ...expectedFields,
    131  });
    132 
    133  let extraMetrics = {
    134    utcOffset: "1",
    135    experimentBranch: "some-branch",
    136  };
    137  ping.scheduleSubmission(extraMetrics);
    138 
    139  await GleanPings.newtabContent.testSubmission(
    140    () => {
    141      // Test Callback
    142      let [clickEvent] = Glean.newtabContent.click.testGetValue();
    143      Assert.ok(clickEvent, "Found click event.");
    144      let [impression] = Glean.newtabContent.impression.testGetValue();
    145      Assert.ok(impression, "Found impression event.");
    146    },
    147    async () => {
    148      // Submit Callback
    149      await ping.testOnlyForceFlush();
    150    }
    151  );
    152 
    153  Assert.equal(
    154    ping.testOnlyCurInstanceEventCount,
    155    2,
    156    "Expected number of events sent"
    157  );
    158 
    159  ping.recordEvent("section_impression", {
    160    ...expectedFields,
    161  });
    162  ping.scheduleSubmission(extraMetrics);
    163  await ping.testOnlyForceFlush();
    164 
    165  Assert.equal(ping.testOnlyCurInstanceEventCount, 2, "No new events sent");
    166 
    167  ping = new NewTabContentPing();
    168  ping.setMaxEventsPerDay(MAX_EVENTS);
    169 
    170  Assert.equal(ping.testOnlyCurInstanceEventCount, 0, "Event count reset");
    171 
    172  ping.recordEvent("section_impression", {
    173    ...expectedFields,
    174  });
    175  ping.scheduleSubmission(extraMetrics);
    176  await ping.testOnlyForceFlush();
    177 
    178  Assert.equal(
    179    ping.testOnlyCurInstanceEventCount,
    180    0,
    181    "No new events after re-creating NewTabContentPing class"
    182  );
    183 
    184  // Some time has passed
    185  let sandbox = sinon.createSandbox();
    186 
    187  sandbox.stub(NewTabContentPing.prototype, "Date").returns({
    188    now: () => Date.now() + 3600 * 25 * 1000, // 25 hours in future
    189  });
    190 
    191  ping.scheduleSubmission(extraMetrics);
    192 
    193  ping.recordEvent("click", {
    194    ...expectedFields,
    195  });
    196 
    197  await GleanPings.newtabContent.testSubmission(
    198    () => {
    199      // Test Callback
    200      let [click] = Glean.newtabContent.click.testGetValue();
    201      Assert.ok(click, "Found click event.");
    202    },
    203    async () => {
    204      // Submit Callback
    205      await ping.testOnlyForceFlush();
    206    }
    207  );
    208  Assert.equal(ping.testOnlyCurInstanceEventCount, 1, "Event sending restored");
    209  sandbox.restore();
    210 });
    211 
    212 /**
    213 * Tests that the recordEvent caps the maximum number of click events
    214 */
    215 add_task(async function test_recordEventDaily_caps_click_events() {
    216  const MAX_DAILY_CLICK_EVENTS = 2;
    217  const MAX_WEEKLY_CLICK_EVENTS = 10;
    218 
    219  let ping = new NewTabContentPing();
    220  ping.setMaxClickEventsPerDay(MAX_DAILY_CLICK_EVENTS);
    221  ping.setMaxClickEventsPerWeek(MAX_WEEKLY_CLICK_EVENTS);
    222  ping.test_only_resetAllStats();
    223 
    224  // These fields are expected to survive the sanitization.
    225  let expectedFields = {
    226    section: "business",
    227    corpus_item_id: "7fc404a1-74ec-450b-8eef-4f52b45ec510",
    228    topic: "business",
    229  };
    230 
    231  for (let i = 0; i < MAX_DAILY_CLICK_EVENTS * 2; i++) {
    232    ping.recordEvent("impression", {
    233      ...expectedFields,
    234    });
    235  }
    236  for (let i = 0; i < MAX_DAILY_CLICK_EVENTS; i++) {
    237    ping.recordEvent("click", {
    238      ...expectedFields,
    239    });
    240  }
    241 
    242  let extraMetrics = {
    243    utcOffset: "1",
    244    experimentBranch: "some-branch",
    245  };
    246 
    247  ping.scheduleSubmission(extraMetrics);
    248 
    249  await GleanPings.newtabContent.testSubmission(
    250    () => {
    251      // Test Callback
    252      let [clickEvent] = Glean.newtabContent.click.testGetValue();
    253      Assert.ok(clickEvent, "Found click event.");
    254      let [impression] = Glean.newtabContent.impression.testGetValue();
    255      Assert.ok(impression, "Found impression event.");
    256    },
    257    async () => {
    258      // Submit Callback
    259      await ping.testOnlyForceFlush();
    260    }
    261  );
    262 
    263  const curTotalEvents = MAX_DAILY_CLICK_EVENTS * 3;
    264  Assert.equal(
    265    ping.testOnlyCurInstanceEventCount,
    266    curTotalEvents,
    267    "Expected number of events sent"
    268  );
    269 
    270  ping.recordEvent("click", {
    271    ...expectedFields,
    272  });
    273  ping.scheduleSubmission(extraMetrics);
    274  await ping.testOnlyForceFlush();
    275 
    276  Assert.equal(
    277    ping.testOnlyCurInstanceEventCount,
    278    curTotalEvents,
    279    "Click cap enforced, no new events sent"
    280  );
    281 
    282  ping = new NewTabContentPing();
    283  ping.setMaxClickEventsPerDay(MAX_DAILY_CLICK_EVENTS);
    284 
    285  Assert.equal(ping.testOnlyCurInstanceEventCount, 0, "Event count reset");
    286 
    287  ping.recordEvent("click", {
    288    ...expectedFields,
    289  });
    290  ping.scheduleSubmission(extraMetrics);
    291  await ping.testOnlyForceFlush();
    292 
    293  Assert.equal(
    294    ping.testOnlyCurInstanceEventCount,
    295    0,
    296    "No new click events after re-creating NewTabContentPing class"
    297  );
    298 
    299  // Some time has passed
    300  let sandbox = sinon.createSandbox();
    301 
    302  sandbox.stub(NewTabContentPing.prototype, "Date").returns({
    303    now: () => Date.now() + 3600 * 25 * 1000, // 25 hours in future
    304  });
    305 
    306  ping.scheduleSubmission(extraMetrics);
    307 
    308  ping.recordEvent("click", {
    309    ...expectedFields,
    310  });
    311 
    312  await GleanPings.newtabContent.testSubmission(
    313    () => {
    314      // Test Callback
    315      let [click] = Glean.newtabContent.click.testGetValue();
    316      Assert.ok(click, "Found click event.");
    317    },
    318    async () => {
    319      // Submit Callback
    320      await ping.testOnlyForceFlush();
    321    }
    322  );
    323  Assert.equal(ping.testOnlyCurInstanceEventCount, 1, "Event sending restored");
    324 
    325  let cur_events_sent = ping.testOnlyCurInstanceEventCount;
    326  for (let i = 0; i < MAX_WEEKLY_CLICK_EVENTS; i++) {
    327    ping.recordEvent("click", {
    328      ...expectedFields,
    329    });
    330  }
    331  ping.scheduleSubmission(extraMetrics);
    332  await ping.testOnlyForceFlush();
    333  // Assert that less than MAX_WEEKLY_CLICK_EVENTS events were sent using a difference check (less) to avoid timing issues
    334  Assert.less(
    335    ping.testOnlyCurInstanceEventCount - cur_events_sent,
    336    MAX_WEEKLY_CLICK_EVENTS,
    337    "Weekly click cap enforced, no new events sent"
    338  );
    339  // Assert that at least 1 item was sent before we reached the cap
    340  Assert.greater(
    341    ping.testOnlyCurInstanceEventCount - cur_events_sent,
    342    0,
    343    "At least one event was sent before reaching the weekly cap"
    344  );
    345 
    346  sandbox.restore();
    347 
    348  // Some more time has passed - weekly reset
    349  sandbox = sinon.createSandbox();
    350  sandbox.stub(NewTabContentPing.prototype, "Date").returns({
    351    now: () => Date.now() + 3600 * 24 * 1000 * 8, // 8 days in the future
    352  });
    353 
    354  cur_events_sent = ping.testOnlyCurInstanceEventCount;
    355  for (let i = 0; i < MAX_WEEKLY_CLICK_EVENTS; i++) {
    356    ping.recordEvent("click", {
    357      ...expectedFields,
    358    });
    359  }
    360  ping.scheduleSubmission(extraMetrics);
    361  await ping.testOnlyForceFlush();
    362  Assert.equal(
    363    ping.testOnlyCurInstanceEventCount - cur_events_sent,
    364    MAX_DAILY_CLICK_EVENTS, // Only daily cap should apply after weekly reset
    365    "Event sending restored after weekly reset"
    366  );
    367 
    368  sandbox.restore();
    369 });
    370 
    371 add_task(function test_decideWithProbability() {
    372  Assert.equal(NewTabContentPing.decideWithProbability(-0.1), false);
    373  Assert.equal(NewTabContentPing.decideWithProbability(1.1), true);
    374 });
    375 
    376 add_task(function test_shuffleArray() {
    377  const shuffled = NewTabContentPing.shuffleArray([1, 3, 5]);
    378  Assert.equal(shuffled.length, 3);
    379  Assert.ok(shuffled.includes(3), "Shuffled item in array");
    380  Assert.ok(shuffled.includes(1), "Shuffled item in array");
    381  Assert.ok(shuffled.includes(5), "Shuffled item in array");
    382  Assert.equal(NewTabContentPing.shuffleArray([]).length, 0);
    383 });
    384 
    385 add_task(async function test_secureRandIntInRange() {
    386  for (let k = 0; k < 10; k++) {
    387    Assert.greater(
    388      10,
    389      NewTabContentPing.secureRandIntInRange(10),
    390      "Random value in range"
    391    );
    392    Assert.less(
    393      -1,
    394      NewTabContentPing.secureRandIntInRange(10),
    395      "Random value in range"
    396    );
    397  }
    398 });