Source code
Revision control
Copy as Markdown
Other Tools
Test Info:
"use strict";
const { IndexedDB } = ChromeUtils.importESModule(
"resource://gre/modules/IndexedDB.sys.mjs"
);
const { PreferenceRollouts } = ChromeUtils.importESModule(
"resource://normandy/lib/PreferenceRollouts.sys.mjs"
);
const {
NormandyTestUtils: {
factories: { preferenceRolloutFactory },
},
} = ChromeUtils.importESModule(
);
decorate_task(
PreferenceRollouts.withTestMock(),
async function testGetMissing() {
ok(
!(await PreferenceRollouts.get("does-not-exist")),
"get should return null when the requested rollout does not exist"
);
}
);
decorate_task(
PreferenceRollouts.withTestMock(),
async function testAddUpdateAndGet() {
const rollout = {
slug: "test-rollout",
state: PreferenceRollouts.STATE_ACTIVE,
preferences: [],
};
await PreferenceRollouts.add(rollout);
let storedRollout = await PreferenceRollouts.get(rollout.slug);
Assert.deepEqual(
rollout,
storedRollout,
"get should retrieve a rollout from storage."
);
rollout.state = PreferenceRollouts.STATE_GRADUATED;
await PreferenceRollouts.update(rollout);
storedRollout = await PreferenceRollouts.get(rollout.slug);
Assert.deepEqual(
rollout,
storedRollout,
"get should retrieve a rollout from storage."
);
}
);
decorate_task(
PreferenceRollouts.withTestMock(),
async function testCantUpdateNonexistent() {
const rollout = {
slug: "test-rollout",
state: PreferenceRollouts.STATE_ACTIVE,
preferences: [],
};
await Assert.rejects(
PreferenceRollouts.update(rollout),
/doesn't already exist/,
"Update should fail if the rollout doesn't exist"
);
ok(
!(await PreferenceRollouts.has("test-rollout")),
"rollout should not have been added"
);
}
);
decorate_task(PreferenceRollouts.withTestMock(), async function testGetAll() {
const rollout1 = {
slug: "test-rollout-1",
preference: [],
};
const rollout2 = {
slug: "test-rollout-2",
preference: [],
};
await PreferenceRollouts.add(rollout1);
await PreferenceRollouts.add(rollout2);
const storedRollouts = await PreferenceRollouts.getAll();
Assert.deepEqual(
storedRollouts.sort((a, b) => a.id - b.id),
[rollout1, rollout2],
"getAll should return every stored rollout."
);
});
decorate_task(
PreferenceRollouts.withTestMock(),
async function testGetAllActive() {
const rollout1 = {
slug: "test-rollout-1",
state: PreferenceRollouts.STATE_ACTIVE,
};
const rollout2 = {
slug: "test-rollout-2",
state: PreferenceRollouts.STATE_GRADUATED,
};
const rollout3 = {
slug: "test-rollout-3",
state: PreferenceRollouts.STATE_ROLLED_BACK,
};
await PreferenceRollouts.add(rollout1);
await PreferenceRollouts.add(rollout2);
await PreferenceRollouts.add(rollout3);
const activeRollouts = await PreferenceRollouts.getAllActive();
Assert.deepEqual(
activeRollouts,
[rollout1],
"getAllActive should return only active rollouts"
);
}
);
decorate_task(PreferenceRollouts.withTestMock(), async function testHas() {
const rollout = {
slug: "test-rollout",
preferences: [],
};
await PreferenceRollouts.add(rollout);
ok(
await PreferenceRollouts.has(rollout.slug),
"has should return true for an existing rollout"
);
ok(
!(await PreferenceRollouts.has("does not exist")),
"has should return false for a missing rollout"
);
});
// recordOriginalValue should update storage to note the original values
decorate_task(
PreferenceRollouts.withTestMock(),
async function testRecordOriginalValuesUpdatesPreviousValues() {
await PreferenceRollouts.add({
slug: "test-rollout",
state: PreferenceRollouts.STATE_ACTIVE,
preferences: [
{ preferenceName: "test.pref", value: 2, previousValue: null },
],
});
await PreferenceRollouts.recordOriginalValues({ "test.pref": 1 });
Assert.deepEqual(
await PreferenceRollouts.getAll(),
[
{
slug: "test-rollout",
state: PreferenceRollouts.STATE_ACTIVE,
preferences: [
{ preferenceName: "test.pref", value: 2, previousValue: 1 },
],
},
],
"rollout in database should be updated"
);
}
);
// recordOriginalValue should graduate a study when all of its preferences are built-in
decorate_task(
withSendEventSpy(),
PreferenceRollouts.withTestMock(),
async function testRecordOriginalValuesGraduates({ sendEventSpy }) {
await PreferenceRollouts.add({
slug: "test-rollout",
state: PreferenceRollouts.STATE_ACTIVE,
preferences: [
{ preferenceName: "test.pref1", value: 2, previousValue: null },
{ preferenceName: "test.pref2", value: 2, previousValue: null },
],
});
// one pref being the same isn't enough to graduate
await PreferenceRollouts.recordOriginalValues({
"test.pref1": 1,
"test.pref2": 2,
});
let rollout = await PreferenceRollouts.get("test-rollout");
is(
rollout.state,
PreferenceRollouts.STATE_ACTIVE,
"rollouts should remain active when only one pref matches the built-in default"
);
sendEventSpy.assertEvents([]);
// both prefs is enough
await PreferenceRollouts.recordOriginalValues({
"test.pref1": 2,
"test.pref2": 2,
});
rollout = await PreferenceRollouts.get("test-rollout");
is(
rollout.state,
PreferenceRollouts.STATE_GRADUATED,
"rollouts should graduate when all prefs matches the built-in defaults"
);
sendEventSpy.assertEvents([
["graduate", "preference_rollout", "test-rollout", {}],
]);
}
);
// init should mark active rollouts in telemetry
decorate_task(
withStub(TelemetryEnvironment, "setExperimentActive"),
PreferenceRollouts.withTestMock(),
async function testInitTelemetry({ setExperimentActiveStub }) {
await PreferenceRollouts.add({
slug: "test-rollout-active-1",
state: PreferenceRollouts.STATE_ACTIVE,
});
await PreferenceRollouts.add({
slug: "test-rollout-active-2",
state: PreferenceRollouts.STATE_ACTIVE,
});
await PreferenceRollouts.add({
slug: "test-rollout-rolled-back",
state: PreferenceRollouts.STATE_ROLLED_BACK,
});
await PreferenceRollouts.add({
slug: "test-rollout-graduated",
state: PreferenceRollouts.STATE_GRADUATED,
});
await PreferenceRollouts.init();
Assert.deepEqual(
setExperimentActiveStub.args,
[
["test-rollout-active-1", "active", { type: "normandy-prefrollout" }],
["test-rollout-active-2", "active", { type: "normandy-prefrollout" }],
],
"init should set activate a telemetry experiment for active preferences"
);
}
);
// init should graduate rollouts in the graduation set
decorate_task(
withStub(TelemetryEnvironment, "setExperimentActive"),
withSendEventSpy(),
PreferenceRollouts.withTestMock({
graduationSet: new Set(["test-rollout"]),
rollouts: [
preferenceRolloutFactory({
slug: "test-rollout",
state: PreferenceRollouts.STATE_ACTIVE,
}),
],
}),
async function testInitGraduationSet({
setExperimentActiveStub,
sendEventSpy,
}) {
await PreferenceRollouts.init();
const newRollout = await PreferenceRollouts.get("test-rollout");
Assert.equal(
newRollout.state,
PreferenceRollouts.STATE_GRADUATED,
"the rollout should be graduated"
);
Assert.deepEqual(
setExperimentActiveStub.args,
[],
"setExperimentActive should not be called"
);
sendEventSpy.assertEvents([
[
"graduate",
"preference_rollout",
"test-rollout",
{ reason: "in-graduation-set" },
],
]);
}
);