/* Any copyright is dedicated to the Public Domain. * http://creativecommons.org/publicdomain/zero/1.0/ */ const { FirefoxLabs } = ChromeUtils.importESModule( "resource://nimbus/FirefoxLabs.sys.mjs" ); function setupTest({ ...ctx }) { return NimbusTestUtils.setupTest({ ...ctx, clearTelemetry: true }); } add_setup(function () { Services.fog.initializeFOG(); }); add_task(async function test_all() { // This recipe conflicts with `feature-conflict`, defined below. const preexisting = NimbusTestUtils.factories.recipe.withFeatureConfig( "preexisting-rollout", { featureId: "nimbus-qa-1" }, { isRollout: true } ); const alreadyEnrolled = NimbusTestUtils.factories.recipe.withFeatureConfig( "already-enrolled-opt-in", { featureId: "nimbus-qa-2" }, { isRollout: true, isFirefoxLabsOptIn: true, firefoxLabsTitle: "title", firefoxLabsDescription: "description", firefoxLabsDescriptionLinks: null, firefoxLabsGroup: "group", requiresRestart: false, } ); const preexistingPaused = NimbusTestUtils.factories.recipe.withFeatureConfig( "preexisting-paused", { featureId: "no-feature-firefox-desktop" }, { isRollout: true, isEnrollmentPaused: true, isFirefoxLabsOptIn: true, firefoxLabsTitle: "true", firefoxLabsDescription: "description", firefoxLabsDescriptionLinks: null, firefoxLabsGroup: "group", requiresRestart: false, } ); const { cleanup } = await setupTest({ init: false, storePath: await NimbusTestUtils.createStoreWith(async store => { await NimbusTestUtils.addEnrollmentForRecipe(preexisting, { store }); await NimbusTestUtils.addEnrollmentForRecipe(alreadyEnrolled, { store }); await NimbusTestUtils.addEnrollmentForRecipe(preexistingPaused, { store, }); }), experiments: [ NimbusTestUtils.factories.recipe("opt-in-rollout", { isRollout: true, isFirefoxLabsOptIn: true, firefoxLabsTitle: "title", firefoxLabsDescription: "description", firefoxLabsDescriptionLinks: null, firefoxLabsGroup: "group", requiresRestart: false, }), NimbusTestUtils.factories.recipe("opt-in-experiment", { branches: [ { ...NimbusTestUtils.factories.recipe.branches[0], firefoxLabsTitle: "title", }, ], isFirefoxLabsOptIn: true, firefoxLabsTitle: "title", firefoxLabsDescription: "description", firefoxLabsDescriptionLinks: null, firefoxLabsGroup: "group", requiresRestart: false, }), NimbusTestUtils.factories.recipe("targeting-fail", { targeting: "false", isRollout: true, isFirefoxLabsOptIn: true, firefoxLabsTitle: "title", firefoxLabsDescription: "description", firefoxLabsDescriptionLinks: null, firefoxLabsGroup: "group", requiresRestart: false, }), NimbusTestUtils.factories.recipe("bucketing-fail", { bucketConfig: { ...NimbusTestUtils.factories.recipe.bucketConfig, count: 0, }, isRollout: true, isFirefoxLabsOptIn: true, firefoxLabsTitle: "title", firefoxLabsDescription: "description", firefoxLabsDescriptionLinks: null, firefoxLabsGroup: "group", requiresRestart: false, }), NimbusTestUtils.factories.recipe.withFeatureConfig( "feature-does-not-exist", { featureId: "bogus" }, { isFirefoxLabsOptIn: true, firefoxLabsTitle: "title", firefoxLabsDescription: "description", firefoxLabsDescriptionLinks: null, firefoxLabsGroup: "group", requiresRestart: false, } ), NimbusTestUtils.factories.recipe.withFeatureConfig( "feature-conflict", { featureId: "nimbus-qa-1" }, { isRollout: true } ), NimbusTestUtils.factories.recipe.withFeatureConfig("experiment", { featureId: "no-feature-firefox-desktop", }), NimbusTestUtils.factories.recipe.withFeatureConfig( "rollout", { featureId: "no-feature-firefox-desktop" }, { isRollout: true } ), NimbusTestUtils.factories.recipe.withFeatureConfig( "paused", { featureId: "no-feature-firefox-desktop" }, { isRollout: true, isEnrollmentPaused: true, isFirefoxLabsOptIn: true, firefoxLabsTitle: "title", firefoxLabsDescription: "description", firefoxLabsDescriptionLinks: null, firefoxLabsGroup: "group", requiresRestart: false, } ), preexisting, // Prevent unenrollment. preexistingPaused, alreadyEnrolled, ], migrationState: NimbusTestUtils.migrationState.LATEST, }); await ExperimentAPI.init(); const labs = await FirefoxLabs.create(); const availableSlugs = Array.from(labs.all(), recipe => recipe.slug).sort(); Assert.deepEqual( availableSlugs, [ "opt-in-rollout", "opt-in-experiment", "already-enrolled-opt-in", "preexisting-paused", ].sort(), "Should return all opt in recipes that match targeting and bucketing" ); await NimbusTestUtils.cleanupManager([ "already-enrolled-opt-in", "experiment", "rollout", "preexisting-rollout", "preexisting-paused", ]); await cleanup(); }); add_task(async function test_enroll() { Services.fog.applyServerKnobsConfig( JSON.stringify({ metrics_enabled: { "nimbus_events.enrollment_status": true, }, }) ); const recipe = NimbusTestUtils.factories.recipe.withFeatureConfig( "opt-in", { featureId: "nimbus-qa-1" }, { isRollout: true, isFirefoxLabsOptIn: true, firefoxLabsTitle: "placeholder", firefoxLabsDescription: "placeholder", firefoxLabsDescriptionLinks: null, firefoxLabsGroup: "placeholder", requiresRestart: false, } ); const { sandbox, manager, cleanup } = await setupTest({ experiments: [recipe], init: false, }); const enrollSpy = sandbox.spy(manager, "enroll"); await ExperimentAPI.init(); const labs = await FirefoxLabs.create(); await Assert.rejects( labs.enroll(), /enroll: slug and branchSlug are required/, "Should throw when enroll() is called without a slug" ); await Assert.rejects( labs.enroll("opt-in"), /enroll: slug and branchSlug are required/, "Should throw when enroll() is called without a branch slug" ); await labs.enroll("bogus", "bogus"); Assert.ok( enrollSpy.notCalled, "ExperimentManager.enroll not called for unknown recipe" ); await labs.enroll(recipe.slug, "bogus"); Assert.ok( enrollSpy.notCalled, "ExperimentManager.enroll not called for invalid branch slug" ); await labs.enroll(recipe.slug, recipe.branches[0].slug); Assert.ok( enrollSpy.calledOnceWith(recipe, "rs-loader", { branchSlug: "control" }), "ExperimentManager.enroll called" ); Assert.deepEqual( Glean.nimbusEvents.enrollmentStatus .testGetValue("nimbus-targeting-context") ?.map(ev => ev.extra), [ { slug: recipe.slug, branch: "control", status: "Enrolled", reason: "OptIn", }, ] ); Assert.ok(manager.store.get(recipe.slug)?.active, "Active enrollment exists"); labs.unenroll(recipe.slug); await cleanup(); }); add_task(async function test_reenroll() { const recipe = NimbusTestUtils.factories.recipe("opt-in", { isFirefoxLabsOptIn: true, firefoxLabsTitle: "placeholder", firefoxLabsDescription: "placeholder", firefoxLabsDescriptionLinks: null, firefoxLabsGroup: "placeholder", requiresRestart: false, isRollout: true, }); const { manager, cleanup } = await setupTest({ experiments: [recipe] }); const labs = await FirefoxLabs.create(); Assert.strictEqual( typeof manager.store.get(recipe.slug), "undefined", `No enrollment for ${recipe.slug}` ); await labs.enroll(recipe.slug, "control"); Assert.ok( manager.store.get(recipe.slug)?.active, `Active enrollment for ${recipe.slug}` ); labs.unenroll(recipe.slug); Assert.strictEqual( manager.store.get(recipe.slug)?.active, false, `Inactive enrollment for ${recipe.slug}` ); await ExperimentAPI._rsLoader.updateRecipes(); Assert.strictEqual( manager.store.get(recipe.slug)?.active, false, `Inactive enrollment for ${recipe.slug} after updateRecipes()` ); await labs.enroll(recipe.slug, "control"); Assert.ok( manager.store.get(recipe.slug)?.active, `Active enrollment for ${recipe.slug}` ); labs.unenroll(recipe.slug); await cleanup(); }); add_task(async function test_unenroll() { Services.fog.applyServerKnobsConfig( JSON.stringify({ metrics_enabled: { "nimbus_events.enrollment_status": true, }, }) ); const { manager, cleanup } = await setupTest({ experiments: [ NimbusTestUtils.factories.recipe.withFeatureConfig( "rollout", { featureId: "nimbus-qa-1" }, { isRollout: true } ), NimbusTestUtils.factories.recipe.withFeatureConfig( "opt-in", { featureId: "nimbus-qa-2" }, { isRollout: true, isFirefoxLabsOptIn: true, firefoxLabsTitle: "title", firefoxLabsDescription: "description", firefoxLabsDescriptionLinks: null, firefoxLabsGroup: "group", requiresRestart: false, } ), ], }); const labs = await FirefoxLabs.create(); Assert.ok(manager.store.get("rollout")?.active, "Enrolled in rollout"); Assert.strictEqual( typeof manager.store.get("opt-in"), "undefined", "Did not enroll in rollout" ); await labs.enroll("opt-in", "control"); Assert.ok(manager.store.get("opt-in")?.active, "Enrolled in opt-in"); // Should not throw. labs.unenroll("bogus"); // Should not throw. labs.unenroll("rollout"); Assert.ok( manager.store.get("rollout").active, "Enrolled in rollout after attempting to unenroll with incorrect API" ); labs.unenroll("opt-in"); Assert.ok(!manager.store.get("opt-in").active, "Unenrolled from opt-in"); // Should not throw. labs.unenroll("opt-in"); Assert.deepEqual( Glean.nimbusEvents.enrollmentStatus .testGetValue("nimbus-targeting-context") ?.map(ev => ev.extra), [ { status: "Enrolled", slug: "opt-in", reason: "OptIn", branch: "control", }, { branch: "control", reason: "OptOut", slug: "opt-in", status: "Disqualified", }, ] ); manager.unenroll("rollout"); await cleanup(); }); add_task(async function test_reenroll_quickly() { const { cleanup } = await setupTest({ experiments: [ NimbusTestUtils.factories.recipe.withFeatureConfig( "optin", { featureId: "nimbus-qa-2" }, { isRollout: true, isFirefoxLabsOptIn: true, firefoxLabsTitle: "title", firefoxLabsDescription: "description", firefoxLabsDescriptionLinks: null, firefoxLabsGroup: "group", requiresRestart: false, } ), ], }); const labs = await FirefoxLabs.create(); Assert.equal( await NimbusTestUtils.queryEnrollment("optin"), null, "Enrollment does not exist" ); info("Enrolling in optin"); await labs.enroll("optin", "control"); await NimbusTestUtils.flushStore(); { const enrollment = await NimbusTestUtils.queryEnrollment("optin"); Assert.ok(enrollment, "Enrollment exists in database"); Assert.ok(enrollment.active, "Enrollment is active"); } info("Unenrolling and re-enrolling"); labs.unenroll("optin"); await labs.enroll("optin", "control"); await NimbusTestUtils.flushStore(); { const enrollment = await NimbusTestUtils.queryEnrollment("optin"); Assert.ok(enrollment, "Enrollment exists in database"); Assert.ok(enrollment.active, "Enrollment is active"); } labs.unenroll("optin"); await cleanup(); });