diff --git a/toolkit/components/normandy/lib/ClientEnvironment.jsm b/toolkit/components/normandy/lib/ClientEnvironment.jsm index 5ac5a95188cf..3da4921666b6 100644 --- a/toolkit/components/normandy/lib/ClientEnvironment.jsm +++ b/toolkit/components/normandy/lib/ClientEnvironment.jsm @@ -21,6 +21,21 @@ ChromeUtils.defineModuleGetter( "PreferenceExperiments", "resource://normandy/lib/PreferenceExperiments.jsm" ); +ChromeUtils.defineModuleGetter( + this, + "PreferenceRollouts", + "resource://normandy/lib/PreferenceRollouts.jsm" +); +ChromeUtils.defineModuleGetter( + this, + "AddonStudies", + "resource://normandy/lib/AddonStudies.jsm" +); +ChromeUtils.defineModuleGetter( + this, + "AddonRollouts", + "resource://normandy/lib/AddonRollouts.jsm" +); ChromeUtils.defineModuleGetter( this, "NormandyUtils", @@ -108,6 +123,32 @@ class ClientEnvironment extends ClientEnvironmentBase { })(); } + static get studies() { + return (async () => { + const rv = { pref: {}, addon: {} }; + for (const prefStudy of await PreferenceExperiments.getAll()) { + rv.pref[prefStudy.slug] = prefStudy; + } + for (const addonStudy of await AddonStudies.getAll()) { + rv.addon[addonStudy.slug] = addonStudy; + } + return rv; + })(); + } + + static get rollouts() { + return (async () => { + const rv = { pref: {}, addon: {} }; + for (const prefRollout of await PreferenceRollouts.getAll()) { + rv.pref[prefRollout.slug] = prefRollout; + } + for (const addonRollout of await AddonRollouts.getAll()) { + rv.addon[addonRollout.slug] = addonRollout; + } + return rv; + })(); + } + static get isFirstRun() { return Services.prefs.getBoolPref("app.normandy.first_run", true); } diff --git a/toolkit/components/normandy/test/NormandyTestUtils.jsm b/toolkit/components/normandy/test/NormandyTestUtils.jsm index 899ee1e5a820..2c960f93b4a4 100644 --- a/toolkit/components/normandy/test/NormandyTestUtils.jsm +++ b/toolkit/components/normandy/test/NormandyTestUtils.jsm @@ -23,7 +23,7 @@ const NormandyTestUtils = { }, factories: { - addonStudyFactory(attrs) { + addonStudyFactory(attrs = {}) { for (const key of ["name", "description"]) { if (attrs && attrs[key]) { throw new Error( @@ -55,7 +55,7 @@ const NormandyTestUtils = { ); }, - branchedAddonStudyFactory(attrs) { + branchedAddonStudyFactory(attrs = {}) { return NormandyTestUtils.factories.addonStudyFactory( Object.assign( { @@ -66,7 +66,7 @@ const NormandyTestUtils = { ); }, - preferenceStudyFactory(attrs) { + preferenceStudyFactory(attrs = {}) { const defaultPref = { "test.study": {}, }; diff --git a/toolkit/components/normandy/test/browser/browser_ClientEnvironment.js b/toolkit/components/normandy/test/browser/browser_ClientEnvironment.js index 678f062af3f7..85ff7b28d3f5 100644 --- a/toolkit/components/normandy/test/browser/browser_ClientEnvironment.js +++ b/toolkit/components/normandy/test/browser/browser_ClientEnvironment.js @@ -4,8 +4,11 @@ ChromeUtils.import("resource://gre/modules/Services.jsm", this); ChromeUtils.import("resource://gre/modules/TelemetryController.jsm", this); ChromeUtils.import("resource://gre/modules/AddonManager.jsm", this); ChromeUtils.import("resource://testing-common/AddonTestUtils.jsm", this); +ChromeUtils.import("resource://normandy/lib/AddonRollouts.jsm", this); ChromeUtils.import("resource://normandy/lib/ClientEnvironment.jsm", this); ChromeUtils.import("resource://normandy/lib/PreferenceExperiments.jsm", this); +ChromeUtils.import("resource://normandy/lib/PreferenceRollouts.jsm", this); +ChromeUtils.import("resource://normandy/lib/RecipeRunner.jsm", this); ChromeUtils.import("resource://testing-common/NormandyTestUtils.jsm", this); add_task(async function testTelemetry() { @@ -164,3 +167,84 @@ add_task(async function isFirstRun() { await SpecialPowers.pushPrefEnv({ set: [["app.normandy.first_run", true]] }); ok(ClientEnvironment.isFirstRun, "isFirstRun is read from preferences"); }); + +decorate_task( + PreferenceExperiments.withMockExperiments([ + NormandyTestUtils.factories.preferenceStudyFactory({ + branch: "a-test-branch", + }), + ]), + AddonStudies.withStudies([ + NormandyTestUtils.factories.branchedAddonStudyFactory({ + branch: "b-test-branch", + }), + ]), + async function testStudies([prefExperiment], [addonStudy]) { + Assert.deepEqual( + await ClientEnvironment.studies, + { + pref: { + [prefExperiment.slug]: prefExperiment, + }, + addon: { + [addonStudy.slug]: addonStudy, + }, + }, + "addon and preference studies shold be accessible" + ); + is( + (await ClientEnvironment.studies).pref[prefExperiment.slug].branch, + "a-test-branch", + "A specific preference experiment field should be accessible in the context" + ); + is( + (await ClientEnvironment.studies).addon[addonStudy.slug].branch, + "b-test-branch", + "A specific addon study field should be accessible in the context" + ); + + ok(RecipeRunner.getCapabilities().has("jexl.context.normandy.studies")); + ok(RecipeRunner.getCapabilities().has("jexl.context.env.studies")); + } +); + +decorate_task(PreferenceRollouts.withTestMock, async function testRollouts() { + const prefRollout = { + slug: "test-rollout", + preference: [], + enrollmentId: "test-enrollment-id-1", + }; + await PreferenceRollouts.add(prefRollout); + const addonRollout = { + slug: "test-rollout-1", + extension: {}, + enrollmentId: "test-enrollment-id-2", + }; + await AddonRollouts.add(addonRollout); + + Assert.deepEqual( + await ClientEnvironment.rollouts, + { + pref: { + [prefRollout.slug]: prefRollout, + }, + addon: { + [addonRollout.slug]: addonRollout, + }, + }, + "addon and preference rollouts should be accessible" + ); + is( + (await ClientEnvironment.rollouts).pref[prefRollout.slug].enrollmentId, + "test-enrollment-id-1", + "A specific preference rollout field should be accessible in the context" + ); + is( + (await ClientEnvironment.rollouts).addon[addonRollout.slug].enrollmentId, + "test-enrollment-id-2", + "A specific addon rollout field should be accessible in the context" + ); + + ok(RecipeRunner.getCapabilities().has("jexl.context.normandy.rollouts")); + ok(RecipeRunner.getCapabilities().has("jexl.context.env.rollouts")); +});