test_NewTabGleanUtils.js (11623B)
1 /* Any copyright is dedicated to the Public Domain. 2 http://creativecommons.org/publicdomain/zero/1.0/ */ 3 4 "use strict"; 5 6 /** 7 * This test file contains tests for NewTabGleanUtils functionality. 8 * It tests the registration of metrics and pings for Glean telemetry in the newtab context. 9 */ 10 11 ChromeUtils.defineESModuleGetters(this, { 12 NewTabGleanUtils: "resource://newtab/lib/NewTabGleanUtils.sys.mjs", 13 sinon: "resource://testing-common/Sinon.sys.mjs", 14 }); 15 16 const TEST_RESOURCE_URI = "resource://test.json"; 17 18 function test_setup() { 19 const sandbox = sinon.createSandbox(); 20 // bug 1954203 - use a builtin ping name to avoid bug with testResetFOG 21 Services.fog.testResetFOG(); 22 23 sandbox.stub(NewTabGleanUtils, "readJSON").returns("{}"); 24 return sandbox; 25 } 26 27 /** 28 * Test case: Empty metrics/pings file 29 * Verifies that no registration occurs when the metrics file is empty 30 */ 31 add_task(async function test_registerMetricsAndPings_emptyFile() { 32 const sandbox = test_setup(); 33 // Test with empty data 34 let result = 35 await NewTabGleanUtils.registerMetricsAndPings(TEST_RESOURCE_URI); 36 Assert.ok(!result, "No metrics or ping registration for empty file"); 37 38 sandbox.restore(); 39 }); 40 41 /** 42 * Test case: Invalid metrics/pings format 43 * Verifies that registration fails when the metrics file contains invalid format 44 * with missing required metric and pings properties 45 */ 46 add_task(async function test_registerMetricsAndPings_invalidFormat() { 47 const sandbox = test_setup(); 48 49 // Test with invalid metrics and pings format 50 NewTabGleanUtils.readJSON.returns({ 51 pings: { 52 ping1: { include_client_id: true }, 53 ping2: { include_client_id: false }, 54 }, 55 metrics: { 56 category: { 57 metric1: { type: "counter" }, 58 metric2: { type: "string" }, 59 }, 60 }, 61 }); 62 63 let result = 64 await NewTabGleanUtils.registerMetricsAndPings(TEST_RESOURCE_URI); 65 Assert.ok(!result, "Registration failure for invalid metric and ping format"); 66 67 sandbox.restore(); 68 }); 69 70 /** 71 * Test case: Valid metrics/pings format 72 * Verifies successful registration with properly formatted metrics and pings 73 * Tests with all required fields and correct property names 74 */ 75 add_task(async function test_registerMetricsAndPings_validFormat() { 76 const sandbox = test_setup(); 77 78 // Test with valid metrics and pings format 79 NewTabGleanUtils.readJSON.returns({ 80 pings: { 81 newtab_ping: { 82 includeClientId: false, 83 sendIfEmpty: false, 84 preciseTimestamps: true, 85 includeInfoSections: true, 86 enabled: true, 87 schedulesPings: [], 88 reasonCodes: [], 89 followsCollectionEnabled: true, 90 uploaderCapabilities: [], 91 }, 92 }, 93 metrics: { 94 newtab_category: { 95 metric1: { 96 type: "text", 97 description: "test-description", 98 lifetime: "ping", 99 pings: ["newtab"], 100 disabled: false, 101 }, 102 }, 103 }, 104 }); 105 106 let result = 107 await NewTabGleanUtils.registerMetricsAndPings(TEST_RESOURCE_URI); 108 Assert.ok(result, "Registration success for valid metric and ping formats"); 109 110 sandbox.restore(); 111 }); 112 113 /** 114 * Test case: Handle ping configuration options name from runtime-metrics JSON 115 * Verifies successful registration of pings with ping config options (such as `include_client_id`) 116 * in snake case inside runtime-metrics JSON 117 */ 118 add_task(async function test_registerPings_validSnakeCaseFormat() { 119 const sandbox = test_setup(); 120 NewTabGleanUtils.readJSON.returns({ 121 pings: { 122 newtab_ping_1: { 123 include_client_id: false, 124 send_if_empty: false, 125 precise_timestamps: true, 126 include_info_sections: true, 127 enabled: true, 128 schedules_pings: [], 129 reason_codes: [], 130 follows_collection_enabled: true, 131 uploader_capabilities: [], 132 }, 133 }, 134 metrics: { 135 newtab_category_1: { 136 metric1: { 137 type: "text", 138 description: "test-description", 139 lifetime: "ping", 140 pings: ["newtab"], 141 disabled: false, 142 }, 143 }, 144 }, 145 }); 146 147 let result = 148 await NewTabGleanUtils.registerMetricsAndPings(TEST_RESOURCE_URI); 149 Assert.ok(result, "Registration success for valid metric and ping formats"); 150 151 sandbox.restore(); 152 }); 153 154 /** 155 * Test case: Optional metric description 156 * Verifies that metric registration succeeds even when description is missing 157 * Tests that description is not a required field for metric registration 158 */ 159 add_task( 160 async function test_registerMetricsAndPings_metricDescriptionOptional() { 161 const sandbox = test_setup(); 162 NewTabGleanUtils.readJSON.returns({ 163 metrics: { 164 newtab: { 165 metric2: { 166 type: "text", 167 lifetime: "ping", 168 pings: ["newtab"], 169 disabled: false, 170 }, 171 }, 172 }, 173 }); 174 175 let result = 176 await NewTabGleanUtils.registerMetricsAndPings(TEST_RESOURCE_URI); 177 Assert.ok( 178 result, 179 `Registration success for metric2 with missing description. 180 Description property not required in JSON for metric registration` 181 ); 182 183 sandbox.restore(); 184 } 185 ); 186 187 /** 188 * Test case: Metric registration telemetry 189 * Verifies that telemetry is properly recorded for both successful and failed metric registrations 190 * Tests with valid and invalid metric options 191 */ 192 add_task(async function test_registerMetricIfNeeded_telemetrySent() { 193 const validOptions = { 194 name: "metric1", 195 type: "text", 196 category: "test", 197 pings: ["newtab"], 198 lifetime: "ping", 199 disabled: false, 200 }; 201 202 NewTabGleanUtils.registerMetricIfNeeded(validOptions); 203 204 // Check instrumented telemetry records success 205 Assert.ok( 206 Glean.newtab.metricRegistered.metric1.testGetValue(), 207 "Glean metricRegistered telemetry sent with value as true" 208 ); 209 210 const invalidOptions = { 211 name: "metric2", 212 type: "text", 213 category: "test", 214 pings: ["newtab"], 215 disabled: false, 216 }; 217 218 // Throws when required lifetime property missing 219 Assert.throws( 220 () => NewTabGleanUtils.registerMetricIfNeeded(invalidOptions), 221 /Failure while registering metrics metric2/, 222 "Throws when metric registration fails due to missing lifetime param" 223 ); 224 225 // Check instrumented telemetry records failure 226 Assert.strictEqual( 227 Glean.newtab.metricRegistered.metric2.testGetValue(), 228 false, 229 "Glean metricRegistered telemetry sent with value as false" 230 ); 231 }); 232 233 /** 234 * Test case: Ping registration telemetry 235 * Verifies that telemetry is properly recorded for both successful and failed ping registrations 236 * Tests with valid and invalid ping options 237 */ 238 add_task(async function test_registerPingIfNeeded_telemetrySent() { 239 const validOptions = { 240 name: "ping1", 241 includeClientId: false, 242 sendIfEmpty: false, 243 preciseTimestamps: true, 244 includeInfoSections: true, 245 enabled: true, 246 schedulesPings: [], 247 reasonCodes: [], 248 followsCollectionEnabled: true, 249 uploaderCapabilities: [], 250 }; 251 252 NewTabGleanUtils.registerPingIfNeeded(validOptions); 253 // Check instrumented telemetry records success for registered ping 254 Assert.ok( 255 Glean.newtab.pingRegistered.ping1.testGetValue(), 256 "Glean pingRegistered telemetry sent with value as true for ping1" 257 ); 258 259 const invalidOptions = { 260 name: "ping2", 261 includeClientId: false, 262 sendIfEmpty: false, 263 preciseTimestamps: true, 264 includeInfoSections: true, 265 enabled: true, 266 schedulesPings: [], 267 reasonCodes: [], 268 followsCollectionEnabled: true, 269 }; 270 271 // And test methods throw appropriately 272 Assert.throws( 273 () => NewTabGleanUtils.registerPingIfNeeded(invalidOptions), 274 /Failure while registering ping ping2/, 275 "Throws when ping registration fails due to missing uploaderCapabilities param" 276 ); 277 278 // Check instrumented telemetry records failure 279 Assert.strictEqual( 280 Glean.newtab.pingRegistered.ping2.testGetValue(), 281 false, 282 "Glean pingRegistered telemetry sent with value as false for ping2" 283 ); 284 }); 285 286 /** 287 * Test case: Event metric registration 288 * Verifies proper registration and recording of event metrics 289 * Tests both basic event metrics and those with extra arguments 290 */ 291 add_task(async function test_registerMetricIfNeeded_eventMetrics() { 292 const options = { 293 name: "event1", 294 category: "test_category", 295 type: "event", 296 pings: ["events"], 297 lifetime: "ping", 298 disabled: false, 299 }; 300 301 NewTabGleanUtils.registerMetricIfNeeded(options); 302 303 // Check instrumented telemetry records success 304 Assert.ok( 305 Glean.newtab.metricRegistered.event1.testGetValue(), 306 "Glean metricRegistered telemetry sent with value as true" 307 ); 308 309 const optionsWithExtra = { 310 name: "event2", 311 category: "test_category", 312 type: "event", 313 pings: ["events"], 314 lifetime: "ping", 315 disabled: false, 316 extraArgs: { 317 allowed_extra_keys: ["extra1", "extra2"], 318 }, 319 }; 320 321 NewTabGleanUtils.registerMetricIfNeeded(optionsWithExtra); 322 323 // Check instrumented telemetry records success 324 Assert.ok( 325 Glean.newtab.metricRegistered.event2.testGetValue(), 326 "Glean metricRegistered telemetry sent with value as true" 327 ); 328 329 let extra = { extra1: "extra1 value", extra2: "extra2 value" }; 330 Glean.testCategory.event2.record(extra); 331 332 let events = Glean.testCategory.event2.testGetValue(); 333 Assert.equal(1, events.length, "Events recorded count"); 334 }); 335 336 /** 337 * Test case: Non-event metric registration 338 * Verifies that non-event metrics cannot use the record method 339 * Tests that appropriate errors are thrown when trying to record non-event metrics 340 */ 341 add_task(async function test_registerMetricIfNeeded_nonEventMetrics() { 342 const options = { 343 name: "event3", 344 type: "text", 345 category: "test_category1", 346 pings: ["events"], 347 lifetime: "ping", 348 disabled: false, 349 extraArgs: { 350 allowed_extra_keys: ["extra1", "extra2"], 351 }, 352 }; 353 354 NewTabGleanUtils.registerMetricIfNeeded(options); 355 356 // Check instrumented telemetry records success 357 Assert.ok( 358 Glean.newtab.metricRegistered.event3.testGetValue(), 359 "Glean metricRegistered telemetry sent with value as true" 360 ); 361 362 let extra = { extra1: "extra1 value", extra2: "extra2 value" }; 363 364 // And test methods throw appropriately 365 Assert.throws( 366 () => Glean.testCategory1.event3.record(extra), 367 /TypeError/, 368 "Throws when using record for non event type" 369 ); 370 371 Glean.testCategory1.event3.set("Test"); 372 373 Assert.equal( 374 "Test", 375 Glean.testCategory1.event3.testGetValue(), 376 "Success when using set to send data of type text" 377 ); 378 }); 379 380 /** 381 * Test case: Memory distribution metric registration 382 * Verifies proper registration and recording of memory_distribution metrics 383 * Ensures that extraArgs (memory_unit) are honored 384 */ 385 add_task( 386 async function test_registerMetricIfNeeded_memoryDistributionMetrics() { 387 const optionsWithExtra = { 388 name: "memdist1", 389 category: "test_category", 390 type: "memory_distribution", 391 pings: ["metrics"], 392 lifetime: "ping", 393 disabled: false, 394 extraArgs: { 395 memory_unit: "megabyte", 396 }, 397 }; 398 399 NewTabGleanUtils.registerMetricIfNeeded(optionsWithExtra); 400 401 Assert.ok( 402 Glean.newtab.metricRegistered.memdist1.testGetValue(), 403 "Glean metricRegistered telemetry sent with value as true" 404 ); 405 406 Glean.testCategory.memdist1.accumulate(7); 407 Glean.testCategory.memdist1.accumulate(17); 408 409 let data = Glean.testCategory.memdist1.testGetValue(); 410 Assert.equal(2, data.count, "Recorded sample count"); 411 Assert.equal(24 * 1024 * 1024, data.sum, "Sum is in bytes for MB unit"); 412 } 413 );