зеркало из https://github.com/mozilla/gecko-dev.git
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:
Родитель
5ebe77b709
Коммит
5b085f6396
|
@ -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(
|
||||
|
|
Загрузка…
Ссылка в новой задаче