"use strict"; const { ExperimentAPI, _ExperimentFeature: ExperimentFeature } = ChromeUtils.importESModule("resource://nimbus/ExperimentAPI.sys.mjs"); const { AppConstants } = ChromeUtils.importESModule( "resource://gre/modules/AppConstants.sys.mjs" ); async function setupForExperimentFeature() { const sandbox = sinon.createSandbox(); const manager = ExperimentFakes.manager(); await manager.onStartup(); sandbox.stub(ExperimentAPI, "_store").get(() => manager.store); return { sandbox, manager }; } const FEATURE_ID = "testfeature1"; // Note: this gets deleted at the end of tests const TEST_PREF_BRANCH = "testfeature1."; const TEST_VARIABLES = { enabled: { type: "boolean", fallbackPref: `${TEST_PREF_BRANCH}enabled`, }, name: { type: "string", fallbackPref: `${TEST_PREF_BRANCH}name`, }, count: { type: "int", fallbackPref: `${TEST_PREF_BRANCH}count`, }, items: { type: "json", fallbackPref: `${TEST_PREF_BRANCH}items`, }, }; function createInstanceWithVariables(variables) { return new ExperimentFeature(FEATURE_ID, { variables, }); } add_task(async function test_ExperimentFeature_getFallbackPrefName() { const instance = createInstanceWithVariables(TEST_VARIABLES); Assert.equal( instance.getFallbackPrefName("enabled"), "testfeature1.enabled", "should return the fallback preference name" ); }); add_task(async function test_ExperimentFeature_getVariable_notRegistered() { const instance = createInstanceWithVariables(TEST_VARIABLES); if (Cu.isInAutomation || AppConstants.NIGHTLY_BUILD) { Assert.throws( () => { instance.getVariable("non_existant_variable"); }, /Nimbus: Warning - variable "non_existant_variable" is not defined in FeatureManifest\.yaml/, "should throw in automation for variables not defined in the manifest" ); } else { info("Won't throw when running in Beta and release candidates"); } }); add_task(async function test_ExperimentFeature_getVariable_noFallbackPref() { const instance = createInstanceWithVariables({ foo: { type: "json" }, }); Assert.equal( instance.getVariable("foo"), undefined, "should return undefined if no values are set and no fallback pref is defined" ); }); add_task(async function test_ExperimentFeature_getVariable_precedence() { const { sandbox, manager } = await setupForExperimentFeature(); const instance = createInstanceWithVariables(TEST_VARIABLES); const prefName = TEST_VARIABLES.items.fallbackPref; const rollout = ExperimentFakes.rollout(`${FEATURE_ID}-rollout`, { branch: { slug: "slug", features: [ { featureId: FEATURE_ID, value: { items: [4, 5, 6] }, }, ], }, }); Services.prefs.clearUserPref(prefName); Assert.equal( instance.getVariable("items"), undefined, "should return undefined if the fallback pref is not set" ); // Default pref values Services.prefs.setStringPref(prefName, JSON.stringify([1, 2, 3])); Assert.deepEqual( instance.getVariable("items"), [1, 2, 3], "should return the default pref value" ); // Remote default values await manager.store.addEnrollment(rollout); Assert.deepEqual( instance.getVariable("items"), [4, 5, 6], "should return the remote default value over the default pref value" ); // Experiment values const doExperimentCleanup = await ExperimentFakes.enrollWithFeatureConfig( { featureId: FEATURE_ID, value: { items: [7, 8, 9], }, }, { manager } ); Assert.deepEqual( instance.getVariable("items"), [7, 8, 9], "should return the experiment value over the remote value" ); // Cleanup Services.prefs.deleteBranch(TEST_PREF_BRANCH); await doExperimentCleanup(); sandbox.restore(); }); add_task(async function test_ExperimentFeature_getVariable_partial_values() { const { sandbox, manager } = await setupForExperimentFeature(); const instance = createInstanceWithVariables(TEST_VARIABLES); const rollout = ExperimentFakes.rollout(`${FEATURE_ID}-rollout`, { branch: { slug: "slug", features: [ { featureId: FEATURE_ID, value: { name: "abc" }, }, ], }, }); // Set up a pref value for .enabled, // a remote value for .name, // an experiment value for .items Services.prefs.setBoolPref(TEST_VARIABLES.enabled.fallbackPref, true); await manager.store.addEnrollment(rollout); const doExperimentCleanup = await ExperimentFakes.enrollWithFeatureConfig( { featureId: FEATURE_ID, value: {}, }, { manager } ); Assert.equal( instance.getVariable("enabled"), true, "should skip missing variables from remote defaults" ); Assert.equal( instance.getVariable("name"), "abc", "should skip missing variables from experiments" ); // Cleanup Services.prefs.getDefaultBranch("").deleteBranch(TEST_PREF_BRANCH); Services.prefs.deleteBranch(TEST_PREF_BRANCH); await doExperimentCleanup(); sandbox.restore(); });