diff options
Diffstat (limited to 'toolkit/components/messaging-system/test/unit/test_ExperimentAPI.js')
-rw-r--r-- | toolkit/components/messaging-system/test/unit/test_ExperimentAPI.js | 374 |
1 files changed, 374 insertions, 0 deletions
diff --git a/toolkit/components/messaging-system/test/unit/test_ExperimentAPI.js b/toolkit/components/messaging-system/test/unit/test_ExperimentAPI.js new file mode 100644 index 0000000000..3847f8f0e6 --- /dev/null +++ b/toolkit/components/messaging-system/test/unit/test_ExperimentAPI.js @@ -0,0 +1,374 @@ +"use strict"; + +const { ExperimentAPI } = ChromeUtils.import( + "resource://messaging-system/experiments/ExperimentAPI.jsm" +); +const { ExperimentFakes } = ChromeUtils.import( + "resource://testing-common/MSTestUtils.jsm" +); +const { TestUtils } = ChromeUtils.import( + "resource://testing-common/TestUtils.jsm" +); +const COLLECTION_ID_PREF = "messaging-system.rsexperimentloader.collection_id"; + +/** + * #getExperiment + */ +add_task(async function test_getExperiment_fromChild_slug() { + const sandbox = sinon.createSandbox(); + const manager = ExperimentFakes.manager(); + const expected = ExperimentFakes.experiment("foo"); + + await manager.onStartup(); + + sandbox.stub(ExperimentAPI, "_store").get(() => ExperimentFakes.childStore()); + + manager.store.addExperiment(expected); + + // Wait to sync to child + await TestUtils.waitForCondition( + () => ExperimentAPI.getExperiment({ slug: "foo" }), + "Wait for child to sync" + ); + + Assert.equal( + ExperimentAPI.getExperiment({ slug: "foo" }).slug, + expected.slug, + "should return an experiment by slug" + ); + + sandbox.restore(); +}); + +add_task(async function test_getExperiment_fromParent_slug() { + const sandbox = sinon.createSandbox(); + const manager = ExperimentFakes.manager(); + const expected = ExperimentFakes.experiment("foo"); + + await manager.onStartup(); + sandbox.stub(ExperimentAPI, "_store").get(() => manager.store); + await ExperimentAPI.ready(); + + manager.store.addExperiment(expected); + + Assert.equal( + ExperimentAPI.getExperiment({ slug: "foo" }).slug, + expected.slug, + "should return an experiment by slug" + ); + + sandbox.restore(); +}); + +add_task(async function test_getExperimentMetaData() { + const sandbox = sinon.createSandbox(); + const manager = ExperimentFakes.manager(); + const expected = ExperimentFakes.experiment("foo"); + + await manager.onStartup(); + sandbox.stub(ExperimentAPI, "_store").get(() => manager.store); + await ExperimentAPI.ready(); + + manager.store.addExperiment(expected); + + let metadata = ExperimentAPI.getExperimentMetaData({ slug: expected.slug }); + + Assert.equal( + Object.keys(metadata.branch).length, + 1, + "Should only expose one property" + ); + Assert.equal( + metadata.branch.slug, + expected.branch.slug, + "Should have the slug prop" + ); + + sandbox.restore(); +}); + +add_task(async function test_getExperiment_feature() { + const sandbox = sinon.createSandbox(); + const manager = ExperimentFakes.manager(); + const expected = ExperimentFakes.experiment("foo", { + branch: { + slug: "treatment", + value: { title: "hi" }, + feature: { featureId: "cfr", enabled: true }, + }, + }); + + await manager.onStartup(); + + sandbox.stub(ExperimentAPI, "_store").get(() => ExperimentFakes.childStore()); + + manager.store.addExperiment(expected); + + // Wait to sync to child + await TestUtils.waitForCondition( + () => ExperimentAPI.getExperiment({ featureId: "cfr" }), + "Wait for child to sync" + ); + + Assert.equal( + ExperimentAPI.getExperiment({ featureId: "cfr" }).slug, + expected.slug, + "should return an experiment by featureId" + ); + + sandbox.restore(); +}); + +/** + * #getValue + */ +add_task(async function test_getValue() { + const sandbox = sinon.createSandbox(); + const manager = ExperimentFakes.manager(); + const feature = { + featureId: "aboutwelcome", + enabled: true, + value: { title: "hi" }, + }; + const expected = ExperimentFakes.experiment("foo", { + branch: { slug: "treatment", feature }, + }); + + await manager.onStartup(); + + sandbox.stub(ExperimentAPI, "_store").get(() => ExperimentFakes.childStore()); + + manager.store.addExperiment(expected); + + await TestUtils.waitForCondition( + () => ExperimentAPI.getExperiment({ slug: "foo" }), + "Wait for child to sync" + ); + + Assert.deepEqual( + ExperimentAPI.getFeatureValue({ featureId: "aboutwelcome" }), + feature.value, + "should return a Branch by feature" + ); + + Assert.deepEqual( + ExperimentAPI.getFeatureBranch({ featureId: "aboutwelcome" }), + expected.branch, + "should return an experiment branch by feature" + ); + + Assert.equal( + ExperimentAPI.getFeatureBranch({ featureId: "doesnotexist" }), + undefined, + "should return undefined if the experiment is not found" + ); + + sandbox.restore(); +}); + +/** + * #isFeatureEnabled + */ + +add_task(async function test_isFeatureEnabledDefault() { + const sandbox = sinon.createSandbox(); + const manager = ExperimentFakes.manager(); + const FEATURE_ENABLED_DEFAULT = true; + const expected = ExperimentFakes.experiment("foo"); + + await manager.onStartup(); + + sandbox.stub(ExperimentAPI, "_store").get(() => manager.store); + + manager.store.addExperiment(expected); + + Assert.deepEqual( + ExperimentAPI.isFeatureEnabled("aboutwelcome", FEATURE_ENABLED_DEFAULT), + FEATURE_ENABLED_DEFAULT, + "should return enabled true as default" + ); + sandbox.restore(); +}); + +add_task(async function test_isFeatureEnabled() { + const sandbox = sinon.createSandbox(); + const manager = ExperimentFakes.manager(); + const feature = { + featureId: "aboutwelcome", + enabled: false, + value: null, + }; + const expected = ExperimentFakes.experiment("foo", { + branch: { slug: "treatment", feature }, + }); + + await manager.onStartup(); + + sandbox.stub(ExperimentAPI, "_store").get(() => manager.store); + + manager.store.addExperiment(expected); + + Assert.deepEqual( + ExperimentAPI.isFeatureEnabled("aboutwelcome", true), + feature.enabled, + "should return feature as disabled" + ); + sandbox.restore(); +}); + +/** + * #getRecipe + */ +add_task(async function test_getRecipe() { + const sandbox = sinon.createSandbox(); + const RECIPE = ExperimentFakes.recipe("foo"); + const collectionName = Services.prefs.getStringPref(COLLECTION_ID_PREF); + sandbox.stub(ExperimentAPI._remoteSettingsClient, "get").resolves([RECIPE]); + + const recipe = await ExperimentAPI.getRecipe("foo"); + Assert.deepEqual( + recipe, + RECIPE, + "should return an experiment recipe if found" + ); + Assert.equal( + ExperimentAPI._remoteSettingsClient.collectionName, + collectionName, + "Loaded the expected collection" + ); + + sandbox.restore(); +}); + +add_task(async function test_getRecipe_Failure() { + const sandbox = sinon.createSandbox(); + sandbox.stub(ExperimentAPI._remoteSettingsClient, "get").throws(); + + const recipe = await ExperimentAPI.getRecipe("foo"); + Assert.equal(recipe, undefined, "should return undefined if RS throws"); + + sandbox.restore(); +}); + +/** + * #getAllBranches + */ +add_task(async function test_getAllBranches() { + const sandbox = sinon.createSandbox(); + const RECIPE = ExperimentFakes.recipe("foo"); + sandbox.stub(ExperimentAPI._remoteSettingsClient, "get").resolves([RECIPE]); + + const branches = await ExperimentAPI.getAllBranches("foo"); + Assert.deepEqual( + branches, + RECIPE.branches, + "should return all branches if found a recipe" + ); + + sandbox.restore(); +}); + +add_task(async function test_getAllBranches_Failure() { + const sandbox = sinon.createSandbox(); + sandbox.stub(ExperimentAPI._remoteSettingsClient, "get").throws(); + + const branches = await ExperimentAPI.getAllBranches("foo"); + Assert.equal(branches, undefined, "should return undefined if RS throws"); + + sandbox.restore(); +}); + +/** + * #on + * #off + */ +add_task(async function test_addExperiment_eventEmit_add() { + const sandbox = sinon.createSandbox(); + const slugStub = sandbox.stub(); + const featureStub = sandbox.stub(); + const experiment = ExperimentFakes.experiment("foo", { + branch: { + slug: "variant", + feature: { featureId: "purple", enabled: true }, + }, + }); + const store = ExperimentFakes.store(); + sandbox.stub(ExperimentAPI, "_store").get(() => store); + + await store.init(); + await ExperimentAPI.ready(); + + ExperimentAPI.on("update", { slug: "foo" }, slugStub); + ExperimentAPI.on("update", { featureId: "purple" }, featureStub); + + store.addExperiment(experiment); + + Assert.equal(slugStub.callCount, 1); + Assert.equal(slugStub.firstCall.args[1].slug, experiment.slug); + Assert.equal(featureStub.callCount, 1); + Assert.equal(featureStub.firstCall.args[1].slug, experiment.slug); +}); + +add_task(async function test_updateExperiment_eventEmit_add_and_update() { + const sandbox = sinon.createSandbox(); + const slugStub = sandbox.stub(); + const featureStub = sandbox.stub(); + const experiment = ExperimentFakes.experiment("foo", { + branch: { + slug: "variant", + feature: { featureId: "purple", enabled: true }, + }, + }); + const store = ExperimentFakes.store(); + sandbox.stub(ExperimentAPI, "_store").get(() => store); + + await store.init(); + await ExperimentAPI.ready(); + + store.addExperiment(experiment); + + ExperimentAPI.on("update", { slug: "foo" }, slugStub); + ExperimentAPI.on("update", { featureId: "purple" }, featureStub); + + store.updateExperiment(experiment.slug, experiment); + + await TestUtils.waitForCondition( + () => slugStub.callCount == 2, + "Wait for `on` method to notify callback about the `add` event." + ); + // Called twice, once when attaching the event listener (because there is an + // existing experiment with that name) and 2nd time for the update event + Assert.equal(slugStub.firstCall.args[1].slug, experiment.slug); + Assert.equal(featureStub.callCount, 2, "Called twice for feature"); + Assert.equal(featureStub.firstCall.args[1].slug, experiment.slug); +}); + +add_task(async function test_updateExperiment_eventEmit_off() { + const sandbox = sinon.createSandbox(); + const slugStub = sandbox.stub(); + const featureStub = sandbox.stub(); + const experiment = ExperimentFakes.experiment("foo", { + branch: { + slug: "variant", + feature: { featureId: "purple", enabled: true }, + }, + }); + const store = ExperimentFakes.store(); + sandbox.stub(ExperimentAPI, "_store").get(() => store); + + await store.init(); + await ExperimentAPI.ready(); + + ExperimentAPI.on("update", { slug: "foo" }, slugStub); + ExperimentAPI.on("update", { featureId: "purple" }, featureStub); + + store.addExperiment(experiment); + + ExperimentAPI.off("update:foo", slugStub); + ExperimentAPI.off("update:purple", featureStub); + + store.updateExperiment(experiment.slug, experiment); + + Assert.equal(slugStub.callCount, 1, "Called only once before `off`"); + Assert.equal(featureStub.callCount, 1, "Called only once before `off`"); +}); |