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 });