forked from mirrors/gecko-dev
473 lines
16 KiB
JavaScript
473 lines
16 KiB
JavaScript
"use strict";
|
|
|
|
ChromeUtils.import("resource://gre/modules/Services.jsm", this);
|
|
ChromeUtils.import("resource://gre/modules/Preferences.jsm", this);
|
|
ChromeUtils.import("resource://gre/modules/TelemetryEnvironment.jsm", this);
|
|
ChromeUtils.import("resource://normandy/actions/PreferenceRolloutAction.jsm", this);
|
|
ChromeUtils.import("resource://normandy/lib/PreferenceRollouts.jsm", this);
|
|
ChromeUtils.import("resource://normandy/lib/TelemetryEvents.jsm", this);
|
|
|
|
// Test that a simple recipe enrolls as expected
|
|
decorate_task(
|
|
PreferenceRollouts.withTestMock,
|
|
withStub(TelemetryEnvironment, "setExperimentActive"),
|
|
withSendEventStub,
|
|
async function simple_recipe_enrollment(setExperimentActiveStub, sendEventStub) {
|
|
const recipe = {
|
|
id: 1,
|
|
arguments: {
|
|
slug: "test-rollout",
|
|
preferences: [
|
|
{preferenceName: "test.pref1", value: 1},
|
|
{preferenceName: "test.pref2", value: true},
|
|
{preferenceName: "test.pref3", value: "it works"},
|
|
],
|
|
},
|
|
};
|
|
|
|
const action = new PreferenceRolloutAction();
|
|
await action.runRecipe(recipe);
|
|
await action.finalize();
|
|
|
|
// rollout prefs are set
|
|
is(Services.prefs.getIntPref("test.pref1"), 1, "integer pref should be set");
|
|
is(Services.prefs.getBoolPref("test.pref2"), true, "boolean pref should be set");
|
|
is(Services.prefs.getCharPref("test.pref3"), "it works", "string pref should be set");
|
|
|
|
// start up prefs are set
|
|
is(Services.prefs.getIntPref("app.normandy.startupRolloutPrefs.test.pref1"), 1, "integer startup pref should be set");
|
|
is(Services.prefs.getBoolPref("app.normandy.startupRolloutPrefs.test.pref2"), true, "boolean startup pref should be set");
|
|
is(Services.prefs.getCharPref("app.normandy.startupRolloutPrefs.test.pref3"), "it works", "string startup pref should be set");
|
|
|
|
// rollout was stored
|
|
Assert.deepEqual(
|
|
await PreferenceRollouts.getAll(),
|
|
[{
|
|
slug: "test-rollout",
|
|
state: PreferenceRollouts.STATE_ACTIVE,
|
|
preferences: [
|
|
{preferenceName: "test.pref1", value: 1, previousValue: null},
|
|
{preferenceName: "test.pref2", value: true, previousValue: null},
|
|
{preferenceName: "test.pref3", value: "it works", previousValue: null},
|
|
],
|
|
}],
|
|
"Rollout should be stored in db"
|
|
);
|
|
|
|
Assert.deepEqual(
|
|
sendEventStub.args,
|
|
[["enroll", "preference_rollout", recipe.arguments.slug, {}]],
|
|
"an enrollment event should be sent"
|
|
);
|
|
Assert.deepEqual(
|
|
setExperimentActiveStub.args,
|
|
[["test-rollout", "active", {type: "normandy-prefrollout"}]],
|
|
"a telemetry experiment should be activated",
|
|
);
|
|
|
|
// Cleanup
|
|
Services.prefs.getDefaultBranch("").deleteBranch("test.pref1");
|
|
Services.prefs.getDefaultBranch("").deleteBranch("test.pref2");
|
|
Services.prefs.getDefaultBranch("").deleteBranch("test.pref3");
|
|
},
|
|
);
|
|
|
|
// Test that an enrollment's values can change, be removed, and be added
|
|
decorate_task(
|
|
PreferenceRollouts.withTestMock,
|
|
withSendEventStub,
|
|
async function update_enrollment(sendEventStub) {
|
|
// first enrollment
|
|
const recipe = {
|
|
id: 1,
|
|
arguments: {
|
|
slug: "test-rollout",
|
|
preferences: [
|
|
{preferenceName: "test.pref1", value: 1},
|
|
{preferenceName: "test.pref2", value: 1},
|
|
],
|
|
},
|
|
};
|
|
|
|
let action = new PreferenceRolloutAction();
|
|
await action.runRecipe(recipe);
|
|
await action.finalize();
|
|
|
|
const defaultBranch = Services.prefs.getDefaultBranch("");
|
|
is(defaultBranch.getIntPref("test.pref1"), 1, "pref1 should be set");
|
|
is(defaultBranch.getIntPref("test.pref2"), 1, "pref2 should be set");
|
|
is(Services.prefs.getIntPref("app.normandy.startupRolloutPrefs.test.pref1"), 1, "startup pref1 should be set");
|
|
is(Services.prefs.getIntPref("app.normandy.startupRolloutPrefs.test.pref2"), 1, "startup pref2 should be set");
|
|
|
|
// update existing enrollment
|
|
recipe.arguments.preferences = [
|
|
// pref1 is removed
|
|
// pref2's value is updated
|
|
{preferenceName: "test.pref2", value: 2},
|
|
// pref3 is added
|
|
{preferenceName: "test.pref3", value: 2},
|
|
];
|
|
action = new PreferenceRolloutAction();
|
|
await action.runRecipe(recipe);
|
|
await action.finalize();
|
|
|
|
is(Services.prefs.getPrefType("test.pref1"), Services.prefs.PREF_INVALID, "pref1 should be removed");
|
|
is(Services.prefs.getIntPref("test.pref2"), 2, "pref2 should be updated");
|
|
is(Services.prefs.getIntPref("test.pref3"), 2, "pref3 should be added");
|
|
|
|
is(Services.prefs.getPrefType(
|
|
"app.normandy.startupRolloutPrefs.test.pref1"),
|
|
Services.prefs.PREF_INVALID,
|
|
"startup pref1 should be removed",
|
|
);
|
|
is(
|
|
Services.prefs.getIntPref("app.normandy.startupRolloutPrefs.test.pref2"),
|
|
2,
|
|
"startup pref2 should be updated",
|
|
);
|
|
is(
|
|
Services.prefs.getIntPref("app.normandy.startupRolloutPrefs.test.pref3"),
|
|
2,
|
|
"startup pref3 should be added",
|
|
);
|
|
|
|
// rollout in the DB has been updated
|
|
Assert.deepEqual(
|
|
await PreferenceRollouts.getAll(),
|
|
[{
|
|
slug: "test-rollout",
|
|
state: PreferenceRollouts.STATE_ACTIVE,
|
|
preferences: [
|
|
{preferenceName: "test.pref2", value: 2, previousValue: null},
|
|
{preferenceName: "test.pref3", value: 2, previousValue: null},
|
|
],
|
|
}],
|
|
"Rollout should be updated in db"
|
|
);
|
|
|
|
Assert.deepEqual(
|
|
sendEventStub.args,
|
|
[
|
|
["enroll", "preference_rollout", "test-rollout", {}],
|
|
["update", "preference_rollout", "test-rollout", {previousState: "active"}],
|
|
],
|
|
"update event was sent"
|
|
);
|
|
|
|
// Cleanup
|
|
Services.prefs.getDefaultBranch("").deleteBranch("test.pref1");
|
|
Services.prefs.getDefaultBranch("").deleteBranch("test.pref2");
|
|
Services.prefs.getDefaultBranch("").deleteBranch("test.pref3");
|
|
},
|
|
);
|
|
|
|
// Test that a graduated rollout can be ungraduated
|
|
decorate_task(
|
|
PreferenceRollouts.withTestMock,
|
|
withSendEventStub,
|
|
async function ungraduate_enrollment(sendEventStub) {
|
|
Services.prefs.getDefaultBranch("").setIntPref("test.pref", 1);
|
|
await PreferenceRollouts.add({
|
|
slug: "test-rollout",
|
|
state: PreferenceRollouts.STATE_GRADUATED,
|
|
preferences: [{preferenceName: "test.pref", value: 1, previousValue: 1}],
|
|
});
|
|
|
|
let recipe = {
|
|
id: 1,
|
|
arguments: {
|
|
slug: "test-rollout",
|
|
preferences: [{preferenceName: "test.pref", value: 2}],
|
|
},
|
|
};
|
|
|
|
const action = new PreferenceRolloutAction();
|
|
await action.runRecipe(recipe);
|
|
await action.finalize();
|
|
|
|
is(Services.prefs.getIntPref("test.pref"), 2, "pref should be updated");
|
|
is(Services.prefs.getIntPref("app.normandy.startupRolloutPrefs.test.pref"), 2, "startup pref should be set");
|
|
|
|
// rollout in the DB has been ungraduated
|
|
Assert.deepEqual(
|
|
await PreferenceRollouts.getAll(),
|
|
[{
|
|
slug: "test-rollout",
|
|
state: PreferenceRollouts.STATE_ACTIVE,
|
|
preferences: [{preferenceName: "test.pref", value: 2, previousValue: 1}],
|
|
}],
|
|
"Rollout should be updated in db"
|
|
);
|
|
|
|
Assert.deepEqual(
|
|
sendEventStub.args,
|
|
[
|
|
["update", "preference_rollout", "test-rollout", {previousState: "graduated"}],
|
|
],
|
|
"correct events was sent"
|
|
);
|
|
|
|
// Cleanup
|
|
Services.prefs.getDefaultBranch("").deleteBranch("test.pref");
|
|
},
|
|
);
|
|
|
|
// Test when recipes conflict, only one is applied
|
|
decorate_task(
|
|
PreferenceRollouts.withTestMock,
|
|
withSendEventStub,
|
|
async function conflicting_recipes(sendEventStub) {
|
|
// create two recipes that each share a pref and have a unique pref.
|
|
const recipe1 = {
|
|
id: 1,
|
|
arguments: {
|
|
slug: "test-rollout-1",
|
|
preferences: [
|
|
{preferenceName: "test.pref1", value: 1},
|
|
{preferenceName: "test.pref2", value: 1},
|
|
],
|
|
},
|
|
};
|
|
const recipe2 = {
|
|
id: 2,
|
|
arguments: {
|
|
slug: "test-rollout-2",
|
|
preferences: [
|
|
{preferenceName: "test.pref1", value: 2},
|
|
{preferenceName: "test.pref3", value: 2},
|
|
],
|
|
},
|
|
};
|
|
|
|
// running both in the same session
|
|
let action = new PreferenceRolloutAction();
|
|
await action.runRecipe(recipe1);
|
|
await action.runRecipe(recipe2);
|
|
await action.finalize();
|
|
|
|
// running recipe2 in a separate session shouldn't change things
|
|
action = new PreferenceRolloutAction();
|
|
await action.runRecipe(recipe2);
|
|
await action.finalize();
|
|
|
|
is(Services.prefs.getIntPref("test.pref1"), 1, "pref1 is set to recipe1's value");
|
|
is(Services.prefs.getIntPref("test.pref2"), 1, "pref2 is set to recipe1's value");
|
|
is(Services.prefs.getPrefType("test.pref3"), Services.prefs.PREF_INVALID, "pref3 is not set");
|
|
|
|
is(Services.prefs.getIntPref("app.normandy.startupRolloutPrefs.test.pref1"), 1, "startup pref1 is set to recipe1's value");
|
|
is(Services.prefs.getIntPref("app.normandy.startupRolloutPrefs.test.pref2"), 1, "startup pref2 is set to recipe1's value");
|
|
is(Services.prefs.getPrefType("app.normandy.startupRolloutPrefs.test.pref3"), Services.prefs.PREF_INVALID, "startup pref3 is not set");
|
|
|
|
// only successful rollout was stored
|
|
Assert.deepEqual(
|
|
await PreferenceRollouts.getAll(),
|
|
[{
|
|
slug: "test-rollout-1",
|
|
state: PreferenceRollouts.STATE_ACTIVE,
|
|
preferences: [
|
|
{preferenceName: "test.pref1", value: 1, previousValue: null},
|
|
{preferenceName: "test.pref2", value: 1, previousValue: null},
|
|
],
|
|
}],
|
|
"Only recipe1's rollout should be stored in db",
|
|
);
|
|
|
|
Assert.deepEqual(
|
|
sendEventStub.args,
|
|
[
|
|
["enroll", "preference_rollout", recipe1.arguments.slug, {}],
|
|
["enrollFailed", "preference_rollout", recipe2.arguments.slug, {reason: "conflict", preference: "test.pref1"}],
|
|
["enrollFailed", "preference_rollout", recipe2.arguments.slug, {reason: "conflict", preference: "test.pref1"}],
|
|
]
|
|
);
|
|
|
|
// Cleanup
|
|
Services.prefs.getDefaultBranch("").deleteBranch("test.pref1");
|
|
Services.prefs.getDefaultBranch("").deleteBranch("test.pref2");
|
|
Services.prefs.getDefaultBranch("").deleteBranch("test.pref3");
|
|
},
|
|
);
|
|
|
|
// Test when the wrong value type is given, the recipe is not applied
|
|
decorate_task(
|
|
PreferenceRollouts.withTestMock,
|
|
withSendEventStub,
|
|
async function wrong_preference_value(sendEventStub) {
|
|
Services.prefs.getDefaultBranch("").setCharPref("test.pref", "not an int");
|
|
const recipe = {
|
|
id: 1,
|
|
arguments: {
|
|
slug: "test-rollout",
|
|
preferences: [{preferenceName: "test.pref", value: 1}],
|
|
},
|
|
};
|
|
|
|
const action = new PreferenceRolloutAction();
|
|
await action.runRecipe(recipe);
|
|
await action.finalize();
|
|
|
|
is(Services.prefs.getCharPref("test.pref"), "not an int", "the pref should not be modified");
|
|
is(Services.prefs.getPrefType("app.normandy.startupRolloutPrefs.test.pref"), Services.prefs.PREF_INVALID, "startup pref is not set");
|
|
|
|
Assert.deepEqual(await PreferenceRollouts.getAll(), [], "no rollout is stored in the db");
|
|
Assert.deepEqual(
|
|
sendEventStub.args,
|
|
[["enrollFailed", "preference_rollout", recipe.arguments.slug, {reason: "invalid type", preference: "test.pref"}]],
|
|
"an enrollment failed event should be sent",
|
|
);
|
|
|
|
// Cleanup
|
|
Services.prefs.getDefaultBranch("").deleteBranch("test.pref");
|
|
},
|
|
);
|
|
|
|
// Test that even when applying a rollout, user prefs are preserved
|
|
decorate_task(
|
|
PreferenceRollouts.withTestMock,
|
|
async function preserves_user_prefs() {
|
|
Services.prefs.getDefaultBranch("").setCharPref("test.pref", "builtin value");
|
|
Services.prefs.setCharPref("test.pref", "user value");
|
|
const recipe = {
|
|
id: 1,
|
|
arguments: {
|
|
slug: "test-rollout",
|
|
preferences: [{preferenceName: "test.pref", value: "rollout value"}],
|
|
}
|
|
};
|
|
|
|
const action = new PreferenceRolloutAction();
|
|
await action.runRecipe(recipe);
|
|
await action.finalize();
|
|
|
|
is(Services.prefs.getCharPref("test.pref"), "user value", "user branch value should be preserved");
|
|
is(Services.prefs.getDefaultBranch("").getCharPref("test.pref"), "rollout value", "default branch value should change");
|
|
|
|
Assert.deepEqual(
|
|
await PreferenceRollouts.getAll(),
|
|
[{
|
|
slug: "test-rollout",
|
|
state: PreferenceRollouts.STATE_ACTIVE,
|
|
preferences: [{preferenceName: "test.pref", value: "rollout value", previousValue: "builtin value"}],
|
|
}],
|
|
"the rollout is added to the db with the correct previous value",
|
|
);
|
|
|
|
// Cleanup
|
|
Services.prefs.getDefaultBranch("").deleteBranch("test.pref");
|
|
Services.prefs.deleteBranch("test.pref");
|
|
},
|
|
);
|
|
|
|
// Enrollment works for prefs with only a user branch value, and no default value.
|
|
decorate_task(
|
|
PreferenceRollouts.withTestMock,
|
|
async function simple_recipe_enrollment() {
|
|
const recipe = {
|
|
id: 1,
|
|
arguments: {
|
|
slug: "test-rollout",
|
|
preferences: [{preferenceName: "test.pref", value: 1}],
|
|
},
|
|
};
|
|
|
|
// Set a pref on the user branch only
|
|
Services.prefs.setIntPref("test.pref", 2);
|
|
|
|
const action = new PreferenceRolloutAction();
|
|
await action.runRecipe(recipe);
|
|
await action.finalize();
|
|
|
|
is(Services.prefs.getIntPref("test.pref"), 2, "original user branch value still visible");
|
|
is(Services.prefs.getDefaultBranch("").getIntPref("test.pref"), 1, "default branch was set");
|
|
is(Services.prefs.getIntPref("app.normandy.startupRolloutPrefs.test.pref"), 1, "startup pref is est");
|
|
|
|
// Cleanup
|
|
Services.prefs.getDefaultBranch("").deleteBranch("test.pref");
|
|
},
|
|
);
|
|
|
|
// 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(
|
|
PreferenceRollouts.withTestMock,
|
|
withSendEventStub,
|
|
async function(sendEventStub) {
|
|
const recipe = {
|
|
id: 1,
|
|
arguments: {
|
|
slug: "test-rollout",
|
|
preferences: [{preferenceName: "test.pref", value: 1}],
|
|
},
|
|
};
|
|
|
|
// run once
|
|
let action = new PreferenceRolloutAction();
|
|
await action.runRecipe(recipe);
|
|
await action.finalize();
|
|
|
|
// run a second time
|
|
action = new PreferenceRolloutAction();
|
|
await action.runRecipe(recipe);
|
|
await action.finalize();
|
|
|
|
Assert.deepEqual(
|
|
sendEventStub.args,
|
|
[["enroll", "preference_rollout", "test-rollout", {}]],
|
|
"only an enrollment event should be generated",
|
|
);
|
|
|
|
Assert.deepEqual(
|
|
await PreferenceRollouts.getAll(),
|
|
[{
|
|
slug: "test-rollout",
|
|
state: PreferenceRollouts.STATE_ACTIVE,
|
|
preferences: [{preferenceName: "test.pref", value: 1, previousValue: null}],
|
|
}],
|
|
"the DB should have the correct value stored for previousValue",
|
|
);
|
|
},
|
|
);
|
|
|
|
// New rollouts that are no-ops should send errors
|
|
decorate_task(
|
|
PreferenceRollouts.withTestMock,
|
|
withStub(TelemetryEnvironment, "setExperimentActive"),
|
|
withSendEventStub,
|
|
async function no_op_new_recipe(setExperimentActiveStub, sendEventStub) {
|
|
Services.prefs.getDefaultBranch("").setIntPref("test.pref", 1);
|
|
|
|
const recipe = {
|
|
id: 1,
|
|
arguments: {
|
|
slug: "test-rollout",
|
|
preferences: [{preferenceName: "test.pref", value: 1}],
|
|
},
|
|
};
|
|
|
|
const action = new PreferenceRolloutAction();
|
|
await action.runRecipe(recipe);
|
|
await action.finalize();
|
|
|
|
is(Services.prefs.getIntPref("test.pref"), 1, "pref should not change");
|
|
|
|
// start up pref isn't set
|
|
is(Services.prefs.getPrefType(
|
|
"app.normandy.startupRolloutPrefs.test.pref"),
|
|
Services.prefs.PREF_INVALID,
|
|
"startup pref1 should not be set",
|
|
);
|
|
|
|
// rollout was not stored
|
|
Assert.deepEqual(await PreferenceRollouts.getAll(), [], "Rollout should not be stored in db");
|
|
|
|
Assert.deepEqual(
|
|
sendEventStub.args,
|
|
[["enrollFailed", "preference_rollout", recipe.arguments.slug, {reason: "would-be-no-op"}]],
|
|
"an enrollment failure event should be sent"
|
|
);
|
|
Assert.deepEqual(setExperimentActiveStub.args, [], "a telemetry experiment should not be activated");
|
|
|
|
// Cleanup
|
|
Services.prefs.getDefaultBranch("").deleteBranch("test.pref");
|
|
},
|
|
);
|