test_ASRouterTelemetry.js (20017B)
1 /* Any copyright is dedicated to the Public Domain. 2 http://creativecommons.org/publicdomain/zero/1.0/ */ 3 4 "use strict"; 5 6 const { MESSAGE_TYPE_HASH: msg } = ChromeUtils.importESModule( 7 "resource:///modules/asrouter/ActorConstants.mjs" 8 ); 9 10 const { ASRouterTelemetry } = ChromeUtils.importESModule( 11 "resource:///modules/asrouter/ASRouterTelemetry.sys.mjs" 12 ); 13 14 ChromeUtils.defineESModuleGetters(this, { 15 AboutWelcomeTelemetry: 16 "resource:///modules/aboutwelcome/AboutWelcomeTelemetry.sys.mjs", 17 NimbusFeatures: "resource://nimbus/ExperimentAPI.sys.mjs", 18 JsonSchemaValidator: 19 "resource://gre/modules/components-utils/JsonSchemaValidator.sys.mjs", 20 TelemetryController: "resource://gre/modules/TelemetryController.sys.mjs", 21 UpdateUtils: "resource://gre/modules/UpdateUtils.sys.mjs", 22 }); 23 24 const FAKE_UUID = "{foo-123-foo}"; 25 const PREF_TELEMETRY = "browser.newtabpage.activity-stream.telemetry"; 26 27 let ASRouterEventPingSchemaPromise; 28 29 function assertPingMatchesSchema(pingKind, ping, schema) { 30 // Unlike the validator from JsonSchema.sys.mjs, JsonSchemaValidator 31 // lets us opt-in to having "undefined" properties, which are then 32 // ignored. This is fine because the ping is sent as a JSON string 33 // over an XHR, and undefined properties are culled as part of the 34 // JSON encoding process. 35 let result = JsonSchemaValidator.validate(ping, schema, { 36 allowExplicitUndefinedProperties: true, 37 }); 38 39 if (!result.valid) { 40 info(`${pingKind} failed to validate against the schema: ${result.error}`); 41 } 42 43 Assert.ok(result.valid, `${pingKind} is valid against the schema.`); 44 } 45 46 async function assertASRouterEventPingValid(ping) { 47 let schema = await ASRouterEventPingSchemaPromise; 48 assertPingMatchesSchema("ASRouterEventPing", ping, schema); 49 } 50 51 add_setup(async function setup() { 52 ASRouterEventPingSchemaPromise = IOUtils.readJSON( 53 do_get_file("../schemas/asrouter_event_ping.schema.json").path 54 ); 55 56 do_get_profile(); 57 58 await TelemetryController.testReset(); 59 }); 60 61 add_task(async function test_applyCFRPolicy_prerelease() { 62 info( 63 "ASRouterTelemetry.applyCFRPolicy should use client_id and message_id " + 64 "in prerelease" 65 ); 66 let sandbox = sinon.createSandbox(); 67 sandbox.stub(UpdateUtils, "getUpdateChannel").returns("nightly"); 68 69 let instance = new ASRouterTelemetry(); 70 71 let data = { 72 action: "cfr_user_event", 73 event: "IMPRESSION", 74 message_id: "cfr_message_01", 75 bucket_id: "cfr_bucket_01", 76 }; 77 let { ping, pingType } = await instance.applyCFRPolicy(data); 78 79 Assert.equal(pingType, "cfr"); 80 Assert.equal(ping.impression_id, undefined); 81 Assert.equal( 82 ping.client_id, 83 Services.prefs.getCharPref("toolkit.telemetry.cachedClientID") 84 ); 85 Assert.equal(ping.bucket_id, "cfr_bucket_01"); 86 Assert.equal(ping.message_id, "cfr_message_01"); 87 88 sandbox.restore(); 89 }); 90 91 add_task(async function test_applyCFRPolicy_release() { 92 info( 93 "ASRouterTelemetry.applyCFRPolicy should use impression_id and bucket_id " + 94 "in release" 95 ); 96 let sandbox = sinon.createSandbox(); 97 sandbox.stub(UpdateUtils, "getUpdateChannel").returns("release"); 98 sandbox 99 .stub(ASRouterTelemetry.prototype, "getOrCreateImpressionId") 100 .returns(FAKE_UUID); 101 102 let instance = new ASRouterTelemetry(); 103 104 let data = { 105 action: "cfr_user_event", 106 event: "IMPRESSION", 107 message_id: "cfr_message_01", 108 bucket_id: "cfr_bucket_01", 109 }; 110 let { ping, pingType } = await instance.applyCFRPolicy(data); 111 112 Assert.equal(pingType, "cfr"); 113 Assert.equal(ping.impression_id, FAKE_UUID); 114 Assert.equal(ping.client_id, undefined); 115 Assert.equal(ping.bucket_id, "cfr_bucket_01"); 116 Assert.equal(ping.message_id, "n/a"); 117 118 sandbox.restore(); 119 }); 120 121 add_task(async function test_applyCFRPolicy_experiment_release() { 122 info( 123 "ASRouterTelemetry.applyCFRPolicy should use impression_id and bucket_id " + 124 "in release" 125 ); 126 let sandbox = sinon.createSandbox(); 127 sandbox.stub(UpdateUtils, "getUpdateChannel").returns("release"); 128 sandbox.stub(NimbusFeatures.cfr, "getEnrollmentMetadata").returns({ 129 slug: "SOME-CFR-EXP", 130 branch: "branch-slug", 131 isRollout: false, 132 }); 133 134 let instance = new ASRouterTelemetry(); 135 136 let data = { 137 action: "cfr_user_event", 138 event: "IMPRESSION", 139 message_id: "cfr_message_01", 140 bucket_id: "cfr_bucket_01", 141 }; 142 let { ping, pingType } = await instance.applyCFRPolicy(data); 143 144 Assert.equal(pingType, "cfr"); 145 Assert.equal(ping.impression_id, undefined); 146 Assert.equal( 147 ping.client_id, 148 Services.prefs.getCharPref("toolkit.telemetry.cachedClientID") 149 ); 150 Assert.equal(ping.bucket_id, "cfr_bucket_01"); 151 Assert.equal(ping.message_id, "cfr_message_01"); 152 153 sandbox.restore(); 154 }); 155 156 add_task(async function test_applyCFRPolicy_release_private_browsing() { 157 info( 158 "ASRouterTelemetry.applyCFRPolicy should use impression_id and bucket_id " + 159 "in Private Browsing in release" 160 ); 161 let sandbox = sinon.createSandbox(); 162 sandbox.stub(UpdateUtils, "getUpdateChannel").returns("release"); 163 sandbox 164 .stub(ASRouterTelemetry.prototype, "getOrCreateImpressionId") 165 .returns(FAKE_UUID); 166 167 let instance = new ASRouterTelemetry(); 168 169 let data = { 170 action: "cfr_user_event", 171 event: "IMPRESSION", 172 is_private: true, 173 message_id: "cfr_message_01", 174 bucket_id: "cfr_bucket_01", 175 }; 176 let { ping, pingType } = await instance.applyCFRPolicy(data); 177 178 Assert.equal(pingType, "cfr"); 179 Assert.equal(ping.impression_id, FAKE_UUID); 180 Assert.equal(ping.client_id, undefined); 181 Assert.equal(ping.bucket_id, "cfr_bucket_01"); 182 Assert.equal(ping.message_id, "n/a"); 183 184 sandbox.restore(); 185 }); 186 187 add_task( 188 async function test_applyCFRPolicy_release_experiment_private_browsing() { 189 info( 190 "ASRouterTelemetry.applyCFRPolicy should use client_id and message_id in the " + 191 "experiment cohort in Private Browsing in release" 192 ); 193 let sandbox = sinon.createSandbox(); 194 sandbox.stub(UpdateUtils, "getUpdateChannel").returns("release"); 195 sandbox.stub(NimbusFeatures.cfr, "getEnrollmentMetadata").returns({ 196 slug: "SOME-CFR-EXP", 197 branch: "branch-slug", 198 isRollout: false, 199 }); 200 201 let instance = new ASRouterTelemetry(); 202 203 let data = { 204 action: "cfr_user_event", 205 event: "IMPRESSION", 206 is_private: true, 207 message_id: "cfr_message_01", 208 bucket_id: "cfr_bucket_01", 209 }; 210 let { ping, pingType } = await instance.applyCFRPolicy(data); 211 212 Assert.equal(pingType, "cfr"); 213 Assert.equal(ping.impression_id, undefined); 214 Assert.equal( 215 ping.client_id, 216 Services.prefs.getCharPref("toolkit.telemetry.cachedClientID") 217 ); 218 Assert.equal(ping.bucket_id, "cfr_bucket_01"); 219 Assert.equal(ping.message_id, "cfr_message_01"); 220 221 sandbox.restore(); 222 } 223 ); 224 225 add_task(async function test_applyToolbarBadgePolicy() { 226 info( 227 "ASRouterTelemetry.applyToolbarBadgePolicy should set client_id and set pingType" 228 ); 229 let instance = new ASRouterTelemetry(); 230 let { ping, pingType } = await instance.applyToolbarBadgePolicy({}); 231 232 Assert.equal( 233 ping.client_id, 234 Services.prefs.getCharPref("toolkit.telemetry.cachedClientID") 235 ); 236 Assert.equal(pingType, "toolbar-badge"); 237 }); 238 239 add_task(async function test_applyInfoBarPolicy() { 240 info( 241 "ASRouterTelemetry.applyInfoBarPolicy should set client_id and set pingType" 242 ); 243 let instance = new ASRouterTelemetry(); 244 let { ping, pingType } = await instance.applyInfoBarPolicy({}); 245 246 Assert.equal( 247 ping.client_id, 248 Services.prefs.getCharPref("toolkit.telemetry.cachedClientID") 249 ); 250 Assert.equal(pingType, "infobar"); 251 }); 252 253 add_task(async function test_applyToastNotificationPolicy() { 254 info( 255 "ASRouterTelemetry.applyToastNotificationPolicy should set client_id " + 256 "and set pingType" 257 ); 258 let instance = new ASRouterTelemetry(); 259 let { ping, pingType } = await instance.applyToastNotificationPolicy({}); 260 261 Assert.equal( 262 ping.client_id, 263 Services.prefs.getCharPref("toolkit.telemetry.cachedClientID") 264 ); 265 Assert.equal(pingType, "toast_notification"); 266 }); 267 268 add_task(async function test_applySpotlightPolicy() { 269 info( 270 "ASRouterTelemetry.applySpotlightPolicy should set client_id " + 271 "and set pingType" 272 ); 273 let instance = new ASRouterTelemetry(); 274 let { ping, pingType } = await instance.applySpotlightPolicy({ 275 action: "foo", 276 }); 277 278 Assert.equal( 279 ping.client_id, 280 Services.prefs.getCharPref("toolkit.telemetry.cachedClientID") 281 ); 282 Assert.equal(pingType, "spotlight"); 283 Assert.equal(ping.action, undefined); 284 }); 285 286 add_task(async function test_applyMomentsPolicy_prerelease() { 287 info( 288 "ASRouterTelemetry.applyMomentsPolicy should use client_id and " + 289 "message_id in prerelease" 290 ); 291 let sandbox = sinon.createSandbox(); 292 sandbox.stub(UpdateUtils, "getUpdateChannel").returns("nightly"); 293 294 let instance = new ASRouterTelemetry(); 295 let data = { 296 action: "moments_user_event", 297 event: "IMPRESSION", 298 message_id: "moments_message_01", 299 bucket_id: "moments_bucket_01", 300 }; 301 let { ping, pingType } = await instance.applyMomentsPolicy(data); 302 303 Assert.equal(pingType, "moments"); 304 Assert.equal(ping.impression_id, undefined); 305 Assert.equal( 306 ping.client_id, 307 Services.prefs.getCharPref("toolkit.telemetry.cachedClientID") 308 ); 309 Assert.equal(ping.bucket_id, "moments_bucket_01"); 310 Assert.equal(ping.message_id, "moments_message_01"); 311 312 sandbox.restore(); 313 }); 314 315 add_task(async function test_applyMomentsPolicy_release() { 316 info( 317 "ASRouterTelemetry.applyMomentsPolicy should use impression_id and " + 318 "bucket_id in release" 319 ); 320 let sandbox = sinon.createSandbox(); 321 sandbox.stub(UpdateUtils, "getUpdateChannel").returns("release"); 322 sandbox 323 .stub(ASRouterTelemetry.prototype, "getOrCreateImpressionId") 324 .returns(FAKE_UUID); 325 326 let instance = new ASRouterTelemetry(); 327 let data = { 328 action: "moments_user_event", 329 event: "IMPRESSION", 330 message_id: "moments_message_01", 331 bucket_id: "moments_bucket_01", 332 }; 333 let { ping, pingType } = await instance.applyMomentsPolicy(data); 334 335 Assert.equal(pingType, "moments"); 336 Assert.equal(ping.impression_id, FAKE_UUID); 337 Assert.equal(ping.client_id, undefined); 338 Assert.equal(ping.bucket_id, "moments_bucket_01"); 339 Assert.equal(ping.message_id, "n/a"); 340 341 sandbox.restore(); 342 }); 343 344 add_task(async function test_applyMomentsPolicy_experiment_release() { 345 info( 346 "ASRouterTelemetry.applyMomentsPolicy client_id and message_id in " + 347 "the experiment cohort in release" 348 ); 349 let sandbox = sinon.createSandbox(); 350 sandbox.stub(UpdateUtils, "getUpdateChannel").returns("release"); 351 sandbox.stub(NimbusFeatures.cfr, "getEnrollmentMetadata").returns({ 352 slug: "SOME-CFR-EXP", 353 branch: "branch-slug", 354 isRollout: false, 355 }); 356 357 let instance = new ASRouterTelemetry(); 358 let data = { 359 action: "moments_user_event", 360 event: "IMPRESSION", 361 message_id: "moments_message_01", 362 bucket_id: "moments_bucket_01", 363 }; 364 let { ping, pingType } = await instance.applyMomentsPolicy(data); 365 366 Assert.equal(pingType, "moments"); 367 Assert.equal(ping.impression_id, undefined); 368 Assert.equal( 369 ping.client_id, 370 Services.prefs.getCharPref("toolkit.telemetry.cachedClientID") 371 ); 372 Assert.equal(ping.bucket_id, "moments_bucket_01"); 373 Assert.equal(ping.message_id, "moments_message_01"); 374 375 sandbox.restore(); 376 }); 377 378 add_task(async function test_applyMenuMessagePolicy() { 379 info( 380 "ASRouterTelemetry.applyMenuMessagePolicy should set client_id and set pingType" 381 ); 382 let instance = new ASRouterTelemetry(); 383 let { ping, pingType } = await instance.applyMenuMessagePolicy({}); 384 385 Assert.equal( 386 ping.client_id, 387 Services.prefs.getCharPref("toolkit.telemetry.cachedClientID") 388 ); 389 Assert.equal(pingType, "menu"); 390 }); 391 392 add_task(async function test_applyUndesiredEventPolicy() { 393 info( 394 "ASRouterTelemetry.applyUndesiredEventPolicy should exclude client_id " + 395 "and use impression_id" 396 ); 397 let sandbox = sinon.createSandbox(); 398 sandbox 399 .stub(ASRouterTelemetry.prototype, "getOrCreateImpressionId") 400 .returns(FAKE_UUID); 401 402 let instance = new ASRouterTelemetry(); 403 let data = { 404 action: "asrouter_undesired_event", 405 event: "RS_MISSING_DATA", 406 }; 407 let { ping, pingType } = await instance.applyUndesiredEventPolicy(data); 408 409 Assert.equal(pingType, "undesired-events"); 410 Assert.equal(ping.client_id, undefined); 411 Assert.equal(ping.impression_id, FAKE_UUID); 412 413 sandbox.restore(); 414 }); 415 416 add_task(async function test_createASRouterEvent_valid_ping() { 417 info( 418 "ASRouterTelemetry.createASRouterEvent should create a valid " + 419 "ASRouterEventPing ping" 420 ); 421 let instance = new ASRouterTelemetry(); 422 let action = { 423 type: msg.AS_ROUTER_TELEMETRY_USER_EVENT, 424 data: { 425 action: "cfr_user_event", 426 event: "CLICK", 427 message_id: "cfr_message_01", 428 }, 429 }; 430 let { ping } = await instance.createASRouterEvent(action); 431 432 await assertASRouterEventPingValid(ping); 433 Assert.equal(ping.event, "CLICK"); 434 }); 435 436 add_task(async function test_createASRouterEvent_call_correctPolicy() { 437 let testCallCorrectPolicy = async (expectedPolicyFnName, data) => { 438 info( 439 `ASRouterTelemetry.createASRouterEvent should call ${expectedPolicyFnName} ` + 440 `on action ${data.action} and event ${data.event}` 441 ); 442 let sandbox = sinon.createSandbox(); 443 let instance = new ASRouterTelemetry(); 444 sandbox.stub(instance, expectedPolicyFnName); 445 446 let action = { type: msg.AS_ROUTER_TELEMETRY_USER_EVENT, data }; 447 await instance.createASRouterEvent(action); 448 Assert.ok( 449 instance[expectedPolicyFnName].calledOnce, 450 `ASRouterTelemetry.${expectedPolicyFnName} called` 451 ); 452 453 sandbox.restore(); 454 }; 455 456 testCallCorrectPolicy("applyCFRPolicy", { 457 action: "cfr_user_event", 458 event: "IMPRESSION", 459 message_id: "cfr_message_01", 460 }); 461 462 testCallCorrectPolicy("applyToolbarBadgePolicy", { 463 action: "badge_user_event", 464 event: "IMPRESSION", 465 message_id: "badge_message_01", 466 }); 467 468 testCallCorrectPolicy("applyMomentsPolicy", { 469 action: "moments_user_event", 470 event: "CLICK_BUTTON", 471 message_id: "moments_message_01", 472 }); 473 474 testCallCorrectPolicy("applySpotlightPolicy", { 475 action: "spotlight_user_event", 476 event: "CLICK", 477 message_id: "SPOTLIGHT_MESSAGE_93", 478 }); 479 480 testCallCorrectPolicy("applyToastNotificationPolicy", { 481 action: "toast_notification_user_event", 482 event: "IMPRESSION", 483 message_id: "TEST_TOAST_NOTIFICATION1", 484 }); 485 486 testCallCorrectPolicy("applyUndesiredEventPolicy", { 487 action: "asrouter_undesired_event", 488 event: "UNDESIRED_EVENT", 489 }); 490 }); 491 492 add_task(async function test_createASRouterEvent_stringify_event_context() { 493 info( 494 "ASRouterTelemetry.createASRouterEvent should stringify event_context if " + 495 "it is an Object" 496 ); 497 let instance = new ASRouterTelemetry(); 498 let action = { 499 type: msg.AS_ROUTER_TELEMETRY_USER_EVENT, 500 data: { 501 action: "asrouter_undesired_event", 502 event: "UNDESIRED_EVENT", 503 event_context: { foo: "bar" }, 504 }, 505 }; 506 let { ping } = await instance.createASRouterEvent(action); 507 508 Assert.equal(ping.event_context, JSON.stringify({ foo: "bar" })); 509 }); 510 511 add_task(async function test_createASRouterEvent_not_stringify_event_context() { 512 info( 513 "ASRouterTelemetry.createASRouterEvent should not stringify event_context " + 514 "if it is a String" 515 ); 516 let instance = new ASRouterTelemetry(); 517 let action = { 518 type: msg.AS_ROUTER_TELEMETRY_USER_EVENT, 519 data: { 520 action: "asrouter_undesired_event", 521 event: "UNDESIRED_EVENT", 522 event_context: "foo", 523 }, 524 }; 525 let { ping } = await instance.createASRouterEvent(action); 526 527 Assert.equal(ping.event_context, "foo"); 528 }); 529 530 add_task(async function test_onAction_calls_handleASRouterUserEvent() { 531 let actions = [ 532 msg.AS_ROUTER_TELEMETRY_USER_EVENT, 533 msg.TOOLBAR_BADGE_TELEMETRY, 534 msg.TOOLBAR_PANEL_TELEMETRY, 535 msg.MOMENTS_PAGE_TELEMETRY, 536 msg.DOORHANGER_TELEMETRY, 537 ]; 538 539 Services.prefs.setBoolPref(PREF_TELEMETRY, true); 540 actions.forEach(type => { 541 info(`Testing ${type} action`); 542 let sandbox = sinon.createSandbox(); 543 let instance = new ASRouterTelemetry(); 544 545 const eventHandler = sandbox.spy(instance, "handleASRouterUserEvent"); 546 const action = { 547 type, 548 data: { event: "CLICK" }, 549 }; 550 551 instance.onAction(action); 552 553 Assert.ok(eventHandler.calledWith(action)); 554 sandbox.restore(); 555 }); 556 557 Services.prefs.clearUserPref(PREF_TELEMETRY); 558 }); 559 560 add_task( 561 async function test_SendASRouterUndesiredEvent_calls_handleASRouterUserEvent() { 562 info( 563 "ASRouterTelemetry.SendASRouterUndesiredEvent should call " + 564 "handleASRouterUserEvent" 565 ); 566 let sandbox = sinon.createSandbox(); 567 let instance = new ASRouterTelemetry(); 568 569 sandbox.stub(instance, "handleASRouterUserEvent"); 570 571 instance.SendASRouterUndesiredEvent({ foo: "bar" }); 572 573 Assert.ok( 574 instance.handleASRouterUserEvent.calledOnce, 575 "ASRouterTelemetry.handleASRouterUserEvent was called once" 576 ); 577 let [payload] = instance.handleASRouterUserEvent.firstCall.args; 578 Assert.equal(payload.data.action, "asrouter_undesired_event"); 579 Assert.equal(payload.data.foo, "bar"); 580 581 sandbox.restore(); 582 } 583 ); 584 585 add_task( 586 async function test_handleASRouterUserEvent_calls_submitGleanPingForPing() { 587 info( 588 "ASRouterTelemetry.handleASRouterUserEvent should call " + 589 "submitGleanPingForPing on known pingTypes when telemetry is enabled" 590 ); 591 592 let data = { 593 action: "spotlight_user_event", 594 event: "IMPRESSION", 595 message_id: "12345", 596 }; 597 let sandbox = sinon.createSandbox(); 598 let instance = new ASRouterTelemetry(); 599 Services.fog.testResetFOG(); 600 601 Services.prefs.setBoolPref(PREF_TELEMETRY, true); 602 603 sandbox.spy(AboutWelcomeTelemetry.prototype, "submitGleanPingForPing"); 604 605 await instance.handleASRouterUserEvent({ data }); 606 607 Assert.ok( 608 AboutWelcomeTelemetry.prototype.submitGleanPingForPing.calledOnce, 609 "AboutWelcomeTelemetry.submitGleanPingForPing called once" 610 ); 611 612 Services.prefs.clearUserPref(PREF_TELEMETRY); 613 sandbox.restore(); 614 } 615 ); 616 617 add_task( 618 async function test_handleASRouterUserEvent_no_submit_unknown_pingTypes() { 619 info( 620 "ASRouterTelemetry.handleASRouterUserEvent not submit pings on unknown pingTypes" 621 ); 622 623 let data = { 624 action: "unknown_event", 625 event: "IMPRESSION", 626 message_id: "12345", 627 }; 628 let sandbox = sinon.createSandbox(); 629 let instance = new ASRouterTelemetry(); 630 Services.fog.testResetFOG(); 631 632 Services.prefs.setBoolPref(PREF_TELEMETRY, true); 633 634 sandbox.spy(AboutWelcomeTelemetry.prototype, "submitGleanPingForPing"); 635 636 await instance.handleASRouterUserEvent({ data }); 637 638 Assert.ok( 639 AboutWelcomeTelemetry.prototype.submitGleanPingForPing.notCalled, 640 "AboutWelcomeTelemetry.submitGleanPingForPing not called" 641 ); 642 643 Services.prefs.clearUserPref(PREF_TELEMETRY); 644 sandbox.restore(); 645 } 646 ); 647 648 add_task( 649 async function test_isInCFRCohort_return_false_for_no_CFR_experiment() { 650 info( 651 "ASRouterTelemetry.isInCFRCohort should return false if there " + 652 "is no CFR experiment registered" 653 ); 654 let instance = new ASRouterTelemetry(); 655 Assert.ok( 656 !instance.isInCFRCohort, 657 "Should not be in CFR cohort by default" 658 ); 659 } 660 ); 661 662 add_task( 663 async function test_isInCFRCohort_return_true_for_registered_CFR_experiment() { 664 info( 665 "ASRouterTelemetry.isInCFRCohort should return true if there " + 666 "is a CFR experiment registered" 667 ); 668 let sandbox = sinon.createSandbox(); 669 let instance = new ASRouterTelemetry(); 670 671 sandbox.stub(NimbusFeatures.cfr, "getEnrollmentMetadata").returns({ 672 slug: "SOME-CFR-EXP", 673 branch: "branch-slug", 674 isRollout: false, 675 }); 676 677 Assert.ok(instance.isInCFRCohort, "Should be in a CFR cohort"); 678 679 sandbox.restore(); 680 } 681 );