Bug 1693581 - Use named arguments instead for Normandy test decorators r=Gijs

Differential Revision: https://phabricator.services.mozilla.com/D106853
This commit is contained in:
Michael Cooper 2021-03-08 17:34:48 +00:00
Родитель 5ebe77b709
Коммит 5b085f6396
32 изменённых файлов: 701 добавлений и 708 удалений

Просмотреть файл

@ -147,19 +147,19 @@ var AddonStudies = {
* getDatabase, which we don't expose to avoid outside modules relying on the
* type of storage used for studies.
*
* @param {Array} [studies=[]]
* @param {Array} [addonStudies=[]]
*/
withStudies(studies = []) {
withStudies(addonStudies = []) {
return function wrapper(testFunction) {
return async function wrappedTestFunction(...args) {
return async function wrappedTestFunction(args) {
const oldStudies = await AddonStudies.getAll();
let db = await getDatabase();
await AddonStudies.clear();
const store = getStore(db, "readwrite");
await Promise.all(studies.map(study => store.add(study)));
await Promise.all(addonStudies.map(study => store.add(study)));
try {
await testFunction(...args, studies);
await testFunction({ ...args, addonStudies });
} finally {
db = await getDatabase();
await AddonStudies.clear();

Просмотреть файл

@ -353,12 +353,12 @@ var PreferenceExperiments = {
* Test wrapper that temporarily replaces the stored experiment data with fake
* data for testing.
*/
withMockExperiments(mockExperiments = []) {
withMockExperiments(prefExperiments = []) {
return function wrapper(testFunction) {
return async function wrappedTestFunction(...args) {
return async function wrappedTestFunction(args) {
const experiments = {};
for (const exp of mockExperiments) {
for (const exp of prefExperiments) {
if (exp.name) {
throw new Error(
"Preference experiments 'name' field has been replaced by 'slug' and 'userFacingName', please update."
@ -377,7 +377,7 @@ var PreferenceExperiments = {
const oldObservers = experimentObservers;
experimentObservers = new Map();
try {
await testFunction(...args, mockExperiments);
await testFunction({ ...args, prefExperiments });
} finally {
gStorePromise = oldPromise;
PreferenceExperiments.stopAllObservers();

Просмотреть файл

@ -201,18 +201,21 @@ var PreferenceRollouts = {
* Test wrapper that temporarily replaces the stored rollout data with fake
* data for testing.
*/
withTestMock({ graduationSet = new Set(), rollouts = [] } = {}) {
withTestMock({
graduationSet = new Set(),
rollouts: prefRollouts = [],
} = {}) {
return testFunction => {
return async (...args) => {
return async args => {
let db = await getDatabase();
const oldData = await getStore(db, "readonly").getAll();
await getStore(db, "readwrite").clear();
await Promise.all(rollouts.map(r => this.add(r)));
await Promise.all(prefRollouts.map(r => this.add(r)));
const oldGraduationSet = this.GRADUATION_SET;
this.GRADUATION_SET = graduationSet;
try {
await testFunction(...args, rollouts);
await testFunction({ ...args, prefRollouts });
} finally {
this.GRADUATION_SET = oldGraduationSet;
db = await getDatabase();

Просмотреть файл

@ -3,6 +3,9 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const { Preferences } = ChromeUtils.import(
"resource://gre/modules/Preferences.jsm"
);
const { AddonStudies } = ChromeUtils.import(
"resource://normandy/lib/AddonStudies.jsm"
);
@ -25,6 +28,11 @@ let _preferenceRolloutFactoryId = 0;
let testGlobals = {};
const preferenceBranches = {
user: Preferences,
default: new Preferences({ defaultBranch: true }),
};
const NormandyTestUtils = {
init({ add_task } = {}) {
testGlobals.add_task = add_task;
@ -211,7 +219,7 @@ const NormandyTestUtils = {
withMockRecipeCollection(recipes = []) {
return function wrapper(testFunc) {
return async function inner(...args) {
return async function inner(args) {
let recipeIds = new Set();
for (const recipe of recipes) {
if (!recipe.id || recipeIds.has(recipe.id)) {
@ -238,7 +246,7 @@ const NormandyTestUtils = {
let lastModified = await db.getLastModified();
await db.importChanges({}, lastModified + 1);
const collectionHelper = {
const mockRecipeCollection = {
async addRecipes(newRecipes) {
for (const recipe of newRecipes) {
if (!recipe.id || recipeIds.has(recipe)) {
@ -262,7 +270,7 @@ const NormandyTestUtils = {
};
try {
await testFunc(...args, collectionHelper);
await testFunc({ ...args, mockRecipeCollection });
} finally {
db = await RecipeRunner._remoteSettingsClientForTesting.db;
await db.clear();
@ -272,4 +280,50 @@ const NormandyTestUtils = {
};
};
},
MockPreferences: class {
constructor() {
this.oldValues = { user: {}, default: {} };
}
set(name, value, branch = "user") {
this.preserve(name, branch);
preferenceBranches[branch].set(name, value);
}
preserve(name, branch) {
if (!(name in this.oldValues[branch])) {
this.oldValues[branch][name] = preferenceBranches[branch].get(
name,
undefined
);
}
}
cleanup() {
for (const [branchName, values] of Object.entries(this.oldValues)) {
const preferenceBranch = preferenceBranches[branchName];
for (const [name, value] of Object.entries(values)) {
if (value !== undefined) {
preferenceBranch.set(name, value);
} else {
preferenceBranch.reset(name);
}
}
}
}
},
withMockPreferences() {
return function(testFunction) {
return async function inner(args) {
const mockPreferences = new NormandyTestUtils.MockPreferences();
try {
await testFunction({ ...args, mockPreferences });
} finally {
mockPreferences.cleanup();
}
};
};
},
};

Просмотреть файл

@ -115,7 +115,7 @@ decorate_task(AddonRollouts.withTestMock(), async function testHas() {
decorate_task(
AddonRollouts.withTestMock(),
withStub(TelemetryEnvironment, "setExperimentActive"),
async function testInitTelemetry(setExperimentActiveStub) {
async function testInitTelemetry({ setExperimentActiveStub }) {
await AddonRollouts.add({
slug: "test-rollout-active-1",
state: AddonRollouts.STATE_ACTIVE,

Просмотреть файл

@ -26,7 +26,7 @@ decorate_task(AddonStudies.withStudies(), async function testGetMissing() {
decorate_task(
AddonStudies.withStudies([addonStudyFactory({ slug: "test-study" })]),
async function testGet([study]) {
async function testGet({ addonStudies: [study] }) {
const storedStudy = await AddonStudies.get(study.recipeId);
Assert.deepEqual(study, storedStudy, "get retrieved a study from storage.");
}
@ -34,11 +34,11 @@ decorate_task(
decorate_task(
AddonStudies.withStudies([addonStudyFactory(), addonStudyFactory()]),
async function testGetAll(studies) {
async function testGetAll({ addonStudies }) {
const storedStudies = await AddonStudies.getAll();
Assert.deepEqual(
new Set(storedStudies),
new Set(studies),
new Set(addonStudies),
"getAll returns every stored study."
);
}
@ -46,7 +46,7 @@ decorate_task(
decorate_task(
AddonStudies.withStudies([addonStudyFactory({ slug: "test-study" })]),
async function testHas([study]) {
async function testHas({ addonStudies: [study] }) {
let hasStudy = await AddonStudies.has(study.recipeId);
ok(hasStudy, "has returns true for a study that exists in storage.");
@ -63,7 +63,7 @@ decorate_task(
addonStudyFactory({ slug: "test-study1" }),
addonStudyFactory({ slug: "test-study2" }),
]),
async function testClear([study1, study2]) {
async function testClear({ addonStudies: [study1, study2] }) {
const hasAll =
(await AddonStudies.has(study1.recipeId)) &&
(await AddonStudies.has(study2.recipeId));
@ -79,7 +79,7 @@ decorate_task(
decorate_task(
AddonStudies.withStudies([addonStudyFactory({ slug: "foo" })]),
async function testUpdate([study]) {
async function testUpdate({ addonStudies: [study] }) {
Assert.deepEqual(await AddonStudies.get(study.recipeId), study);
const updatedStudy = {
@ -109,13 +109,13 @@ decorate_task(
withSendEventSpy(),
withInstalledWebExtension(
{ id: "installed@example.com" },
/* expectUninstall: */ true
{ expectUninstall: true }
),
async function testInit(
[activeUninstalledStudy, activeInstalledStudy, inactiveStudy],
async function testInit({
addonStudies: [activeUninstalledStudy, activeInstalledStudy, inactiveStudy],
sendEventSpy,
[addonId, addonFile]
) {
installedWebExtension: { addonId },
}) {
await AddonStudies.init();
const newActiveStudy = await AddonStudies.get(
@ -198,30 +198,25 @@ decorate_task(
withInstalledWebExtensionSafe({ id: "installed1@example.com" }),
withInstalledWebExtension({ id: "installed2@example.com" }),
withStub(TelemetryEnvironment, "setExperimentActive"),
async function testInit(
studies,
[extensionId1],
[extensionId2],
setExperimentActiveStub
) {
async function testInit({ addonStudies, setExperimentActiveStub }) {
await AddonStudies.init();
Assert.deepEqual(
setExperimentActiveStub.args,
[
[
studies[0].slug,
studies[0].branch,
addonStudies[0].slug,
addonStudies[0].branch,
{
type: "normandy-addonstudy",
enrollmentId: studies[0].enrollmentId,
enrollmentId: addonStudies[0].enrollmentId,
},
],
[
studies[1].slug,
studies[1].branch,
addonStudies[1].slug,
addonStudies[1].branch,
{
type: "normandy-addonstudy",
enrollmentId: studies[1].enrollmentId,
enrollmentId: addonStudies[1].enrollmentId,
},
],
],
@ -241,10 +236,13 @@ decorate_task(
]),
withInstalledWebExtension(
{ id: "installed@example.com" },
/* expectUninstall: */ true
{ expectUninstall: true }
),
async function testInit([study], [id, addonFile]) {
const addon = await AddonManager.getAddonByID(id);
async function testInit({
addonStudies: [study],
installedWebExtension: { addonId },
}) {
const addon = await AddonManager.getAddonByID(addonId);
await addon.uninstall();
await TestUtils.topicObserved("shield-study-ended", (subject, message) => {
return message === `${study.recipeId}`;
@ -267,7 +265,9 @@ decorate_task(
NormandyTestUtils.factories.addonStudyFactory({ active: true }),
NormandyTestUtils.factories.branchedAddonStudyFactory(),
]),
async function testRemoveOldAddonStudies([noBranchStudy, branchedStudy]) {
async function testRemoveOldAddonStudies({
addonStudies: [noBranchStudy, branchedStudy],
}) {
// pre check, both studies are active
const preActiveIds = (await AddonStudies.getAllActive()).map(
addon => addon.recipeId

Просмотреть файл

@ -1,38 +0,0 @@
"use strict";
const { AddonTestUtils } = ChromeUtils.import(
"resource://testing-common/AddonTestUtils.jsm"
);
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
const { Addons } = ChromeUtils.import("resource://normandy/lib/Addons.jsm");
// Initialize test utils
AddonTestUtils.initMochitest(this);
const testInstallId = "testInstallUpdate@example.com";
decorate_task(
withInstalledWebExtension({ version: "1.0", id: testInstallId }),
withWebExtension({ version: "2.0", id: testInstallId }),
async function testInstallUpdate([id1, addonFile1], [id2, addonFile2]) {
// Fail to install the 2.0 add-on without updating enabled
const newAddonUrl = Services.io.newFileURI(addonFile2).spec;
await Assert.rejects(
Addons.install(newAddonUrl, { update: false }),
/updating is disabled/,
"install rejects when the study add-on is already installed and updating is disabled"
);
// Install the new add-on with updating enabled
const startupPromise = AddonTestUtils.promiseWebExtensionStartup(
testInstallId
);
await Addons.install(newAddonUrl, { update: true });
const addon = await startupPromise;
is(
addon.version,
"2.0",
"install can successfully update an already-installed addon when updating is enabled."
);
}
);

Просмотреть файл

@ -114,9 +114,9 @@ decorate_task(
);
// Test that per-recipe uptake telemetry is recorded
decorate_task(withStub(Uptake, "reportRecipe"), async function(
reportRecipeStub
) {
decorate_task(withStub(Uptake, "reportRecipe"), async function({
reportRecipeStub,
}) {
const action = new NoopAction();
const recipe = recipeFactory();
await action.processRecipe(recipe, BaseAction.suitability.FILTER_MATCH);
@ -128,9 +128,9 @@ decorate_task(withStub(Uptake, "reportRecipe"), async function(
});
// Finalize causes action telemetry to be recorded
decorate_task(withStub(Uptake, "reportAction"), async function(
reportActionStub
) {
decorate_task(withStub(Uptake, "reportAction"), async function({
reportActionStub,
}) {
const action = new NoopAction();
await action.finalize();
ok(
@ -145,9 +145,9 @@ decorate_task(withStub(Uptake, "reportAction"), async function(
});
// Recipes can't be run after finalize is called
decorate_task(withStub(Uptake, "reportRecipe"), async function(
reportRecipeStub
) {
decorate_task(withStub(Uptake, "reportRecipe"), async function({
reportRecipeStub,
}) {
const action = new NoopAction();
const recipe1 = recipeFactory();
const recipe2 = recipeFactory();
@ -172,7 +172,7 @@ decorate_task(withStub(Uptake, "reportRecipe"), async function(
decorate_task(
withStub(Uptake, "reportRecipe"),
withStub(Uptake, "reportAction"),
async function(reportRecipeStub, reportActionStub) {
async function({ reportRecipeStub, reportActionStub }) {
const recipe = recipeFactory();
const action = new FailPreExecutionAction();
is(
@ -233,7 +233,7 @@ decorate_task(
decorate_task(
withStub(Uptake, "reportRecipe"),
withStub(Uptake, "reportAction"),
async function(reportRecipeStub, reportActionStub) {
async function({ reportRecipeStub, reportActionStub }) {
const recipe = recipeFactory();
const action = new FailRunAction();
await action.processRecipe(recipe, BaseAction.suitability.FILTER_MATCH);
@ -269,7 +269,7 @@ decorate_task(
decorate_task(
withStub(Uptake, "reportRecipe"),
withStub(Uptake, "reportAction"),
async function(reportRecipeStub, reportActionStub) {
async function({ reportRecipeStub, reportActionStub }) {
const recipe = recipeFactory();
const action = new FailFinalizeAction();
await action.processRecipe(recipe, BaseAction.suitability.FILTER_MATCH);
@ -293,7 +293,7 @@ decorate_task(
decorate_task(
withStub(Uptake, "reportRecipe"),
withStub(Uptake, "reportAction"),
async function(reportRecipeStub, reportActionStub) {
async function({ reportRecipeStub, reportActionStub }) {
const recipe = recipeFactory();
const action = new NoopAction();

Просмотреть файл

@ -193,7 +193,10 @@ decorate_task(
branch: "b-test-branch",
}),
]),
async function testStudies([prefExperiment], [addonStudy]) {
async function testStudies({
prefExperiments: [prefExperiment],
addonStudies: [addonStudy],
}) {
Assert.deepEqual(
await ClientEnvironment.studies,
{

Просмотреть файл

@ -34,7 +34,7 @@ function withStubInits() {
withStub(PreferenceExperiments, "init"),
withStub(RecipeRunner, "init"),
withStub(TelemetryEvents, "init"),
() => testFunction()
testFunction
);
};
}
@ -135,7 +135,7 @@ decorate_task(
decorate_task(
withStub(Normandy, "finishInit"),
async function testStartupDelayed(finishInitStub) {
async function testStartupDelayed({ finishInitStub }) {
await Normandy.init();
ok(
!finishInitStub.called,
@ -153,12 +153,16 @@ decorate_task(
// During startup, preferences that are changed for experiments should
// be record by calling PreferenceExperiments.recordOriginalValues.
decorate_task(
withStub(PreferenceExperiments, "recordOriginalValues"),
withStub(PreferenceRollouts, "recordOriginalValues"),
async function testApplyStartupPrefs(
withStub(PreferenceExperiments, "recordOriginalValues", {
as: "experimentsRecordOriginalValuesStub",
}),
withStub(PreferenceRollouts, "recordOriginalValues", {
as: "rolloutsRecordOriginalValueStub",
}),
async function testApplyStartupPrefs({
experimentsRecordOriginalValuesStub,
rolloutsRecordOriginalValueStub
) {
rolloutsRecordOriginalValueStub,
}) {
const defaultBranch = Services.prefs.getDefaultBranch("");
defaultBranch.setBoolPref(experimentPref1, false);
@ -300,10 +304,10 @@ decorate_task(
]),
PreferenceRollouts.withTestMock(),
AddonRollouts.withTestMock(),
async function disablingTelemetryClearsEnrollmentIds(
[prefExperiment],
[addonStudy]
) {
async function disablingTelemetryClearsEnrollmentIds({
prefExperiments: [prefExperiment],
addonStudies: [addonStudy],
}) {
const prefRollout = {
slug: "test-rollout",
state: PreferenceRollouts.STATE_ACTIVE,

Просмотреть файл

@ -7,7 +7,7 @@ const { NormandyAddonManager } = ChromeUtils.import(
"resource://normandy/lib/NormandyAddonManager.jsm"
);
decorate_task(ensureAddonCleanup, async function download_and_install() {
decorate_task(ensureAddonCleanup(), async function download_and_install() {
const applyDeferred = PromiseUtils.defer();
const [addonId, addonVersion] = await NormandyAddonManager.downloadAndInstall(
@ -39,7 +39,7 @@ decorate_task(ensureAddonCleanup, async function download_and_install() {
await addon.uninstall();
});
decorate_task(ensureAddonCleanup, async function id_mismatch() {
decorate_task(ensureAddonCleanup(), async function id_mismatch() {
const applyDeferred = PromiseUtils.defer();
const undoDeferred = PromiseUtils.defer();
@ -89,7 +89,7 @@ decorate_task(ensureAddonCleanup, async function id_mismatch() {
ok(!addon, "add-on is not installed");
});
decorate_task(ensureAddonCleanup, async function version_mismatch() {
decorate_task(ensureAddonCleanup(), async function version_mismatch() {
const applyDeferred = PromiseUtils.defer();
const undoDeferred = PromiseUtils.defer();
@ -139,7 +139,7 @@ decorate_task(ensureAddonCleanup, async function version_mismatch() {
ok(!addon, "add-on is not installed");
});
decorate_task(ensureAddonCleanup, async function download_failure() {
decorate_task(ensureAddonCleanup(), async function download_failure() {
const applyDeferred = PromiseUtils.defer();
const undoDeferred = PromiseUtils.defer();

Просмотреть файл

@ -2,9 +2,9 @@ const { NormandyMigrations } = ChromeUtils.import(
"resource://normandy/NormandyMigrations.jsm"
);
decorate_task(withMockPreferences(), async function testApplyMigrations(
mockPreferences
) {
decorate_task(withMockPreferences(), async function testApplyMigrations({
mockPreferences,
}) {
const migrationsAppliedPref = "app.normandy.migrationsApplied";
mockPreferences.set(migrationsAppliedPref, 0);
@ -17,9 +17,9 @@ decorate_task(withMockPreferences(), async function testApplyMigrations(
);
});
decorate_task(withMockPreferences(), async function testPrefMigration(
mockPreferences
) {
decorate_task(withMockPreferences(), async function testPrefMigration({
mockPreferences,
}) {
const legacyPref = "extensions.shield-recipe-client.test";
const migratedPref = "app.normandy.test";
mockPreferences.set(legacyPref, 1);
@ -52,9 +52,9 @@ decorate_task(withMockPreferences(), async function testPrefMigration(
Services.prefs.clearUserPref(migratedPref);
});
decorate_task(withMockPreferences(), async function testMigration0(
mockPreferences
) {
decorate_task(withMockPreferences(), async function testMigration0({
mockPreferences,
}) {
const studiesEnabledPref = "app.shield.optoutstudies.enabled";
const healthReportUploadEnabledPref =
"datareporting.healthreport.uploadEnabled";

Просмотреть файл

@ -268,7 +268,7 @@ add_task(async function migration03KeepsActionName() {
// Test that migration 5 works as expected
decorate_task(
PreferenceExperiments.withMockExperiments([
withMockExperiments([
NormandyTestUtils.factories.preferenceStudyFactory({
actionName: "PreferenceExperimentAction",
expired: false,
@ -278,7 +278,7 @@ decorate_task(
expired: false,
}),
]),
async function migration05Works([expKeep, expExpire]) {
async function migration05Works({ prefExperiments: [expKeep, expExpire] }) {
// pre check
const activeSlugsBefore = (await PreferenceExperiments.getAllActive()).map(
e => e.slug
@ -315,7 +315,7 @@ decorate_task(
// clearAllExperimentStorage
decorate_task(
withMockExperiments([preferenceStudyFactory({ slug: "test" })]),
async function(experiments) {
async function({ prefExperiments }) {
ok(await PreferenceExperiments.has("test"), "Mock experiment is detected.");
await PreferenceExperiments.clearAllExperimentStorage();
ok(
@ -329,7 +329,7 @@ decorate_task(
decorate_task(
withMockExperiments([preferenceStudyFactory({ slug: "test" })]),
withSendEventSpy(),
async function(experiments, sendEventSpy) {
async function({ sendEventSpy }) {
await Assert.rejects(
PreferenceExperiments.start({
slug: "test",
@ -363,7 +363,7 @@ decorate_task(
}),
]),
withSendEventSpy(),
async function(experiments, sendEventSpy) {
async function({ sendEventSpy }) {
await Assert.rejects(
PreferenceExperiments.start({
slug: "different",
@ -398,10 +398,9 @@ decorate_task(
);
// start should throw if an invalid preferenceBranchType is given
decorate_task(withMockExperiments(), withSendEventSpy(), async function(
experiments,
sendEventSpy
) {
decorate_task(withMockExperiments(), withSendEventSpy(), async function({
sendEventSpy,
}) {
await Assert.rejects(
PreferenceExperiments.start({
slug: "test",
@ -431,12 +430,12 @@ decorate_task(
withMockPreferences(),
withStub(PreferenceExperiments, "startObserver"),
withSendEventSpy(),
async function testStart(
experiments,
async function testStart({
prefExperiments,
mockPreferences,
startObserverStub,
sendEventSpy
) {
sendEventSpy,
}) {
mockPreferences.set("fake.preference", "oldvalue", "default");
mockPreferences.set("fake.preference", "uservalue", "user");
mockPreferences.set("fake.preferenceinteger", 1, "default");
@ -534,7 +533,7 @@ decorate_task(
withMockExperiments(),
withMockPreferences(),
withStub(PreferenceExperiments, "startObserver"),
async function(experiments, mockPreferences, startObserver) {
async function({ mockPreferences, startObserverStub }) {
mockPreferences.set("fake.preference", "olddefaultvalue", "default");
mockPreferences.set("fake.preference", "oldvalue", "user");
@ -552,7 +551,7 @@ decorate_task(
};
await PreferenceExperiments.start(experiment);
ok(
startObserver.calledWith("test", experiment.preferences),
startObserverStub.calledWith("test", experiment.preferences),
"start registered an observer"
);
@ -595,10 +594,10 @@ decorate_task(
);
// start should detect if a new preference value type matches the previous value type
decorate_task(withMockPreferences(), withSendEventSpy(), async function(
decorate_task(withMockPreferences(), withSendEventSpy(), async function({
mockPreferences,
sendEventSpy
) {
sendEventSpy,
}) {
mockPreferences.set("fake.type_preference", "oldvalue");
await Assert.rejects(
@ -651,10 +650,7 @@ decorate_task(withMockExperiments(), async function() {
decorate_task(
withMockExperiments(),
withMockPreferences(),
async function testObserversCanObserveChanges(
mockExperiments,
mockPreferences
) {
async function testObserversCanObserveChanges({ mockPreferences }) {
const preferences = {
"fake.preferencestring": {
preferenceType: "string",
@ -744,7 +740,7 @@ decorate_task(
withMockExperiments(),
withMockPreferences(),
withStub(PreferenceExperiments, "stop", { returnValue: Promise.resolve() }),
async function(mockExperiments, mockPreferences, stopStub) {
async function({ mockPreferences, stopStub }) {
const preferenceInfo = {
"fake.preferencestring": {
preferenceType: "string",
@ -794,7 +790,7 @@ decorate_task(
withMockExperiments(),
withMockPreferences(),
withStub(PreferenceExperiments, "stop", { returnValue: Promise.resolve() }),
async function(mockExperiments, mockPreferences, stopStub) {
async function({ mockPreferences, stopStub }) {
mockPreferences.set("fake.preference", "startvalue");
mockPreferences.set("other.fake.preference", "startvalue");
@ -859,7 +855,7 @@ decorate_task(
withMockExperiments([
preferenceStudyFactory({ slug: "test", lastSeen: oldDate }),
]),
async function([experiment]) {
async function({ prefExperiments: [experiment] }) {
await PreferenceExperiments.markLastSeen("test");
Assert.notEqual(
experiment.lastSeen,
@ -870,10 +866,9 @@ decorate_task(
);
// stop should throw if an experiment with the given name doesn't exist
decorate_task(withMockExperiments(), withSendEventSpy(), async function(
experiments,
sendEventSpy
) {
decorate_task(withMockExperiments(), withSendEventSpy(), async function({
sendEventSpy,
}) {
await Assert.rejects(
PreferenceExperiments.stop("test"),
/could not find/i,
@ -896,7 +891,7 @@ decorate_task(
preferenceStudyFactory({ slug: "test", expired: true }),
]),
withSendEventSpy(),
async function(experiments, sendEventSpy) {
async function({ sendEventSpy }) {
await Assert.rejects(
PreferenceExperiments.stop("test"),
/already expired/,
@ -935,12 +930,7 @@ decorate_task(
withMockPreferences(),
withSpy(PreferenceExperiments, "stopObserver"),
withSendEventSpy(),
async function testStop(
experiments,
mockPreferences,
stopObserverSpy,
sendEventSpy
) {
async function testStop({ mockPreferences, stopObserverSpy, sendEventSpy }) {
// this assertion is mostly useful for --verify test runs, to make
// sure that tests clean up correctly.
ok(!Preferences.get("fake.preference"), "preference should start unset");
@ -1008,13 +998,12 @@ decorate_task(
withMockPreferences(),
withStub(PreferenceExperiments, "stopObserver"),
withStub(PreferenceExperiments, "hasObserver"),
async function testStopUserPrefs(
experiments,
async function testStopUserPrefs({
mockPreferences,
stopObserver,
hasObserver
) {
hasObserver.returns(true);
stopObserverStub,
hasObserverStub,
}) {
hasObserverStub.returns(true);
mockPreferences.set("fake.preference", "experimentvalue", "user");
PreferenceExperiments.startObserver("test", {
@ -1025,7 +1014,7 @@ decorate_task(
});
await PreferenceExperiments.stop("test");
ok(stopObserver.calledWith("test"), "stop removed an observer");
ok(stopObserverStub.calledWith("test"), "stop removed an observer");
const experiment = await PreferenceExperiments.get("test");
is(experiment.expired, true, "stop marked the experiment as expired");
is(
@ -1033,7 +1022,7 @@ decorate_task(
"oldvalue",
"stop reverted the preference to its previous value"
);
stopObserver.restore();
stopObserverStub.restore();
PreferenceExperiments.stopAllObservers();
}
);
@ -1055,7 +1044,8 @@ decorate_task(
}),
]),
withMockPreferences(),
async function(experiments, mockPreferences) {
withStub(PreferenceExperiments, "stopObserver"),
async function({ mockPreferences }) {
mockPreferences.set("fake.preference", "experimentvalue", "user");
await PreferenceExperiments.stop("test");
@ -1086,12 +1076,7 @@ decorate_task(
withMockPreferences(),
withStub(PreferenceExperiments, "stopObserver"),
withSendEventSpy(),
async function testStopReset(
experiments,
mockPreferences,
stopObserverStub,
sendEventSpy
) {
async function testStopReset({ mockPreferences, sendEventSpy }) {
mockPreferences.set("fake.preference", "customvalue", "default");
await PreferenceExperiments.stop("test", {
@ -1159,7 +1144,7 @@ decorate_task(withMockExperiments(), async function() {
// get
decorate_task(
withMockExperiments([preferenceStudyFactory({ slug: "test" })]),
async function(experiments) {
async function({ prefExperiments }) {
const experiment = await PreferenceExperiments.get("test");
is(experiment.slug, "test", "get fetches the correct experiment");
@ -1176,7 +1161,7 @@ decorate_task(
preferenceStudyFactory({ slug: "experiment1", disabled: false }),
preferenceStudyFactory({ slug: "experiment2", disabled: true }),
]),
async function testGetAll([experiment1, experiment2]) {
async function testGetAll({ prefExperiments: [experiment1, experiment2] }) {
const fetchedExperiments = await PreferenceExperiments.getAll();
is(
fetchedExperiments.length,
@ -1219,7 +1204,9 @@ decorate_task(
}),
]),
withMockPreferences(),
async function testGetAllActive([activeExperiment, inactiveExperiment]) {
async function testGetAllActive({
prefExperiments: [activeExperiment, inactiveExperiment],
}) {
let allActiveExperiments = await PreferenceExperiments.getAllActive();
Assert.deepEqual(
allActiveExperiments,
@ -1240,7 +1227,7 @@ decorate_task(
// has
decorate_task(
withMockExperiments([preferenceStudyFactory({ slug: "test" })]),
async function(experiments) {
async function() {
ok(
await PreferenceExperiments.has("test"),
"has returned true for a stored experiment"
@ -1270,18 +1257,17 @@ decorate_task(
withMockPreferences(),
withStub(TelemetryEnvironment, "setExperimentActive"),
withStub(PreferenceExperiments, "startObserver"),
async function testInit(
experiments,
async function testInit({
prefExperiments,
mockPreferences,
setActiveStub,
startObserverStub
) {
setExperimentActiveStub,
}) {
mockPreferences.set("fake.pref", "experiment value");
await PreferenceExperiments.init();
ok(
setActiveStub.calledWith("test", "branch", {
setExperimentActiveStub.calledWith("test", "branch", {
type: "normandy-exp",
enrollmentId: experiments[0].enrollmentId,
enrollmentId: prefExperiments[0].enrollmentId,
}),
"Experiment is registered by init"
);
@ -1306,16 +1292,11 @@ decorate_task(
withMockPreferences(),
withStub(TelemetryEnvironment, "setExperimentActive"),
withStub(PreferenceExperiments, "startObserver"),
async function testInit(
experiments,
mockPreferences,
setActiveStub,
startObserverStub
) {
async function testInit({ mockPreferences, setExperimentActiveStub }) {
mockPreferences.set("fake.pref", "experiment value");
await PreferenceExperiments.init();
ok(
setActiveStub.calledWith("test", "branch", {
setExperimentActiveStub.calledWith("test", "branch", {
type: "normandy-pref-test",
enrollmentId: sinon.match(NormandyTestUtils.isUuid),
}),
@ -1330,12 +1311,11 @@ decorate_task(
withStub(TelemetryEnvironment, "setExperimentActive"),
withStub(TelemetryEnvironment, "setExperimentInactive"),
withSendEventSpy(),
async function testStartAndStopTelemetry(
experiments,
setActiveStub,
setInactiveStub,
sendEventSpy
) {
async function testStartAndStopTelemetry({
setExperimentActiveStub,
setExperimentInactiveStub,
sendEventSpy,
}) {
let { enrollmentId } = await PreferenceExperiments.start({
slug: "test",
actionName: "SomeAction",
@ -1355,13 +1335,13 @@ decorate_task(
);
Assert.deepEqual(
setActiveStub.getCall(0).args,
setExperimentActiveStub.getCall(0).args,
["test", "branch", { type: "normandy-exp", enrollmentId }],
"Experiment is registered by start()"
);
await PreferenceExperiments.stop("test", { reason: "test-reason" });
Assert.deepEqual(
setInactiveStub.args,
setExperimentInactiveStub.args,
[["test"]],
"Experiment is unregistered by stop()"
);
@ -1398,12 +1378,10 @@ decorate_task(
withStub(TelemetryEnvironment, "setExperimentActive"),
withStub(TelemetryEnvironment, "setExperimentInactive"),
withSendEventSpy(),
async function testInitTelemetryExperimentType(
experiments,
setActiveStub,
setInactiveStub,
sendEventSpy
) {
async function testInitTelemetryExperimentType({
setExperimentActiveStub,
sendEventSpy,
}) {
const { enrollmentId } = await PreferenceExperiments.start({
slug: "test",
actionName: "SomeAction",
@ -1419,7 +1397,7 @@ decorate_task(
});
Assert.deepEqual(
setActiveStub.getCall(0).args,
setExperimentActiveStub.getCall(0).args,
["test", "branch", { type: "normandy-pref-test", enrollmentId }],
"start() should register the experiment with the provided type"
);
@ -1453,9 +1431,12 @@ decorate_task(
}),
]),
withStub(TelemetryEnvironment, "setExperimentActive"),
async function testInitTelemetryExpired(experiments, setActiveStub) {
async function testInitTelemetryExpired({ setExperimentActiveStub }) {
await PreferenceExperiments.init();
ok(!setActiveStub.called, "Expired experiment is not registered by init");
ok(
!setExperimentActiveStub.called,
"Expired experiment is not registered by init"
);
}
);
@ -1474,8 +1455,7 @@ decorate_task(
]),
withMockPreferences(),
withStub(PreferenceExperiments, "stop"),
async function testInitChanges(experiments, mockPreferences, stopStub) {
stopStub.returnValue = Promise.resolve();
async function testInitChanges({ mockPreferences, stopStub }) {
mockPreferences.set("fake.preference", "experiment value", "default");
mockPreferences.set("fake.preference", "changed value", "user");
await PreferenceExperiments.init();
@ -1519,12 +1499,11 @@ decorate_task(
withStub(PreferenceExperiments, "startObserver"),
withStub(PreferenceExperiments, "stop"),
withStub(CleanupManager, "addCleanupHandler"),
async function testInitRegistersObserver(
experiments,
async function testInitRegistersObserver({
mockPreferences,
startObserver,
stopStub
) {
startObserverStub,
stopStub,
}) {
stopStub.throws("Stop should not be called");
mockPreferences.set("fake.preference", "experiment value", "default");
is(
@ -1534,9 +1513,9 @@ decorate_task(
);
await PreferenceExperiments.init();
ok(startObserver.calledOnce, "init should register an observer");
ok(startObserverStub.calledOnce, "init should register an observer");
Assert.deepEqual(
startObserver.getCall(0).args,
startObserverStub.getCall(0).args,
[
"test",
{
@ -1584,7 +1563,7 @@ decorate_task(
},
}),
]),
async function testSaveStartupPrefs(experiments) {
async function testSaveStartupPrefs() {
Services.prefs.deleteBranch(startupPrefs);
Services.prefs.setBoolPref(`${startupPrefs}.fake.old`, true);
await PreferenceExperiments.saveStartupPrefs();
@ -1622,7 +1601,7 @@ decorate_task(
},
}),
]),
async function testSaveStartupPrefsError(experiments) {
async function testSaveStartupPrefsError() {
await Assert.rejects(
PreferenceExperiments.saveStartupPrefs(),
/invalid preference type/i,
@ -1655,7 +1634,7 @@ decorate_task(
},
}),
]),
async function testSaveStartupPrefsUserBranch(experiments) {
async function testSaveStartupPrefsUserBranch() {
Assert.deepEqual(
Services.prefs.getChildList(startupPrefs),
[],
@ -1693,7 +1672,7 @@ decorate_task(
withMockPreferences(),
withStub(PreferenceExperiments, "startObserver"),
withStub(PreferenceExperiments, "stopObserver"),
async function testDefaultBranchStop(mockExperiments, mockPreferences) {
async function testDefaultBranchStop({ mockPreferences }) {
const prefName = "fake.preference";
mockPreferences.set(prefName, "old version's value", "default");
@ -1747,7 +1726,7 @@ decorate_task(
withMockPreferences(),
withStub(PreferenceExperiments, "startObserver"),
withStub(PreferenceExperiments, "stopObserver"),
async function testDefaultBranchStop(mockExperiments, mockPreferences) {
async function testDefaultBranchStop({ mockPreferences }) {
const prefName = "fake.preference";
mockPreferences.set(prefName, "old version's value", "default");
@ -1808,12 +1787,7 @@ decorate_task(
withMockPreferences(),
withStub(PreferenceExperiments, "stopObserver"),
withSendEventSpy(),
async function testStopUnknownReason(
experiments,
mockPreferences,
stopObserverStub,
sendEventSpy
) {
async function testStopUnknownReason({ mockPreferences, sendEventSpy }) {
mockPreferences.set("fake.preference", "default value", "default");
await PreferenceExperiments.stop("test");
is(
@ -1849,12 +1823,7 @@ decorate_task(
withMockPreferences(),
withStub(PreferenceExperiments, "stopObserver"),
withSendEventSpy(),
async function testStopResetValue(
experiments,
mockPreferences,
stopObserverStub,
sendEventSpy
) {
async function testStopResetValue({ mockPreferences, sendEventSpy }) {
mockPreferences.set("fake.preference1", "default value", "default");
await PreferenceExperiments.stop("test1", { resetValue: true });
is(sendEventSpy.callCount, 1);
@ -1895,11 +1864,11 @@ decorate_task(
},
}),
]),
async function testPrefChangeEventTelemetry(
async function testPrefChangeEventTelemetry({
mockPreferences,
sendEventSpy,
mockExperiments
) {
prefExperiments,
}) {
ok(!Preferences.get("fake.preference"), "preference should start unset");
mockPreferences.set("fake.preference", "oldvalue", "default");
PreferenceExperiments.startObserver("test", {
@ -1929,7 +1898,7 @@ decorate_task(
didResetValue: "false",
reason: "user-preference-changed",
branch: "fakebranch",
enrollmentId: mockExperiments[0].enrollmentId,
enrollmentId: prefExperiments[0].enrollmentId,
changedPref: "fake.preference",
},
],

Просмотреть файл

@ -179,7 +179,7 @@ decorate_task(
decorate_task(
withSendEventSpy(),
PreferenceRollouts.withTestMock(),
async function testRecordOriginalValuesGraduates(sendEventSpy) {
async function testRecordOriginalValuesGraduates({ sendEventSpy }) {
await PreferenceRollouts.add({
slug: "test-rollout",
state: PreferenceRollouts.STATE_ACTIVE,
@ -231,7 +231,7 @@ decorate_task(
decorate_task(
withStub(TelemetryEnvironment, "setExperimentActive"),
PreferenceRollouts.withTestMock(),
async function testInitTelemetry(setExperimentActiveStub) {
async function testInitTelemetry({ setExperimentActiveStub }) {
await PreferenceRollouts.add({
slug: "test-rollout-active-1",
state: PreferenceRollouts.STATE_ACTIVE,
@ -288,7 +288,10 @@ decorate_task(
}),
],
}),
async function testInitGraduationSet(setExperimentActiveStub, sendEventSpy) {
async function testInitGraduationSet({
setExperimentActiveStub,
sendEventSpy,
}) {
await PreferenceRollouts.init();
const newRollout = await PreferenceRollouts.get("test-rollout");
Assert.equal(

Просмотреть файл

@ -131,10 +131,10 @@ decorate_task(
withStub(FilterExpressions, "eval"),
withStub(Uptake, "reportRecipe"),
withStub(NormandyApi, "verifyObjectSignature"),
async function test_getRecipeSuitability_canHandleExceptions(
async function test_getRecipeSuitability_canHandleExceptions({
evalStub,
reportRecipeStub
) {
reportRecipeStub,
}) {
evalStub.throws("this filter was broken somehow");
const someRecipe = {
id: "1",
@ -158,10 +158,10 @@ decorate_task(
withSpy(FilterExpressions, "eval"),
withStub(RecipeRunner, "getCapabilities"),
withStub(NormandyApi, "verifyObjectSignature"),
async function test_getRecipeSuitability_checksCapabilities(
async function test_getRecipeSuitability_checksCapabilities({
evalSpy,
getCapabilitiesStub
) {
getCapabilitiesStub,
}) {
getCapabilitiesStub.returns(new Set(["test-capability"]));
is(
@ -211,8 +211,11 @@ decorate_task(
decorate_task(
withMockNormandyApi(),
withStub(ClientEnvironment, "getClientClassification"),
async function testClientClassificationCache(api, getStub) {
getStub.returns(Promise.resolve(false));
async function testClientClassificationCache({
mockNormandyApi,
getClientClassificationStub,
}) {
getClientClassificationStub.returns(Promise.resolve(false));
await SpecialPowers.pushPrefEnv({
set: [["app.normandy.api_url", "https://example.com/selfsupport-dummy"]],
@ -222,18 +225,32 @@ decorate_task(
await SpecialPowers.pushPrefEnv({
set: [["app.normandy.experiments.lazy_classify", false]],
});
ok(!getStub.called, "getClientClassification hasn't been called");
ok(
!getClientClassificationStub.called,
"getClientClassification hasn't been called"
);
await RecipeRunner.run();
ok(getStub.called, "getClientClassification was called eagerly");
ok(
getClientClassificationStub.called,
"getClientClassification was called eagerly"
);
// When the experiment pref is true, do not eagerly call getClientClassification.
await SpecialPowers.pushPrefEnv({
set: [["app.normandy.experiments.lazy_classify", true]],
});
getStub.reset();
ok(!getStub.called, "getClientClassification hasn't been called");
getClientClassificationStub.reset();
ok(
!getClientClassificationStub.called,
"getClientClassification hasn't been called"
);
await RecipeRunner.run();
ok(!getStub.called, "getClientClassification was not called eagerly");
ok(
!getClientClassificationStub.called,
"getClientClassification was not called eagerly"
);
}
);
@ -241,7 +258,7 @@ decorate_task(
withStub(Uptake, "reportRunner"),
withStub(ActionsManager.prototype, "finalize"),
NormandyTestUtils.withMockRecipeCollection([]),
async function testRunEvents(reportRunnerStub, finalizeStub) {
async function testRunEvents() {
const startPromise = TestUtils.topicObserved("recipe-runner:start");
const endPromise = TestUtils.topicObserved("recipe-runner:end");
@ -258,8 +275,8 @@ decorate_task(
withStub(RecipeRunner, "getCapabilities"),
withStub(NormandyApi, "verifyObjectSignature"),
NormandyTestUtils.withMockRecipeCollection([{ id: 1 }]),
async function test_run_includesCapabilities(getCapabilitiesStub) {
getCapabilitiesStub.returns(new Set(["test-capabilitiy"]));
async function test_run_includesCapabilities({ getCapabilitiesStub }) {
getCapabilitiesStub.returns(new Set(["test-capability"]));
await RecipeRunner.run();
ok(getCapabilitiesStub.called, "getCapabilities should be called");
}
@ -270,12 +287,12 @@ decorate_task(
withStub(ActionsManager.prototype, "processRecipe"),
withStub(ActionsManager.prototype, "finalize"),
withStub(Uptake, "reportRecipe"),
async function testReadFromRemoteSettings(
async function testReadFromRemoteSettings({
verifyObjectSignatureStub,
processRecipeStub,
finalizeStub,
reportRecipeStub
) {
reportRecipeStub,
}) {
const matchRecipe = {
id: 1,
name: "match",
@ -354,11 +371,10 @@ decorate_task(
withStub(NormandyApi, "verifyObjectSignature"),
withStub(ActionsManager.prototype, "processRecipe"),
withStub(RecipeRunner, "getCapabilities"),
async function testReadFromRemoteSettings(
verifyObjectSignatureStub,
processRecipe,
getCapabilitiesStub
) {
async function testReadFromRemoteSettings({
processRecipeStub,
getCapabilitiesStub,
}) {
getCapabilitiesStub.returns(new Set(["compatible"]));
const compatibleRecipe = {
name: "match",
@ -396,7 +412,7 @@ decorate_task(
await RecipeRunner.run();
Assert.deepEqual(
processRecipe.args,
processRecipeStub.args,
[
[compatibleRecipe, BaseAction.suitability.FILTER_MATCH],
[incompatibleRecipe, BaseAction.suitability.CAPABILITES_MISMATCH],
@ -411,12 +427,12 @@ decorate_task(
withStub(NormandyApi, "verifyObjectSignature"),
withStub(Uptake, "reportRecipe"),
NormandyTestUtils.withMockRecipeCollection(),
async function testBadSignatureFromRemoteSettings(
async function testBadSignatureFromRemoteSettings({
processRecipeStub,
verifyObjectSignatureStub,
reportRecipeStub,
mockRecipeCollection
) {
mockRecipeCollection,
}) {
verifyObjectSignatureStub.throws(new Error("fake signature error"));
const badSigRecipe = {
id: 1,
@ -450,7 +466,7 @@ decorate_task(
}),
withStub(RecipeRunner, "run"),
withStub(RecipeRunner, "registerTimer"),
async function testInit(runStub, registerTimerStub) {
async function testInit({ runStub, registerTimerStub }) {
await RecipeRunner.init();
ok(
!runStub.called,
@ -471,7 +487,7 @@ decorate_task(
withStub(RecipeRunner, "run"),
withStub(RecipeRunner, "registerTimer"),
withStub(RecipeRunner._remoteSettingsClientForTesting, "sync"),
async function testInitDevMode(runStub, registerTimerStub, syncStub) {
async function testInitDevMode({ runStub, registerTimerStub, syncStub }) {
await RecipeRunner.init();
Assert.deepEqual(
runStub.args,
@ -498,7 +514,11 @@ decorate_task(
withStub(RecipeRunner, "run"),
withStub(RecipeRunner, "registerTimer"),
withStub(RecipeRunner, "watchPrefs"),
async function testInitFirstRun(runStub, registerTimerStub, watchPrefsStub) {
async function testInitFirstRun({
runStub,
registerTimerStub,
watchPrefsStub,
}) {
await RecipeRunner.init();
Assert.deepEqual(
runStub.args,
@ -536,12 +556,7 @@ decorate_task(
withStub(RecipeRunner, "disable"),
withStub(CleanupManager, "addCleanupHandler"),
async function testPrefWatching(
runStub,
enableStub,
disableStub,
addCleanupHandlerStub
) {
async function testPrefWatching({ runStub, enableStub, disableStub }) {
await RecipeRunner.init();
is(enableStub.callCount, 1, "Enable should be called initially");
is(disableStub.callCount, 0, "Disable should not be called initially");
@ -594,8 +609,7 @@ decorate_task(
decorate_task(
withStub(RecipeRunner, "registerTimer"),
withStub(RecipeRunner, "unregisterTimer"),
async function testPrefWatching(registerTimerStub, unregisterTimerStub) {
async function testPrefWatching({ registerTimerStub }) {
const originalEnabled = RecipeRunner.enabled;
try {
@ -621,7 +635,7 @@ decorate_task(
set: [["app.normandy.onsync_skew_sec", 0]],
}),
withStub(RecipeRunner, "run"),
async function testRunOnSyncRemoteSettings(runStub) {
async function testRunOnSyncRemoteSettings({ runStub }) {
const rsClient = RecipeRunner._remoteSettingsClientForTesting;
await RecipeRunner.init();
ok(
@ -661,7 +675,7 @@ decorate_task(
],
}),
withStub(RecipeRunner, "run"),
async function testOnSyncRunDelayed(runStub) {
async function testOnSyncRunDelayed({ runStub }) {
ok(
!RecipeRunner._syncSkewTimeout,
"precondition: No timer should be active"
@ -676,8 +690,8 @@ decorate_task(
decorate_task(
withStub(RecipeRunner._remoteSettingsClientForTesting, "get"),
async function testRunCanRunOnlyOnce(getRecipesStub) {
getRecipesStub.returns(
async function testRunCanRunOnlyOnce({ getStub }) {
getStub.returns(
// eslint-disable-next-line mozilla/no-arbitrary-setTimeout
new Promise(resolve => setTimeout(() => resolve([]), 10))
);
@ -685,7 +699,7 @@ decorate_task(
// Run 2 in parallel.
await Promise.all([RecipeRunner.run(), RecipeRunner.run()]);
is(getRecipesStub.callCount, 1, "run() is no-op if already running");
is(getStub.callCount, 1, "run() is no-op if already running");
}
);
@ -702,7 +716,7 @@ decorate_task(
withSpy(RecipeRunner, "run"),
withStub(ActionsManager.prototype, "finalize"),
withStub(Uptake, "reportRunner"),
async function testSyncDelaysTimer(runSpy, finalizeStub, reportRecipeStub) {
async function testSyncDelaysTimer({ runSpy }) {
// Mark any existing timer as having run just now.
for (const { value } of Services.catMan.enumerateCategory("update-timer")) {
const timerID = value.split(",")[2];
@ -787,7 +801,7 @@ decorate_task(
withStub(Uptake, "reportRunner"),
withStub(ActionsManager.prototype, "finalize"),
NormandyTestUtils.withMockRecipeCollection([]),
async function testRunEvents(reportRunnerStub, finalizeStub) {
async function testRunEvents({ reportRunnerStub, finalizeStub }) {
const observer = sinon.spy();
Services.obs.addObserver(observer, "recipe-runner:start");

Просмотреть файл

@ -25,10 +25,10 @@ decorate_task(
addonStudyFactory({ active: true }),
addonStudyFactory({ active: true }),
]),
async function testDisableStudiesWhenOptOutDisabled(
async function testDisableStudiesWhenOptOutDisabled({
mockPreferences,
[study1, study2]
) {
addonStudies: [study1, study2],
}) {
mockPreferences.set(OPT_OUT_STUDIES_ENABLED_PREF, true);
const observers = [
studyEndObserved(study1.recipeId),
@ -53,11 +53,11 @@ decorate_task(
preferenceStudyFactory({ active: true }),
]),
withStub(PreferenceExperiments, "stop"),
async function testDisableExperimentsWhenOptOutDisabled(
async function testDisableExperimentsWhenOptOutDisabled({
mockPreferences,
[study1, study2],
stopStub
) {
prefExperiments: [study1, study2],
stopStub,
}) {
mockPreferences.set(OPT_OUT_STUDIES_ENABLED_PREF, true);
let stopArgs = [];
let stoppedBoth = new Promise(resolve => {

Просмотреть файл

@ -4,9 +4,9 @@ const OPT_OUT_PREF = "app.shield.optoutstudies.enabled";
function withPrivacyPrefs() {
return function(testFunc) {
return async (...args) =>
return async args =>
BrowserTestUtils.withNewTab("about:preferences#privacy", async browser =>
testFunc(...args, browser)
testFunc({ ...args, browser })
);
};
}
@ -16,7 +16,7 @@ decorate_task(
set: [[OPT_OUT_PREF, true]],
}),
withPrivacyPrefs(),
async function testCheckedOnLoad(browser) {
async function testCheckedOnLoad({ browser }) {
const checkbox = browser.contentDocument.getElementById(
"optOutStudiesEnabled"
);
@ -32,7 +32,7 @@ decorate_task(
set: [[OPT_OUT_PREF, false]],
}),
withPrivacyPrefs(),
async function testUncheckedOnLoad(browser) {
async function testUncheckedOnLoad({ browser }) {
const checkbox = browser.contentDocument.getElementById(
"optOutStudiesEnabled"
);
@ -48,7 +48,7 @@ decorate_task(
set: [[OPT_OUT_PREF, true]],
}),
withPrivacyPrefs(),
async function testCheckboxes(browser) {
async function testCheckboxes({ browser }) {
const optOutCheckbox = browser.contentDocument.getElementById(
"optOutStudiesEnabled"
);
@ -71,7 +71,7 @@ decorate_task(
set: [[OPT_OUT_PREF, true]],
}),
withPrivacyPrefs(),
async function testPrefWatchers(browser) {
async function testPrefWatchers({ browser }) {
const optOutCheckbox = browser.contentDocument.getElementById(
"optOutStudiesEnabled"
);
@ -89,7 +89,9 @@ decorate_task(
}
);
decorate_task(withPrivacyPrefs(), async function testViewStudiesLink(browser) {
decorate_task(withPrivacyPrefs(), async function testViewStudiesLink({
browser,
}) {
browser.contentDocument.getElementById("viewShieldStudies").click();
await BrowserTestUtils.waitForLocationChange(gBrowser);

Просмотреть файл

@ -23,17 +23,17 @@ const {
function withAboutStudies() {
return function(testFunc) {
return async (...args) =>
return async args =>
BrowserTestUtils.withNewTab("about:studies", async browser =>
testFunc(...args, browser)
testFunc({ ...args, browser })
);
};
}
// Test that the code renders at all
decorate_task(withAboutStudies(), async function testAboutStudiesWorks(
browser
) {
decorate_task(withAboutStudies(), async function testAboutStudiesWorks({
browser,
}) {
const appFound = await SpecialPowers.spawn(
browser,
[],
@ -48,7 +48,7 @@ decorate_task(
set: [["app.normandy.shieldLearnMoreUrl", "http://test/%OS%/"]],
}),
withAboutStudies(),
async function testLearnMore(browser) {
async function testLearnMore({ browser }) {
SpecialPowers.spawn(browser, [], async () => {
const doc = content.document;
await ContentTaskUtils.waitForCondition(() =>
@ -69,9 +69,9 @@ decorate_task(
);
// Test that jumping to preferences worked as expected
decorate_task(withAboutStudies(), async function testUpdatePreferences(
browser
) {
decorate_task(withAboutStudies(), async function testUpdatePreferences({
browser,
}) {
let loadPromise = BrowserTestUtils.firstBrowserLoaded(window);
// We have to use gBrowser instead of browser in most spots since we're
@ -147,11 +147,11 @@ decorate_task(
}),
]),
withAboutStudies(),
async function testStudyListing(addonStudies, prefStudies, browser) {
async function testStudyListing({ addonStudies, prefExperiments, browser }) {
await SpecialPowers.spawn(
browser,
[{ addonStudies, prefStudies }],
async ({ addonStudies, prefStudies }) => {
[{ addonStudies, prefExperiments }],
async ({ addonStudies, prefExperiments }) => {
const doc = content.document;
function getStudyRow(docElem, slug) {
@ -171,16 +171,16 @@ decorate_task(
Assert.deepEqual(
activeNames,
[
prefStudies[2].slug,
prefExperiments[2].slug,
addonStudies[0].slug,
prefStudies[0].slug,
prefExperiments[0].slug,
addonStudies[2].slug,
],
"Active studies are grouped by enabled status, and sorted by date"
);
Assert.deepEqual(
inactiveNames,
[prefStudies[1].slug, addonStudies[1].slug],
[prefExperiments[1].slug, addonStudies[1].slug],
"Inactive studies are grouped by enabled status, and sorted by date"
);
@ -219,8 +219,8 @@ decorate_task(
"Inactive studies do not show a remove button"
);
const activePrefStudy = getStudyRow(doc, prefStudies[0].slug);
const preferenceName = Object.keys(prefStudies[0].preferences)[0];
const activePrefStudy = getStudyRow(doc, prefExperiments[0].slug);
const preferenceName = Object.keys(prefExperiments[0].preferences)[0];
ok(
activePrefStudy
.querySelector(".study-description")
@ -237,7 +237,7 @@ decorate_task(
"Active studies show a remove button"
);
const inactivePrefStudy = getStudyRow(doc, prefStudies[1].slug);
const inactivePrefStudy = getStudyRow(doc, prefExperiments[1].slug);
is(
inactivePrefStudy.querySelector(".study-status").textContent,
"Complete",
@ -259,10 +259,10 @@ decorate_task(
activePrefStudy.querySelector(".remove-button").click();
await ContentTaskUtils.waitForCondition(() =>
getStudyRow(doc, prefStudies[0].slug).matches(".study.disabled")
getStudyRow(doc, prefExperiments[0].slug).matches(".study.disabled")
);
ok(
getStudyRow(doc, prefStudies[0].slug).matches(".study.disabled"),
getStudyRow(doc, prefExperiments[0].slug).matches(".study.disabled"),
"Clicking the remove button updates the UI to show that the study has been disabled."
);
}
@ -275,7 +275,7 @@ decorate_task(
);
const updatedPrefStudy = await PreferenceExperiments.get(
prefStudies[0].slug
prefExperiments[0].slug
);
ok(
updatedPrefStudy.expired,
@ -288,7 +288,7 @@ decorate_task(
decorate_task(
AddonStudies.withStudies([]),
withAboutStudies(),
async function testStudyListingNoStudies(studies, browser) {
async function testStudyListingNoStudies({ browser }) {
await SpecialPowers.spawn(browser, [], async () => {
const doc = content.document;
await ContentTaskUtils.waitForCondition(
@ -325,11 +325,7 @@ decorate_task(
expired: true,
}),
]),
async function testStudyListingDisabled(
browser,
addonStudies,
preferenceStudies
) {
async function testStudyListingDisabled({ browser }) {
try {
RecipeRunner.disable();
@ -364,7 +360,7 @@ decorate_task(
withAboutStudies(),
AddonStudies.withStudies([]),
PreferenceExperiments.withMockExperiments([]),
async function testStudyListingStudiesOptOut(browser) {
async function testStudyListingStudiesOptOut({ browser }) {
RecipeRunner.checkPrefs();
ok(
RecipeRunner.enabled,
@ -408,7 +404,11 @@ decorate_task(
}),
]),
withAboutStudies(),
async function testStudyListing([addonStudy], [prefStudy], browser) {
async function testStudyListing({
addonStudies: [addonStudy],
prefExperiments: [prefStudy],
browser,
}) {
// The content page has already loaded. Disabling the studies here shouldn't
// affect it, since it doesn't live-update.
await AddonStudies.markAsEnded(addonStudy, "disabled-automatically-test");
@ -504,7 +504,11 @@ decorate_task(
}),
]),
withAboutStudies(),
async function testOtherTabsUpdated([addonStudy], [prefStudy], browser) {
async function testOtherTabsUpdated({
addonStudies: [addonStudy],
prefExperiments: [prefStudy],
browser,
}) {
// Ensure that both our studies are active in the current tab.
await SpecialPowers.spawn(
browser,

Просмотреть файл

@ -22,15 +22,15 @@ const { NormandyTestUtils } = ChromeUtils.import(
// Test that a simple recipe unenrolls as expected
decorate_task(
AddonRollouts.withTestMock(),
ensureAddonCleanup,
ensureAddonCleanup(),
withMockNormandyApi(),
withStub(TelemetryEnvironment, "setExperimentInactive"),
withSendEventSpy(),
async function simple_recipe_unenrollment(
mockApi,
async function simple_recipe_unenrollment({
mockNormandyApi,
setExperimentInactiveStub,
sendEventSpy
) {
sendEventSpy,
}) {
const rolloutRecipe = {
id: 1,
arguments: {
@ -38,7 +38,7 @@ decorate_task(
extensionApiId: 1,
},
};
mockApi.extensionDetails = {
mockNormandyApi.extensionDetails = {
[rolloutRecipe.arguments.extensionApiId]: extensionDetailsFactory({
id: rolloutRecipe.arguments.extensionApiId,
}),
@ -117,10 +117,10 @@ decorate_task(
// Add-on already uninstalled
decorate_task(
AddonRollouts.withTestMock(),
ensureAddonCleanup,
ensureAddonCleanup(),
withMockNormandyApi(),
withSendEventSpy(),
async function addon_already_uninstalled(mockApi, sendEventSpy) {
async function addon_already_uninstalled({ mockNormandyApi, sendEventSpy }) {
const rolloutRecipe = {
id: 1,
arguments: {
@ -128,7 +128,7 @@ decorate_task(
extensionApiId: 1,
},
};
mockApi.extensionDetails = {
mockNormandyApi.extensionDetails = {
[rolloutRecipe.arguments.extensionApiId]: extensionDetailsFactory({
id: rolloutRecipe.arguments.extensionApiId,
}),
@ -200,10 +200,10 @@ decorate_task(
// Already rolled back, do nothing
decorate_task(
AddonRollouts.withTestMock(),
ensureAddonCleanup,
ensureAddonCleanup(),
withMockNormandyApi(),
withSendEventSpy(),
async function already_rolled_back(mockApi, sendEventSpy) {
async function already_rolled_back({ sendEventSpy }) {
const rollout = {
recipeId: 1,
slug: "test-rollout",

Просмотреть файл

@ -19,15 +19,15 @@ const { NormandyTestUtils } = ChromeUtils.import(
// Test that a simple recipe enrolls as expected
decorate_task(
AddonRollouts.withTestMock(),
ensureAddonCleanup,
ensureAddonCleanup(),
withMockNormandyApi(),
withStub(TelemetryEnvironment, "setExperimentActive"),
withSendEventSpy(),
async function simple_recipe_enrollment(
mockApi,
async function simple_recipe_enrollment({
mockNormandyApi,
setExperimentActiveStub,
sendEventSpy
) {
sendEventSpy,
}) {
const recipe = {
id: 1,
arguments: {
@ -35,7 +35,7 @@ decorate_task(
extensionApiId: 1,
},
};
mockApi.extensionDetails = {
mockNormandyApi.extensionDetails = {
[recipe.arguments.extensionApiId]: extensionDetailsFactory({
id: recipe.arguments.extensionApiId,
}),
@ -98,10 +98,10 @@ decorate_task(
// Test that a rollout can update the addon
decorate_task(
AddonRollouts.withTestMock(),
ensureAddonCleanup,
ensureAddonCleanup(),
withMockNormandyApi(),
withSendEventSpy(),
async function update_rollout(mockApi, sendEventSpy) {
async function update_rollout({ mockNormandyApi, sendEventSpy }) {
// first enrollment
const recipe = {
id: 1,
@ -110,7 +110,7 @@ decorate_task(
extensionApiId: 1,
},
};
mockApi.extensionDetails = {
mockNormandyApi.extensionDetails = {
[recipe.arguments.extensionApiId]: extensionDetailsFactory({
id: recipe.arguments.extensionApiId,
}),
@ -190,10 +190,10 @@ decorate_task(
// Re-running a recipe does nothing
decorate_task(
AddonRollouts.withTestMock(),
ensureAddonCleanup,
ensureAddonCleanup(),
withMockNormandyApi(),
withSendEventSpy(),
async function rerun_recipe(mockApi, sendEventSpy) {
async function rerun_recipe({ mockNormandyApi, sendEventSpy }) {
const recipe = {
id: 1,
arguments: {
@ -201,7 +201,7 @@ decorate_task(
extensionApiId: 1,
},
};
mockApi.extensionDetails = {
mockNormandyApi.extensionDetails = {
[recipe.arguments.extensionApiId]: extensionDetailsFactory({
id: recipe.arguments.extensionApiId,
}),
@ -266,10 +266,10 @@ decorate_task(
// Conflicting rollouts
decorate_task(
AddonRollouts.withTestMock(),
ensureAddonCleanup,
ensureAddonCleanup(),
withMockNormandyApi(),
withSendEventSpy(),
async function conflicting_rollout(mockApi, sendEventSpy) {
async function conflicting_rollout({ mockNormandyApi, sendEventSpy }) {
const recipe = {
id: 1,
arguments: {
@ -277,7 +277,7 @@ decorate_task(
extensionApiId: 1,
},
};
mockApi.extensionDetails = {
mockNormandyApi.extensionDetails = {
[recipe.arguments.extensionApiId]: extensionDetailsFactory({
id: recipe.arguments.extensionApiId,
}),
@ -362,10 +362,13 @@ decorate_task(
// Add-on ID changed
decorate_task(
AddonRollouts.withTestMock(),
ensureAddonCleanup,
ensureAddonCleanup(),
withMockNormandyApi(),
withSendEventSpy(),
async function enroll_failed_addon_id_changed(mockApi, sendEventSpy) {
async function enroll_failed_addon_id_changed({
mockNormandyApi,
sendEventSpy,
}) {
const recipe = {
id: 1,
arguments: {
@ -373,7 +376,7 @@ decorate_task(
extensionApiId: 1,
},
};
mockApi.extensionDetails = {
mockNormandyApi.extensionDetails = {
[recipe.arguments.extensionApiId]: extensionDetailsFactory({
id: recipe.arguments.extensionApiId,
}),
@ -454,10 +457,13 @@ decorate_task(
// Add-on upgrade required
decorate_task(
AddonRollouts.withTestMock(),
ensureAddonCleanup,
ensureAddonCleanup(),
withMockNormandyApi(),
withSendEventSpy(),
async function enroll_failed_upgrade_required(mockApi, sendEventSpy) {
async function enroll_failed_upgrade_required({
mockNormandyApi,
sendEventSpy,
}) {
const recipe = {
id: 1,
arguments: {
@ -465,7 +471,7 @@ decorate_task(
extensionApiId: 1,
},
};
mockApi.extensionDetails = {
mockNormandyApi.extensionDetails = {
[recipe.arguments.extensionApiId]: extensionDetailsFactory({
id: recipe.arguments.extensionApiId,
xpi: FIXTURE_ADDON_DETAILS["normandydriver-a-2.0"].url,

Просмотреть файл

@ -76,19 +76,18 @@ function recipeFromStudy(study, overrides = {}) {
// if recipe is unchanged
decorate_task(
withStudiesEnabled(),
ensureAddonCleanup,
ensureAddonCleanup(),
withMockNormandyApi(),
AddonStudies.withStudies([branchedAddonStudyFactory()]),
withSendEventSpy(),
withInstalledWebExtensionSafe({ id: FIXTURE_ADDON_ID, version: "1.0" }),
async function enrollTwiceFail(
mockApi,
[study],
async function enrollTwiceFail({
mockNormandyApi,
addonStudies: [study],
sendEventSpy,
installedAddon
) {
}) {
const recipe = recipeFromStudy(study);
mockApi.extensionDetails = {
mockNormandyApi.extensionDetails = {
[study.extensionApiId]: extensionDetailsFactory({
id: study.extensionApiId,
extension_id: study.addonId,
@ -110,17 +109,17 @@ decorate_task(
// error is correctly reported.
decorate_task(
withStudiesEnabled(),
ensureAddonCleanup,
ensureAddonCleanup(),
withMockNormandyApi(),
withSendEventSpy(),
AddonStudies.withStudies(),
async function enrollDownloadFail(mockApi, sendEventSpy) {
async function enrollDownloadFail({ mockNormandyApi, sendEventSpy }) {
const recipe = branchedAddonStudyRecipeFactory({
arguments: {
branches: [{ slug: "missing", ratio: 1, extensionApiId: 404 }],
},
});
mockApi.extensionDetails = {
mockNormandyApi.extensionDetails = {
[recipe.arguments.branches[0].extensionApiId]: extensionDetailsFactory({
id: recipe.arguments.branches[0].extensionApiId,
xpi: "https://example.com/404.xpi",
@ -151,13 +150,13 @@ decorate_task(
// Ensure that the database is clean and error correctly reported if hash check fails
decorate_task(
withStudiesEnabled(),
ensureAddonCleanup,
ensureAddonCleanup(),
withMockNormandyApi(),
withSendEventSpy(),
AddonStudies.withStudies(),
async function enrollHashCheckFails(mockApi, sendEventSpy) {
async function enrollHashCheckFails({ mockNormandyApi, sendEventSpy }) {
const recipe = branchedAddonStudyRecipeFactory();
mockApi.extensionDetails = {
mockNormandyApi.extensionDetails = {
[recipe.arguments.branches[0].extensionApiId]: extensionDetailsFactory({
id: recipe.arguments.branches[0].extensionApiId,
xpi: FIXTURE_ADDON_DETAILS["normandydriver-a-1.0"].url,
@ -189,13 +188,16 @@ decorate_task(
// Ensure that the database is clean and error correctly reported if there is a metadata mismatch
decorate_task(
withStudiesEnabled(),
ensureAddonCleanup,
ensureAddonCleanup(),
withMockNormandyApi(),
withSendEventSpy(),
AddonStudies.withStudies(),
async function enrollFailsMetadataMismatch(mockApi, sendEventSpy) {
async function enrollFailsMetadataMismatch({
mockNormandyApi,
sendEventSpy,
}) {
const recipe = branchedAddonStudyRecipeFactory();
mockApi.extensionDetails = {
mockNormandyApi.extensionDetails = {
[recipe.arguments.branches[0].extensionApiId]: extensionDetailsFactory({
id: recipe.arguments.branches[0].extensionApiId,
version: "1.5",
@ -227,25 +229,25 @@ decorate_task(
// Test that in the case of a study add-on conflicting with a non-study add-on, the study does not enroll
decorate_task(
withStudiesEnabled(),
ensureAddonCleanup,
ensureAddonCleanup(),
withMockNormandyApi(),
withSendEventSpy(),
withInstalledWebExtensionSafe({ version: "0.1", id: FIXTURE_ADDON_ID }),
AddonStudies.withStudies(),
async function conflictingEnrollment(
mockApi,
async function conflictingEnrollment({
mockNormandyApi,
sendEventSpy,
[installedAddonId, installedAddonFile]
) {
installedWebExtensionSafe: { addonId },
}) {
is(
installedAddonId,
addonId,
FIXTURE_ADDON_ID,
"Generated, installed add-on should have the same ID as the fixture"
);
const recipe = branchedAddonStudyRecipeFactory({
arguments: { slug: "conflicting" },
});
mockApi.extensionDetails = {
mockNormandyApi.extensionDetails = {
[recipe.arguments.branches[0].extensionApiId]: extensionDetailsFactory({
id: recipe.arguments.extensionApiId,
addonUrl: FIXTURE_ADDON_DETAILS["normandydriver-a-1.0"].url,
@ -278,7 +280,7 @@ decorate_task(
// Test a successful update
decorate_task(
withStudiesEnabled(),
ensureAddonCleanup,
ensureAddonCleanup(),
withMockNormandyApi(),
AddonStudies.withStudies([
branchedAddonStudyFactory({
@ -290,12 +292,11 @@ decorate_task(
]),
withSendEventSpy(),
withInstalledWebExtensionSafe({ id: FIXTURE_ADDON_ID, version: "1.0" }),
async function successfulUpdate(
mockApi,
[study],
async function successfulUpdate({
mockNormandyApi,
addonStudies: [study],
sendEventSpy,
installedAddon
) {
}) {
const addonUrl = FIXTURE_ADDON_DETAILS["normandydriver-a-2.0"].url;
const recipe = recipeFromStudy(study, {
arguments: {
@ -305,7 +306,7 @@ decorate_task(
},
});
const hash = FIXTURE_ADDON_DETAILS["normandydriver-a-2.0"].hash;
mockApi.extensionDetails = {
mockNormandyApi.extensionDetails = {
[recipe.arguments.branches[0].extensionApiId]: extensionDetailsFactory({
id: recipe.arguments.branches[0].extensionApiId,
extension_id: FIXTURE_ADDON_ID,
@ -356,7 +357,7 @@ decorate_task(
// Test update fails when addon ID does not match
decorate_task(
withStudiesEnabled(),
ensureAddonCleanup,
ensureAddonCleanup(),
withMockNormandyApi(),
AddonStudies.withStudies([
branchedAddonStudyFactory({
@ -368,14 +369,13 @@ decorate_task(
]),
withSendEventSpy(),
withInstalledWebExtensionSafe({ id: FIXTURE_ADDON_ID, version: "0.1" }),
async function updateFailsAddonIdMismatch(
mockApi,
[study],
async function updateFailsAddonIdMismatch({
mockNormandyApi,
addonStudies: [study],
sendEventSpy,
installedAddon
) {
}) {
const recipe = recipeFromStudy(study);
mockApi.extensionDetails = {
mockNormandyApi.extensionDetails = {
[study.extensionApiId]: extensionDetailsFactory({
id: study.extensionApiId,
extension_id: FIXTURE_ADDON_ID,
@ -411,7 +411,7 @@ decorate_task(
// Test update fails when original addon does not exist
decorate_task(
withStudiesEnabled(),
ensureAddonCleanup,
ensureAddonCleanup(),
withMockNormandyApi(),
AddonStudies.withStudies([
branchedAddonStudyFactory({
@ -422,14 +422,13 @@ decorate_task(
]),
withSendEventSpy(),
withInstalledWebExtensionSafe({ id: "test@example.com", version: "0.1" }),
async function updateFailsAddonDoesNotExist(
mockApi,
[study],
async function updateFailsAddonDoesNotExist({
mockNormandyApi,
addonStudies: [study],
sendEventSpy,
installedAddon
) {
}) {
const recipe = recipeFromStudy(study);
mockApi.extensionDetails = {
mockNormandyApi.extensionDetails = {
[study.extensionApiId]: extensionDetailsFactory({
id: study.extensionApiId,
extension_id: study.addonId,
@ -468,7 +467,7 @@ decorate_task(
// Test update fails when download fails
decorate_task(
withStudiesEnabled(),
ensureAddonCleanup,
ensureAddonCleanup(),
withMockNormandyApi(),
AddonStudies.withStudies([
branchedAddonStudyFactory({
@ -480,14 +479,13 @@ decorate_task(
]),
withSendEventSpy(),
withInstalledWebExtensionSafe({ id: FIXTURE_ADDON_ID, version: "0.1" }),
async function updateDownloadFailure(
mockApi,
[study],
async function updateDownloadFailure({
mockNormandyApi,
addonStudies: [study],
sendEventSpy,
installedAddon
) {
}) {
const recipe = recipeFromStudy(study);
mockApi.extensionDetails = {
mockNormandyApi.extensionDetails = {
[study.extensionApiId]: extensionDetailsFactory({
id: study.extensionApiId,
extension_id: study.addonId,
@ -524,7 +522,7 @@ decorate_task(
// Test update fails when hash check fails
decorate_task(
withStudiesEnabled(),
ensureAddonCleanup,
ensureAddonCleanup(),
withMockNormandyApi(),
AddonStudies.withStudies([
branchedAddonStudyFactory({
@ -536,14 +534,13 @@ decorate_task(
]),
withSendEventSpy(),
withInstalledWebExtensionSafe({ id: FIXTURE_ADDON_ID, version: "0.1" }),
async function updateFailsHashCheckFail(
mockApi,
[study],
async function updateFailsHashCheckFail({
mockNormandyApi,
addonStudies: [study],
sendEventSpy,
installedAddon
) {
}) {
const recipe = recipeFromStudy(study);
mockApi.extensionDetails = {
mockNormandyApi.extensionDetails = {
[study.extensionApiId]: extensionDetailsFactory({
id: study.extensionApiId,
extension_id: study.addonId,
@ -581,7 +578,7 @@ decorate_task(
// Test update fails on downgrade when study version is greater than extension version
decorate_task(
withStudiesEnabled(),
ensureAddonCleanup,
ensureAddonCleanup(),
withMockNormandyApi(),
AddonStudies.withStudies([
branchedAddonStudyFactory({
@ -593,14 +590,13 @@ decorate_task(
]),
withSendEventSpy(),
withInstalledWebExtensionSafe({ id: FIXTURE_ADDON_ID, version: "2.0" }),
async function upgradeFailsNoDowngrades(
mockApi,
[study],
async function upgradeFailsNoDowngrades({
mockNormandyApi,
addonStudies: [study],
sendEventSpy,
installedAddon
) {
}) {
const recipe = recipeFromStudy(study);
mockApi.extensionDetails = {
mockNormandyApi.extensionDetails = {
[study.extensionApiId]: extensionDetailsFactory({
id: study.extensionApiId,
extension_id: study.addonId,
@ -637,7 +633,7 @@ decorate_task(
// Test update fails when there is a version mismatch with metadata
decorate_task(
withStudiesEnabled(),
ensureAddonCleanup,
ensureAddonCleanup(),
withMockNormandyApi(),
AddonStudies.withStudies([
branchedAddonStudyFactory({
@ -651,14 +647,13 @@ decorate_task(
withInstalledWebExtensionFromURL(
FIXTURE_ADDON_DETAILS["normandydriver-a-1.0"].url
),
async function upgradeFailsMetadataMismatchVersion(
mockApi,
[study],
async function upgradeFailsMetadataMismatchVersion({
mockNormandyApi,
addonStudies: [study],
sendEventSpy,
installedAddon
) {
}) {
const recipe = recipeFromStudy(study);
mockApi.extensionDetails = {
mockNormandyApi.extensionDetails = {
[study.extensionApiId]: extensionDetailsFactory({
id: study.extensionApiId,
extension_id: study.addonId,
@ -707,9 +702,9 @@ decorate_task(
// Test that unenrolling fails if the study doesn't exist
decorate_task(
withStudiesEnabled(),
ensureAddonCleanup,
ensureAddonCleanup(),
AddonStudies.withStudies(),
async function unenrollNonexistent(studies) {
async function unenrollNonexistent() {
const action = new BranchedAddonStudyAction();
await Assert.rejects(
action.unenroll(42),
@ -722,10 +717,10 @@ decorate_task(
// Test that unenrolling an inactive study fails
decorate_task(
withStudiesEnabled(),
ensureAddonCleanup,
ensureAddonCleanup(),
AddonStudies.withStudies([branchedAddonStudyFactory({ active: false })]),
withSendEventSpy(),
async ([study], sendEventSpy) => {
async ({ addonStudies: [study], sendEventSpy }) => {
const action = new BranchedAddonStudyAction();
await Assert.rejects(
action.unenroll(study.recipeId),
@ -739,7 +734,7 @@ decorate_task(
const testStopId = "testStop@example.com";
decorate_task(
withStudiesEnabled(),
ensureAddonCleanup,
ensureAddonCleanup(),
AddonStudies.withStudies([
branchedAddonStudyFactory({
active: true,
@ -747,15 +742,15 @@ decorate_task(
studyEndDate: null,
}),
]),
withInstalledWebExtension({ id: testStopId }, /* expectUninstall: */ true),
withInstalledWebExtension({ id: testStopId }, { expectUninstall: true }),
withSendEventSpy(),
withStub(TelemetryEnvironment, "setExperimentInactive"),
async function unenrollTest(
[study],
[addonId, addonFile],
async function unenrollTest({
addonStudies: [study],
installedWebExtension: { addonId },
sendEventSpy,
setExperimentInactiveStub
) {
setExperimentInactiveStub,
}) {
let addon = await AddonManager.getAddonByID(addonId);
ok(addon, "the add-on should be installed before unenrolling");
@ -794,7 +789,7 @@ decorate_task(
// If the add-on for a study isn't installed, a warning should be logged, but the action is still successful
decorate_task(
withStudiesEnabled(),
ensureAddonCleanup,
ensureAddonCleanup(),
AddonStudies.withStudies([
branchedAddonStudyFactory({
active: true,
@ -802,7 +797,10 @@ decorate_task(
}),
]),
withSendEventSpy(),
async function unenrollMissingAddonTest([study], sendEventSpy) {
async function unenrollMissingAddonTest({
addonStudies: [study],
sendEventSpy,
}) {
const action = new BranchedAddonStudyAction();
await action.unenroll(study.recipeId);
@ -828,17 +826,21 @@ decorate_task(
// Test that the action respects the study opt-out
decorate_task(
withStudiesEnabled(),
ensureAddonCleanup,
ensureAddonCleanup(),
withMockNormandyApi(),
withSendEventSpy(),
withMockPreferences(),
AddonStudies.withStudies(),
async function testOptOut(mockApi, sendEventSpy, mockPreferences) {
async function testOptOut({
mockNormandyApi,
sendEventSpy,
mockPreferences,
}) {
mockPreferences.set("app.shield.optoutstudies.enabled", false);
const action = new BranchedAddonStudyAction();
const enrollSpy = sinon.spy(action, "enroll");
const recipe = branchedAddonStudyRecipeFactory();
mockApi.extensionDetails = {
mockNormandyApi.extensionDetails = {
[recipe.arguments.branches[0].extensionApiId]: extensionDetailsFactory({
id: recipe.arguments.branches[0].extensionApiId,
}),
@ -864,11 +866,11 @@ decorate_task(
// Test that the action does not enroll paused recipes
decorate_task(
withStudiesEnabled(),
ensureAddonCleanup,
ensureAddonCleanup(),
withMockNormandyApi(),
withSendEventSpy(),
AddonStudies.withStudies(),
async function testEnrollmentPaused(mockApi, sendEventSpy) {
async function testEnrollmentPaused({ mockNormandyApi, sendEventSpy }) {
const action = new BranchedAddonStudyAction();
const enrollSpy = sinon.spy(action, "enroll");
const updateSpy = sinon.spy(action, "update");
@ -878,7 +880,7 @@ decorate_task(
const extensionDetails = extensionDetailsFactory({
id: recipe.arguments.extensionApiId,
});
mockApi.extensionDetails = {
mockNormandyApi.extensionDetails = {
[recipe.arguments.extensionApiId]: extensionDetails,
};
await action.processRecipe(recipe, BaseAction.suitability.FILTER_MATCH);
@ -896,7 +898,7 @@ decorate_task(
// Test that the action updates paused recipes
decorate_task(
withStudiesEnabled(),
ensureAddonCleanup,
ensureAddonCleanup(),
withMockNormandyApi(),
AddonStudies.withStudies([
branchedAddonStudyFactory({
@ -908,17 +910,16 @@ decorate_task(
]),
withSendEventSpy(),
withInstalledWebExtensionSafe({ id: FIXTURE_ADDON_ID, version: "1.0" }),
async function testUpdateEnrollmentPaused(
mockApi,
[study],
async function testUpdateEnrollmentPaused({
mockNormandyApi,
addonStudies: [study],
sendEventSpy,
installedAddon
) {
}) {
const addonUrl = FIXTURE_ADDON_DETAILS["normandydriver-a-2.0"].url;
const recipe = recipeFromStudy(study, {
arguments: { isEnrollmentPaused: true },
});
mockApi.extensionDetails = {
mockNormandyApi.extensionDetails = {
[study.extensionApiId]: extensionDetailsFactory({
id: study.extensionApiId,
extension_id: study.addonId,
@ -955,9 +956,9 @@ decorate_task(
// Test that unenroll called if the study is no longer sent from the server
decorate_task(
withStudiesEnabled(),
ensureAddonCleanup,
ensureAddonCleanup(),
AddonStudies.withStudies([branchedAddonStudyFactory()]),
async function unenroll([study]) {
async function unenroll({ addonStudies: [study] }) {
const action = new BranchedAddonStudyAction();
const unenrollSpy = sinon.stub(action, "unenroll");
await action.finalize();
@ -974,12 +975,17 @@ decorate_task(
// Mocks the branch selector, and then tests that the user correctly gets enrolled in that branch.
const successEnrollBranchedTest = decorate(
withStudiesEnabled(),
ensureAddonCleanup,
ensureAddonCleanup(),
withMockNormandyApi(),
withSendEventSpy(),
withStub(TelemetryEnvironment, "setExperimentActive"),
AddonStudies.withStudies(),
async function(branch, mockApi, sendEventSpy, setExperimentActiveStub) {
async function({
branch,
mockNormandyApi,
sendEventSpy,
setExperimentActiveStub,
}) {
ok(branch == "a" || branch == "b", "Branch should be either a or b");
const initialAddonIds = (await AddonManager.getAllAddons()).map(
addon => addon.id
@ -1006,7 +1012,7 @@ const successEnrollBranchedTest = decorate(
],
},
});
mockApi.extensionDetails = {
mockNormandyApi.extensionDetails = {
[recipe.arguments.branches[0].extensionApiId]: {
id: recipe.arguments.branches[0].extensionApiId,
name: "Normandy Fixture A",
@ -1028,7 +1034,7 @@ const successEnrollBranchedTest = decorate(
};
const extensionApiId =
recipe.arguments.branches[branch == "a" ? 0 : 1].extensionApiId;
const extensionDetails = mockApi.extensionDetails[extensionApiId];
const extensionDetails = mockNormandyApi.extensionDetails[extensionApiId];
const action = new BranchedAddonStudyAction();
const chooseBranchStub = sinon.stub(action, "chooseBranch");
@ -1106,26 +1112,26 @@ const successEnrollBranchedTest = decorate(
}
);
add_task(() => successEnrollBranchedTest("a"));
add_task(() => successEnrollBranchedTest("b"));
add_task(args => successEnrollBranchedTest({ ...args, branch: "a" }));
add_task(args => successEnrollBranchedTest({ ...args, branch: "b" }));
// If the enrolled branch no longer exists, unenroll
decorate_task(
withStudiesEnabled(),
ensureAddonCleanup,
ensureAddonCleanup(),
withMockNormandyApi(),
AddonStudies.withStudies([branchedAddonStudyFactory()]),
withSendEventSpy(),
withInstalledWebExtensionSafe(
{ id: FIXTURE_ADDON_ID, version: "1.0" },
/* expectUninstall: */ true
{ expectUninstall: true }
),
async function unenrollIfBranchDisappears(
mockApi,
[study],
async function unenrollIfBranchDisappears({
mockNormandyApi,
addonStudies: [study],
sendEventSpy,
[addonId, addonFile]
) {
installedWebExtensionSafe: { addonId },
}) {
const recipe = recipeFromStudy(study, {
arguments: {
branches: [
@ -1137,7 +1143,7 @@ decorate_task(
],
},
});
mockApi.extensionDetails = {
mockNormandyApi.extensionDetails = {
[study.extensionApiId]: extensionDetailsFactory({
id: study.extensionApiId,
extension_id: study.addonId,
@ -1186,11 +1192,11 @@ decorate_task(
// Test that branches without an add-on can be enrolled and unenrolled succesfully.
decorate_task(
withStudiesEnabled(),
ensureAddonCleanup,
ensureAddonCleanup(),
withMockNormandyApi(),
withSendEventSpy(),
AddonStudies.withStudies(),
async function noAddonBranches(mockApi, sendEventSpy) {
async function noAddonBranches({ sendEventSpy }) {
const initialAddonIds = (await AddonManager.getAllAddons()).map(
addon => addon.id
);
@ -1374,7 +1380,7 @@ decorate_task(
slug: `test-for-suitability-${suitability}`,
}),
]);
await decorator(async ([study]) => {
await decorator(async ({ addonStudies: [study] }) => {
let action = new BranchedAddonStudyAction();
let recipe = recipeFromStudy(study);
await action.processRecipe(recipe, suitability);
@ -1430,7 +1436,7 @@ decorate_task(
temporaryErrorDeadline: unhitDeadline,
}),
]);
await decorator(async ([study]) => {
await decorator(async ({ addonStudies: [study] }) => {
let action = new BranchedAddonStudyAction();
let recipe = recipeFromStudy(study);
await action.processRecipe(recipe, suitability);
@ -1470,7 +1476,7 @@ decorate_task(
temporaryErrorDeadline: hitDeadline,
}),
]);
await decorator(async ([study]) => {
await decorator(async ({ addonStudies: [study] }) => {
let action = new BranchedAddonStudyAction();
let recipe = recipeFromStudy(study);
await action.processRecipe(recipe, suitability);
@ -1512,7 +1518,7 @@ decorate_task(
temporaryErrorDeadline: hitDeadline,
}),
]);
await decorator(async ([study]) => {
await decorator(async ({ addonStudies: [study] }) => {
let action = new BranchedAddonStudyAction();
let recipe = recipeFromStudy(study);
await action.processRecipe(recipe, suitability);
@ -1555,7 +1561,7 @@ decorate_task(
temporaryErrorDeadline: invalidDeadline,
}),
]);
await decorator(async ([study]) => {
await decorator(async ({ addonStudies: [study] }) => {
let action = new BranchedAddonStudyAction();
let recipe = recipeFromStudy(study);
await action.processRecipe(recipe, suitability);

Просмотреть файл

@ -29,7 +29,7 @@ add_task(async function logging_works() {
// test that argument validation works
decorate_task(
withStub(Uptake, "reportRecipe"),
async function arguments_are_validated(reportRecipeStub) {
async function arguments_are_validated({ reportRecipeStub }) {
const action = new ConsoleLogAction();
const infoStub = sinon.stub(action.log, "info");

Просмотреть файл

@ -15,7 +15,7 @@ const { _ExperimentManager, ExperimentManager } = ChromeUtils.import(
decorate_task(
withStudiesEnabled(),
withStub(Uptake, "reportRecipe"),
async function arguments_are_validated(reportRecipe) {
async function arguments_are_validated({ reportRecipeStub }) {
const action = new MessagingExperimentAction();
is(
@ -55,7 +55,7 @@ decorate_task(
await action.processRecipe(recipe, BaseAction.suitability.FILTER_MATCH);
await action.finalize();
Assert.deepEqual(reportRecipe.args, [[recipe, Uptake.RECIPE_SUCCESS]]);
Assert.deepEqual(reportRecipeStub.args, [[recipe, Uptake.RECIPE_SUCCESS]]);
Assert.deepEqual(
onRecipeStub.args,
[[recipe.arguments, "normandy"]],

Просмотреть файл

@ -73,14 +73,14 @@ function preferenceExperimentFactory(args) {
decorate_task(
withStudiesEnabled(),
withStub(Uptake, "reportRecipe"),
async function run_without_errors(reportRecipe) {
async function run_without_errors({ reportRecipeStub }) {
const action = new PreferenceExperimentAction();
const recipe = preferenceExperimentFactory();
await action.processRecipe(recipe, BaseAction.suitability.FILTER_MATCH);
await action.finalize();
// Errors thrown in actions are caught and silenced, so instead check for an
// explicit success here.
Assert.deepEqual(reportRecipe.args, [[recipe, Uptake.RECIPE_SUCCESS]]);
Assert.deepEqual(reportRecipeStub.args, [[recipe, Uptake.RECIPE_SUCCESS]]);
}
);
@ -89,7 +89,7 @@ decorate_task(
withStub(Uptake, "reportRecipe"),
withStub(Uptake, "reportAction"),
withPrefEnv({ set: [["app.shield.optoutstudies.enabled", false]] }),
async function checks_disabled(reportRecipe, reportAction) {
async function checks_disabled({ reportRecipeStub, reportActionStub }) {
const action = new PreferenceExperimentAction();
action.log = mockLogger();
@ -112,10 +112,12 @@ decorate_task(
Assert.deepEqual(action.log.debug.args[1], [
"Skipping post-execution hook for PreferenceExperimentAction because it is disabled.",
]);
Assert.deepEqual(reportRecipe.args, [
Assert.deepEqual(reportRecipeStub.args, [
[recipe, Uptake.RECIPE_ACTION_DISABLED],
]);
Assert.deepEqual(reportAction.args, [[action.name, Uptake.ACTION_SUCCESS]]);
Assert.deepEqual(reportActionStub.args, [
[action.name, Uptake.ACTION_SUCCESS],
]);
}
);
@ -123,7 +125,7 @@ decorate_task(
withStudiesEnabled(),
withStub(PreferenceExperiments, "start"),
PreferenceExperiments.withMockExperiments([]),
async function enroll_user_if_never_been_in_experiment(startStub) {
async function enroll_user_if_never_been_in_experiment({ startStub }) {
const action = new PreferenceExperimentAction();
const recipe = preferenceExperimentFactory({
slug: "test",
@ -186,7 +188,7 @@ decorate_task(
withStudiesEnabled(),
withStub(PreferenceExperiments, "markLastSeen"),
PreferenceExperiments.withMockExperiments([{ slug: "test", expired: false }]),
async function markSeen_if_experiment_active(markLastSeenStub) {
async function markSeen_if_experiment_active({ markLastSeenStub }) {
const action = new PreferenceExperimentAction();
const recipe = preferenceExperimentFactory({
name: "test",
@ -203,7 +205,7 @@ decorate_task(
withStudiesEnabled(),
withStub(PreferenceExperiments, "markLastSeen"),
PreferenceExperiments.withMockExperiments([{ slug: "test", expired: true }]),
async function dont_markSeen_if_experiment_expired(markLastSeenStub) {
async function dont_markSeen_if_experiment_expired({ markLastSeenStub }) {
const action = new PreferenceExperimentAction();
const recipe = preferenceExperimentFactory({
name: "test",
@ -219,7 +221,7 @@ decorate_task(
decorate_task(
withStudiesEnabled(),
withStub(PreferenceExperiments, "start"),
async function do_nothing_if_enrollment_paused(startStub) {
async function do_nothing_if_enrollment_paused({ startStub }) {
const action = new PreferenceExperimentAction();
const recipe = preferenceExperimentFactory({
isEnrollmentPaused: true,
@ -243,7 +245,7 @@ decorate_task(
actionName: "PreferenceExperimentAction",
},
]),
async function stop_experiments_not_seen(stopStub) {
async function stop_experiments_not_seen({ stopStub }) {
const action = new PreferenceExperimentAction();
const recipe = preferenceExperimentFactory({
slug: "seen",
@ -280,7 +282,7 @@ decorate_task(
actionName: "SinglePreferenceExperimentAction",
},
]),
async function dont_stop_experiments_for_other_action(stopStub) {
async function dont_stop_experiments_for_other_action({ stopStub }) {
const action = new PreferenceExperimentAction();
const recipe = preferenceExperimentFactory({
name: "seen",
@ -310,10 +312,10 @@ decorate_task(
expired: false,
},
]),
async function do_nothing_if_preference_is_already_being_tested(
async function do_nothing_if_preference_is_already_being_tested({
startStub,
reportRecipeStub
) {
reportRecipeStub,
}) {
const action = new PreferenceExperimentAction();
const recipe = preferenceExperimentFactory({
name: "new",
@ -344,7 +346,7 @@ decorate_task(
withStudiesEnabled(),
withStub(PreferenceExperiments, "start"),
PreferenceExperiments.withMockExperiments([]),
async function experimentType_with_isHighPopulation_false(startStub) {
async function experimentType_with_isHighPopulation_false({ startStub }) {
const action = new PreferenceExperimentAction();
const recipe = preferenceExperimentFactory({
isHighPopulation: false,
@ -361,7 +363,7 @@ decorate_task(
withStudiesEnabled(),
withStub(PreferenceExperiments, "start"),
PreferenceExperiments.withMockExperiments([]),
async function experimentType_with_isHighPopulation_true(startStub) {
async function experimentType_with_isHighPopulation_true({ startStub }) {
const action = new PreferenceExperimentAction();
const recipe = preferenceExperimentFactory({
isHighPopulation: true,
@ -377,7 +379,7 @@ decorate_task(
decorate_task(
withStudiesEnabled(),
withStub(Sampling, "ratioSample"),
async function chooseBranch_uses_ratioSample(ratioSampleStub) {
async function chooseBranch_uses_ratioSample({ ratioSampleStub }) {
ratioSampleStub.returns(Promise.resolve(1));
const action = new PreferenceExperimentAction();
const branches = [
@ -418,8 +420,8 @@ decorate_task(
withStudiesEnabled(),
withMockPreferences(),
PreferenceExperiments.withMockExperiments([]),
async function integration_test_enroll_and_unenroll(prefs) {
prefs.set("fake.preference", "oldvalue", "user");
async function integration_test_enroll_and_unenroll({ mockPreferences }) {
mockPreferences.set("fake.preference", "oldvalue", "user");
const recipe = preferenceExperimentFactory({
slug: "integration test experiment",
branches: [
@ -543,7 +545,7 @@ decorate_task(
const decorator = PreferenceExperiments.withMockExperiments([
{ slug: `test-for-suitability-${suitability}` },
]);
await decorator(async ([experiment]) => {
await decorator(async ({ prefExperiments: [experiment] }) => {
let action = new PreferenceExperimentAction();
let recipe = preferenceExperimentFactory({ slug: experiment.slug });
await action.processRecipe(recipe, suitability);
@ -599,7 +601,7 @@ decorate_task(
temporaryErrorDeadline: unhitDeadline,
},
]);
await decorator(async ([experiment]) => {
await decorator(async ({ prefExperiments: [experiment] }) => {
let action = new PreferenceExperimentAction();
let recipe = preferenceExperimentFactory({ slug: experiment.slug });
await action.processRecipe(recipe, suitability);
@ -643,7 +645,7 @@ decorate_task(
branch: "test-branch",
},
]);
await decorator(async ([experiment]) => {
await decorator(async ({ prefExperiments: [experiment] }) => {
let action = new PreferenceExperimentAction();
let recipe = preferenceExperimentFactory({ slug: experiment.slug });
await action.processRecipe(recipe, suitability);
@ -688,7 +690,7 @@ decorate_task(
temporaryErrorDeadline: hitDeadline,
}),
]);
await decorator(async ([experiment]) => {
await decorator(async ({ prefExperiments: [experiment] }) => {
let action = new PreferenceExperimentAction();
let recipe = preferenceExperimentFactory({ slug: experiment.slug });
await action.processRecipe(recipe, suitability);
@ -734,7 +736,7 @@ decorate_task(
temporaryErrorDeadline: invalidDeadline,
}),
]);
await decorator(async ([experiment]) => {
await decorator(async ({ prefExperiments: [experiment] }) => {
let action = new PreferenceExperimentAction();
let recipe = preferenceExperimentFactory({ slug: experiment.slug });
await action.processRecipe(recipe, suitability);

Просмотреть файл

@ -19,7 +19,7 @@ decorate_task(
withStub(TelemetryEnvironment, "setExperimentInactive"),
withSendEventSpy(),
PreferenceRollouts.withTestMock(),
async function simple_rollback(setExperimentInactiveStub, sendEventSpy) {
async function simple_rollback({ setExperimentInactiveStub, sendEventSpy }) {
Services.prefs.getDefaultBranch("").setIntPref("test.pref1", 2);
Services.prefs
.getDefaultBranch("")
@ -131,7 +131,7 @@ decorate_task(
decorate_task(
withSendEventSpy(),
PreferenceRollouts.withTestMock(),
async function cant_rollback_graduated(sendEventSpy) {
async function cant_rollback_graduated({ sendEventSpy }) {
Services.prefs.getDefaultBranch("").setIntPref("test.pref", 1);
await PreferenceRollouts.add({
slug: "graduated-rollout",
@ -191,7 +191,7 @@ decorate_task(
withSendEventSpy(),
withStub(Uptake, "reportRecipe"),
PreferenceRollouts.withTestMock(),
async function rollback_without_rollout(sendEventSpy, reportRecipeStub) {
async function rollback_without_rollout({ sendEventSpy, reportRecipeStub }) {
let recipe = { id: 1, arguments: { rolloutSlug: "missing-rollout" } };
const action = new PreferenceRollbackAction();
@ -213,10 +213,10 @@ decorate_task(
withStub(TelemetryEnvironment, "setExperimentInactive"),
withSendEventSpy(),
PreferenceRollouts.withTestMock(),
async function rollback_already_rolled_back(
async function rollback_already_rolled_back({
setExperimentInactiveStub,
sendEventSpy
) {
sendEventSpy,
}) {
Services.prefs.getDefaultBranch("").setIntPref("test.pref", 1);
const recipe = { id: 1, arguments: { rolloutSlug: "test-rollout" } };
@ -314,7 +314,7 @@ decorate_task(
PreferenceRollouts.withTestMock({
graduationSet: new Set(["graduated-rollout"]),
}),
async function cant_rollback_graduation_set(sendEventSpy) {
async function cant_rollback_graduation_set({ sendEventSpy }) {
Services.prefs.getDefaultBranch("").setIntPref("test.pref", 1);
let recipe = { id: 1, arguments: { rolloutSlug: "graduated-rollout" } };

Просмотреть файл

@ -21,10 +21,10 @@ decorate_task(
withStub(TelemetryEnvironment, "setExperimentActive"),
withSendEventSpy(),
PreferenceRollouts.withTestMock(),
async function simple_recipe_enrollment(
async function simple_recipe_enrollment({
setExperimentActiveStub,
sendEventSpy
) {
sendEventSpy,
}) {
const recipe = {
id: 1,
arguments: {
@ -130,7 +130,7 @@ decorate_task(
decorate_task(
withSendEventSpy(),
PreferenceRollouts.withTestMock(),
async function update_enrollment(sendEventSpy) {
async function update_enrollment({ sendEventSpy }) {
// first enrollment
const recipe = {
id: 1,
@ -243,7 +243,7 @@ decorate_task(
decorate_task(
withSendEventSpy(),
PreferenceRollouts.withTestMock(),
async function ungraduate_enrollment(sendEventSpy) {
async function ungraduate_enrollment({ sendEventSpy }) {
Services.prefs.getDefaultBranch("").setIntPref("test.pref", 1);
await PreferenceRollouts.add({
slug: "test-rollout",
@ -308,7 +308,7 @@ decorate_task(
decorate_task(
withSendEventSpy(),
PreferenceRollouts.withTestMock(),
async function conflicting_recipes(sendEventSpy) {
async function conflicting_recipes({ sendEventSpy }) {
// create two recipes that each share a pref and have a unique pref.
const recipe1 = {
id: 1,
@ -421,7 +421,7 @@ decorate_task(
decorate_task(
withSendEventSpy(),
PreferenceRollouts.withTestMock(),
async function wrong_preference_value(sendEventSpy) {
async function wrong_preference_value({ sendEventSpy }) {
Services.prefs.getDefaultBranch("").setCharPref("test.pref", "not an int");
const recipe = {
id: 1,
@ -568,9 +568,9 @@ decorate_task(
// When running a rollout a second time on a pref that doesn't have an existing
// value, the previous value is handled correctly.
decorate_task(
withSendEventSpy(),
PreferenceRollouts.withTestMock(),
async function(sendEventSpy) {
withSendEventSpy(),
async function({ sendEventSpy }) {
const recipe = {
id: 1,
arguments: {
@ -624,7 +624,7 @@ decorate_task(
withStub(TelemetryEnvironment, "setExperimentActive"),
withSendEventSpy(),
PreferenceRollouts.withTestMock(),
async function no_op_new_recipe(setExperimentActiveStub, sendEventSpy) {
async function no_op_new_recipe({ setExperimentActiveStub, sendEventSpy }) {
Services.prefs.getDefaultBranch("").setIntPref("test.pref", 1);
const recipe = {
@ -680,7 +680,10 @@ decorate_task(
withStub(TelemetryEnvironment, "setExperimentActive"),
withSendEventSpy(),
PreferenceRollouts.withTestMock({ graduationSet: new Set(["test-rollout"]) }),
async function graduationSetNewRecipe(setExperimentActiveStub, sendEventSpy) {
async function graduationSetNewRecipe({
setExperimentActiveStub,
sendEventSpy,
}) {
Services.prefs.getDefaultBranch("").setIntPref("test.pref", 1);
const recipe = {

Просмотреть файл

@ -49,7 +49,7 @@ class MockEventEmitter {
function withStubbedHeartbeat() {
return function(testFunction) {
return async function wrappedTestFunction(...args) {
return async function wrappedTestFunction(args) {
const backstage = ChromeUtils.import(
"resource://normandy/actions/ShowHeartbeatAction.jsm",
null
@ -61,10 +61,11 @@ function withStubbedHeartbeat() {
backstage.Heartbeat = heartbeatClassStub;
try {
await testFunction(
{ heartbeatClassStub, heartbeatInstanceStub },
...args
);
await testFunction({
...args,
heartbeatClassStub,
heartbeatInstanceStub,
});
} finally {
backstage.Heartbeat = originalHeartbeat;
}
@ -74,10 +75,10 @@ function withStubbedHeartbeat() {
function withClearStorage() {
return function(testFunction) {
return async function wrappedTestFunction(...args) {
return async function wrappedTestFunction(args) {
Storage.clearAllStorage();
try {
await testFunction(...args);
await testFunction(args);
} finally {
Storage.clearAllStorage();
}
@ -396,7 +397,7 @@ add_task(async function postAnswerUrlUserIdIfRequested() {
decorate_task(
withStubbedHeartbeat(),
withClearStorage(),
async function testGenerateSurveyId({ heartbeatClassStub }) {
async function testGenerateSurveyId() {
const recipeWithoutId = heartbeatRecipeFactory({
arguments: { surveyId: "test-id", includeTelemetryUUID: false },
});

Просмотреть файл

@ -53,16 +53,19 @@ this.TEST_XPI_URL = (function() {
return Services.io.newFileURI(dir).spec;
})();
this.withWebExtension = function(manifestOverrides = {}) {
this.withWebExtension = function(
manifestOverrides = {},
{ as = "webExtension" } = {}
) {
return function wrapper(testFunction) {
return async function wrappedTestFunction(...args) {
return async function wrappedTestFunction(args) {
const random = Math.random()
.toString(36)
.replace(/0./, "")
.substr(-3);
let id = `normandydriver_${random}@example.com`;
let addonId = `normandydriver_${random}@example.com`;
if ("id" in manifestOverrides) {
id = manifestOverrides.id;
addonId = manifestOverrides.id;
delete manifestOverrides.id;
}
@ -73,7 +76,7 @@ this.withWebExtension = function(manifestOverrides = {}) {
version: "1.0",
description: "Dummy test fixture that's a webextension",
applications: {
gecko: { id },
gecko: { id: addonId },
},
},
manifestOverrides
@ -87,7 +90,7 @@ this.withWebExtension = function(manifestOverrides = {}) {
Services.obs.notifyObservers(addonFile, "flush-cache-entry");
try {
await testFunction(...args, [id, addonFile]);
await testFunction({ ...args, [as]: { addonId, addonFile } });
} finally {
AddonTestUtils.cleanupTempXPIs();
}
@ -95,32 +98,34 @@ this.withWebExtension = function(manifestOverrides = {}) {
};
};
this.withCorruptedWebExtension = function() {
this.withCorruptedWebExtension = function(options) {
// This should be an invalid manifest version, so that installing this add-on fails.
return this.withWebExtension({ manifest_version: -1 });
return this.withWebExtension({ manifest_version: -1 }, options);
};
this.withInstalledWebExtension = function(
manifestOverrides = {},
expectUninstall = false
{ expectUninstall = false, as = "installedWebExtension" } = {}
) {
return function wrapper(testFunction) {
return decorate(
withWebExtension(manifestOverrides),
async function wrappedTestFunction(...args) {
const [id, file] = args[args.length - 1];
const startupPromise = AddonTestUtils.promiseWebExtensionStartup(id);
withWebExtension(manifestOverrides, { as }),
async function wrappedTestFunction(args) {
const { addonId, addonFile } = args[as];
const startupPromise = AddonTestUtils.promiseWebExtensionStartup(
addonId
);
const addonInstall = await AddonManager.getInstallForFile(
file,
addonFile,
"application/x-xpinstall"
);
await addonInstall.install();
await startupPromise;
try {
await testFunction(...args);
await testFunction(args);
} finally {
const addonToUninstall = await AddonManager.getAddonByID(id);
const addonToUninstall = await AddonManager.getAddonByID(addonId);
if (addonToUninstall) {
await addonToUninstall.uninstall();
} else {
@ -137,8 +142,8 @@ this.withInstalledWebExtension = function(
this.withMockNormandyApi = function() {
return function(testFunction) {
return async function inner(...args) {
const mockApi = {
return async function inner(args) {
const mockNormandyApi = {
actions: [],
recipes: [],
implementations: {},
@ -146,10 +151,10 @@ this.withMockNormandyApi = function() {
};
// Use callsFake instead of resolves so that the current values in mockApi are used.
mockApi.fetchExtensionDetails = sinon
mockNormandyApi.fetchExtensionDetails = sinon
.stub(NormandyApi, "fetchExtensionDetails")
.callsFake(async extensionId => {
const details = mockApi.extensionDetails[extensionId];
const details = mockNormandyApi.extensionDetails[extensionId];
if (!details) {
throw new Error(`Missing extension details for ${extensionId}`);
}
@ -157,9 +162,9 @@ this.withMockNormandyApi = function() {
});
try {
await testFunction(...args, mockApi);
await testFunction({ ...args, mockNormandyApi });
} finally {
mockApi.fetchExtensionDetails.restore();
mockNormandyApi.fetchExtensionDetails.restore();
}
};
};
@ -172,12 +177,12 @@ const preferenceBranches = {
this.withMockPreferences = function() {
return function(testFunction) {
return async function inner(...args) {
const prefManager = new MockPreferences();
return async function inner(args) {
const mockPreferences = new MockPreferences();
try {
await testFunction(...args, prefManager);
await testFunction({ ...args, mockPreferences });
} finally {
prefManager.cleanup();
mockPreferences.cleanup();
}
};
};
@ -241,10 +246,10 @@ class MockPreferences {
this.withPrefEnv = function(inPrefs) {
return function wrapper(testFunc) {
return async function inner(...args) {
return async function inner(args) {
await SpecialPowers.pushPrefEnv(inPrefs);
try {
await testFunc(...args);
await testFunc(args);
} finally {
await SpecialPowers.popPrefEnv();
}
@ -254,12 +259,12 @@ this.withPrefEnv = function(inPrefs) {
this.withStudiesEnabled = function() {
return function(testFunc) {
return async function inner(...args) {
return async function inner(args) {
await SpecialPowers.pushPrefEnv({
set: [["app.shield.optoutstudies.enabled", true]],
});
try {
await testFunc(...args);
await testFunc(args);
} finally {
await SpecialPowers.popPrefEnv();
}
@ -312,13 +317,17 @@ this.decorate_task = function(...args) {
return add_task(decorate(...args));
};
this.withStub = function(object, method, { returnValue } = {}) {
this.withStub = function(
object,
method,
{ returnValue, as = `${method}Stub` } = {}
) {
return function wrapper(testFunction) {
return async function wrappedTestFunction(...args) {
return async function wrappedTestFunction(args) {
const stub = sinon.stub(object, method);
stub.returnValue = returnValue;
try {
await testFunction(...args, stub);
await testFunction({ ...args, [as]: stub });
} finally {
stub.restore();
}
@ -326,12 +335,12 @@ this.withStub = function(object, method, { returnValue } = {}) {
};
};
this.withSpy = function(...spyArgs) {
this.withSpy = function(object, method, { as = `${method}Spy` } = {}) {
return function wrapper(testFunction) {
return async function wrappedTestFunction(...args) {
const spy = sinon.spy(...spyArgs);
return async function wrappedTestFunction(args) {
const spy = sinon.spy(object, method);
try {
await testFunction(...args, spy);
await testFunction({ ...args, [as]: spy });
} finally {
spy.restore();
}
@ -348,9 +357,9 @@ this.studyEndObserved = function(recipeId) {
this.withSendEventSpy = function() {
return function(testFunction) {
return async function wrappedTestFunction(...args) {
const spy = sinon.spy(TelemetryEvents, "sendEvent");
spy.assertEvents = expected => {
return async function wrappedTestFunction(args) {
const sendEventSpy = sinon.spy(TelemetryEvents, "sendEvent");
sendEventSpy.assertEvents = expected => {
expected = expected.map(event => ["normandy"].concat(event));
TelemetryTestUtils.assertEvents(
expected,
@ -360,10 +369,10 @@ this.withSendEventSpy = function() {
};
Services.telemetry.clearEvents();
try {
await testFunction(...args, spy);
await testFunction({ ...args, sendEventSpy });
} finally {
spy.restore();
Assert.ok(!spy.threw(), "Telemetry events should not fail");
sendEventSpy.restore();
Assert.ok(!sendEventSpy.threw(), "Telemetry events should not fail");
}
};
};
@ -495,20 +504,23 @@ this.safeUninstallAddon = async function(addon) {
* Test decorator that is a modified version of the withInstalledWebExtension
* decorator that safely uninstalls the created addon.
*/
this.withInstalledWebExtensionSafe = function(manifestOverrides = {}) {
this.withInstalledWebExtensionSafe = function(
manifestOverrides = {},
{ as = "installedWebExtensionSafe" } = {}
) {
return testFunction => {
return async function wrappedTestFunction(...args) {
const decorated = withInstalledWebExtension(
manifestOverrides,
true
)(async ([id, file]) => {
return async function wrappedTestFunction(args) {
const decorated = withInstalledWebExtension(manifestOverrides, {
expectUninstall: true,
as,
})(async ({ [as]: { addonId, addonFile } }) => {
try {
await testFunction(...args, [id, file]);
await testFunction({ ...args, [as]: { addonId, addonFile } });
} finally {
let addon = await AddonManager.getAddonByID(id);
let addon = await AddonManager.getAddonByID(addonId);
if (addon) {
await safeUninstallAddon(addon);
addon = await AddonManager.getAddonByID(id);
addon = await AddonManager.getAddonByID(addonId);
ok(!addon, "add-on should be uninstalled");
}
}
@ -521,9 +533,12 @@ this.withInstalledWebExtensionSafe = function(manifestOverrides = {}) {
/**
* Test decorator to provide a web extension installed from a URL.
*/
this.withInstalledWebExtensionFromURL = function(url) {
this.withInstalledWebExtensionFromURL = function(
url,
{ as = "installedWebExtension" } = {}
) {
return function wrapper(testFunction) {
return async function wrappedTestFunction(...args) {
return async function wrappedTestFunction(args) {
let startupPromise;
let addonId;
@ -540,7 +555,7 @@ this.withInstalledWebExtensionFromURL = function(url) {
await startupPromise;
try {
await testFunction(...args, [addonId, url]);
await testFunction({ ...args, [as]: { addonId, url } });
} finally {
const addonToUninstall = await AddonManager.getAddonByID(addonId);
await safeUninstallAddon(addonToUninstall);
@ -553,19 +568,21 @@ this.withInstalledWebExtensionFromURL = function(url) {
* Test decorator that checks that the test cleans up all add-ons installed
* during the test. Likely needs to be the first decorator used.
*/
this.ensureAddonCleanup = function(testFunction) {
return async function wrappedTestFunction(...args) {
const beforeAddons = new Set(await AddonManager.getAllAddons());
this.ensureAddonCleanup = function() {
return function(testFunction) {
return async function wrappedTestFunction(args) {
const beforeAddons = new Set(await AddonManager.getAllAddons());
try {
await testFunction(...args);
} finally {
const afterAddons = new Set(await AddonManager.getAllAddons());
Assert.deepEqual(
beforeAddons,
afterAddons,
"The add-ons should be same before and after the test"
);
}
try {
await testFunction(args);
} finally {
const afterAddons = new Set(await AddonManager.getAllAddons());
Assert.deepEqual(
beforeAddons,
afterAddons,
"The add-ons should be same before and after the test"
);
}
};
};
};

Просмотреть файл

@ -7,19 +7,16 @@ const { CanonicalJSON } = ChromeUtils.import(
const { PromiseUtils } = ChromeUtils.import(
"resource://gre/modules/PromiseUtils.jsm"
);
const { NormandyTestUtils } = ChromeUtils.import(
"resource://testing-common/NormandyTestUtils.jsm"
);
/* import-globals-from utils.js */
load("utils.js");
NormandyTestUtils.init({ add_task });
const { decorate_task } = NormandyTestUtils;
Cu.importGlobalProperties(["fetch"]);
/* import-globals-from utils.js */
load("utils.js");
decorate_task(withMockApiServer(), async function test_get(serverUrl) {
decorate_task(withMockApiServer(), async function test_get({ serverUrl }) {
// Test that NormandyApi can fetch from the test server.
const response = await NormandyApi.get(`${serverUrl}/api/v1/`);
const data = await response.json();
@ -30,7 +27,9 @@ decorate_task(withMockApiServer(), async function test_get(serverUrl) {
);
});
decorate_task(withMockApiServer(), async function test_getApiUrl(serverUrl) {
decorate_task(withMockApiServer(), async function test_getApiUrl({
serverUrl,
}) {
const apiBase = `${serverUrl}/api/v1`;
// Test that NormandyApi can use the self-describing API's index
const recipeListUrl = await NormandyApi.getApiUrl("extension-list");
@ -41,10 +40,10 @@ decorate_task(withMockApiServer(), async function test_getApiUrl(serverUrl) {
);
});
decorate_task(withMockApiServer(), async function test_getApiUrlSlashes(
decorate_task(withMockApiServer(), async function test_getApiUrlSlashes({
serverUrl,
preferences
) {
mockPreferences,
}) {
const fakeResponse = new MockResponse(
JSON.stringify({ "test-endpoint": `${serverUrl}/test/` })
);
@ -55,7 +54,7 @@ decorate_task(withMockApiServer(), async function test_getApiUrlSlashes(
// without slash
{
NormandyApi.clearIndexCache();
preferences.set("app.normandy.api_url", `${serverUrl}/api/v1`);
mockPreferences.set("app.normandy.api_url", `${serverUrl}/api/v1`);
const endpoint = await NormandyApi.getApiUrl("test-endpoint");
equal(endpoint, `${serverUrl}/test/`);
ok(
@ -68,7 +67,7 @@ decorate_task(withMockApiServer(), async function test_getApiUrlSlashes(
// with slash
{
NormandyApi.clearIndexCache();
preferences.set("app.normandy.api_url", `${serverUrl}/api/v1/`);
mockPreferences.set("app.normandy.api_url", `${serverUrl}/api/v1/`);
const endpoint = await NormandyApi.getApiUrl("test-endpoint");
equal(endpoint, `${serverUrl}/test/`);
ok(
@ -168,7 +167,7 @@ decorate_task(withMockApiServer(), async function test_fetchExtensionDetails() {
decorate_task(
withScriptServer("query_server.sjs"),
async function test_getTestServer(serverUrl) {
async function test_getTestServer({ serverUrl }) {
// Test that NormandyApi can fetch from the test server.
const response = await NormandyApi.get(serverUrl);
const data = await response.json();
@ -182,7 +181,7 @@ decorate_task(
decorate_task(
withScriptServer("query_server.sjs"),
async function test_getQueryString(serverUrl) {
async function test_getQueryString({ serverUrl }) {
// Test that NormandyApi can send query string parameters to the test server.
const response = await NormandyApi.get(serverUrl, {
foo: "bar",
@ -200,7 +199,7 @@ decorate_task(
// Test that no credentials are sent, even if the cookie store contains them.
decorate_task(
withScriptServer("cookie_server.sjs"),
async function test_sendsNoCredentials(serverUrl) {
async function test_sendsNoCredentials({ serverUrl }) {
// This test uses cookie_server.sjs, which responds to all requests with a
// response that sets a cookie.

Просмотреть файл

@ -16,9 +16,6 @@ const { TelemetryEvents } = ChromeUtils.import(
const { AddonManager } = ChromeUtils.import(
"resource://gre/modules/AddonManager.jsm"
);
const { NormandyTestUtils } = ChromeUtils.import(
"resource://testing-common/NormandyTestUtils.jsm"
);
const { AddonStudies } = ChromeUtils.import(
"resource://normandy/lib/AddonStudies.jsm"
);
@ -26,14 +23,14 @@ const { PromiseUtils } = ChromeUtils.import(
"resource://gre/modules/PromiseUtils.jsm"
);
/* import-globals-from utils.js */
load("utils.js");
NormandyTestUtils.init({ add_task });
const { decorate_task } = NormandyTestUtils;
const global = this;
/* import-globals-from utils.js */
load("utils.js");
add_task(async () => {
ExtensionTestUtils.init(global);
AddonTestUtils.init(global);
@ -52,12 +49,7 @@ add_task(async () => {
decorate_task(
withMockApiServer(),
AddonStudies.withStudies([]),
async function test_addon_unenroll(
_serverUrl,
_preferences,
apiServer,
_studies
) {
async function test_addon_unenroll({ server: apiServer }) {
const ID = "study@tests.mozilla.org";
// Create a test extension that uses webextension experiments to install
@ -226,12 +218,7 @@ decorate_task(
decorate_task(
withMockApiServer(),
AddonStudies.withStudies([]),
async function test_addon_unenroll(
_serverUrl,
_preferences,
apiServer,
_studies
) {
async function test_addon_unenroll({ server: apiServer }) {
const ID = "study@tests.mozilla.org";
// Create a dummy webextension

Просмотреть файл

@ -13,6 +13,9 @@ const { OS } = ChromeUtils.import("resource://gre/modules/osfile.jsm");
const { NormandyApi } = ChromeUtils.import(
"resource://normandy/lib/NormandyApi.jsm"
);
const { NormandyTestUtils } = ChromeUtils.import(
"resource://testing-common/NormandyTestUtils.jsm"
);
const CryptoHash = Components.Constructor(
"@mozilla.org/security/hash;1",
@ -25,58 +28,6 @@ const FileInputStream = Components.Constructor(
"init"
);
const preferenceBranches = {
user: Preferences,
default: new Preferences({ defaultBranch: true }),
};
// duplicated from test/browser/head.js until we move everything over to mochitests.
function withMockPreferences() {
return function(testFunction) {
return async function inner(...args) {
const prefManager = new MockPreferences();
try {
await testFunction(...args, prefManager);
} finally {
prefManager.cleanup();
}
};
};
}
class MockPreferences {
constructor() {
this.oldValues = { user: {}, default: {} };
}
set(name, value, branch = "user") {
this.preserve(name, branch);
preferenceBranches[branch].set(name, value);
}
preserve(name, branch) {
if (!(name in this.oldValues[branch])) {
this.oldValues[branch][name] = preferenceBranches[branch].get(
name,
undefined
);
}
}
cleanup() {
for (const [branchName, values] of Object.entries(this.oldValues)) {
const preferenceBranch = preferenceBranches[branchName];
for (const [name, value] of Object.entries(values)) {
if (value !== undefined) {
preferenceBranch.set(name, value);
} else {
preferenceBranch.reset(name);
}
}
}
}
}
class MockResponse {
constructor(content) {
this.content = content;
@ -93,22 +44,25 @@ class MockResponse {
function withServer(server) {
return function(testFunction) {
return withMockPreferences(async function inner(preferences) {
const serverUrl = `http://localhost:${server.identity.primaryPort}`;
preferences.set("app.normandy.api_url", `${serverUrl}/api/v1`);
preferences.set(
"security.content.signature.root_hash",
// Hash of the key that signs the normandy dev certificates
"4C:35:B1:C3:E3:12:D9:55:E7:78:ED:D0:A7:E7:8A:38:83:04:EF:01:BF:FA:03:29:B2:46:9F:3C:C5:EC:36:04"
);
NormandyApi.clearIndexCache();
return NormandyTestUtils.decorate(
NormandyTestUtils.withMockPreferences(),
async function inner({ mockPreferences, ...args }) {
const serverUrl = `http://localhost:${server.identity.primaryPort}`;
mockPreferences.set("app.normandy.api_url", `${serverUrl}/api/v1`);
mockPreferences.set(
"security.content.signature.root_hash",
// Hash of the key that signs the normandy dev certificates
"4C:35:B1:C3:E3:12:D9:55:E7:78:ED:D0:A7:E7:8A:38:83:04:EF:01:BF:FA:03:29:B2:46:9F:3C:C5:EC:36:04"
);
NormandyApi.clearIndexCache();
try {
await testFunction(serverUrl, preferences, server);
} finally {
await new Promise(resolve => server.stop(resolve));
try {
await testFunction({ ...args, serverUrl, mockPreferences, server });
} finally {
await new Promise(resolve => server.stop(resolve));
}
}
});
);
};
}

Просмотреть файл

@ -197,18 +197,18 @@ var tests = [
}),
],
}),
async function testNormandyInfoInTroubleshooting(
prefStudies,
async function testNormandyInfoInTroubleshooting({
prefExperiments,
addonStudies,
prefRollouts
) {
prefRollouts,
}) {
await new Promise(resolve => {
Troubleshoot.snapshot(function(snapshot) {
let info = snapshot.normandy;
// The order should be flipped, since each category is sorted by slug.
Assert.deepEqual(
info.prefStudies,
[prefStudies[1], prefStudies[0]],
[prefExperiments[1], prefExperiments[0]],
"prefs studies should exist in the right order"
);
Assert.deepEqual(