Source code

Revision control

Copy as Markdown

Other Tools

Test Info: Warnings

"use strict";
const { IndexedDB } = ChromeUtils.importESModule(
"resource://gre/modules/IndexedDB.sys.mjs"
);
const { NormandyTestUtils } = ChromeUtils.importESModule(
);
const { addonStudyFactory, branchedAddonStudyFactory } =
NormandyTestUtils.factories;
// Initialize test utils
AddonTestUtils.initMochitest(this);
decorate_task(AddonStudies.withStudies(), async function testGetMissing() {
ok(
!(await AddonStudies.get("does-not-exist")),
"get returns null when the requested study does not exist"
);
});
decorate_task(
AddonStudies.withStudies([addonStudyFactory({ slug: "test-study" })]),
async function testGet({ addonStudies: [study] }) {
const storedStudy = await AddonStudies.get(study.recipeId);
Assert.deepEqual(study, storedStudy, "get retrieved a study from storage.");
}
);
decorate_task(
AddonStudies.withStudies([addonStudyFactory(), addonStudyFactory()]),
async function testGetAll({ addonStudies }) {
const storedStudies = await AddonStudies.getAll();
Assert.deepEqual(
new Set(storedStudies),
new Set(addonStudies),
"getAll returns every stored study."
);
}
);
decorate_task(
AddonStudies.withStudies([addonStudyFactory({ slug: "test-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.");
hasStudy = await AddonStudies.has("does-not-exist");
ok(
!hasStudy,
"has returns false for a study that doesn't exist in storage."
);
}
);
decorate_task(
AddonStudies.withStudies([
addonStudyFactory({ slug: "test-study1" }),
addonStudyFactory({ slug: "test-study2" }),
]),
async function testClear({ addonStudies: [study1, study2] }) {
const hasAll =
(await AddonStudies.has(study1.recipeId)) &&
(await AddonStudies.has(study2.recipeId));
ok(hasAll, "Before calling clear, both studies are in storage.");
await AddonStudies.clear();
const hasAny =
(await AddonStudies.has(study1.recipeId)) ||
(await AddonStudies.has(study2.recipeId));
ok(!hasAny, "After calling clear, all studies are removed from storage.");
}
);
decorate_task(
AddonStudies.withStudies([addonStudyFactory({ slug: "foo" })]),
async function testUpdate({ addonStudies: [study] }) {
Assert.deepEqual(await AddonStudies.get(study.recipeId), study);
const updatedStudy = {
...study,
slug: "bar",
};
await AddonStudies.update(updatedStudy);
Assert.deepEqual(await AddonStudies.get(study.recipeId), updatedStudy);
}
);
decorate_task(
AddonStudies.withStudies([
addonStudyFactory({
active: true,
addonId: "does.not.exist@example.com",
studyEndDate: null,
}),
addonStudyFactory({ active: true, addonId: "installed@example.com" }),
addonStudyFactory({
active: false,
addonId: "already.gone@example.com",
studyEndDate: new Date(2012, 1),
}),
]),
withSendEventSpy(),
withInstalledWebExtension(
{ id: "installed@example.com" },
{ expectUninstall: true }
),
async function testInit({
addonStudies: [activeUninstalledStudy, activeInstalledStudy, inactiveStudy],
sendEventSpy,
installedWebExtension: { addonId },
}) {
await AddonStudies.init();
const newActiveStudy = await AddonStudies.get(
activeUninstalledStudy.recipeId
);
ok(
!newActiveStudy.active,
"init marks studies as inactive if their add-on is not installed."
);
ok(
newActiveStudy.studyEndDate,
"init sets the study end date if a study's add-on is not installed."
);
let events = Services.telemetry.snapshotEvents(
Ci.nsITelemetry.DATASET_PRERELEASE_CHANNELS,
false
);
events = (events.parent || []).filter(e => e[1] == "normandy");
Assert.deepEqual(
events[0].slice(2), // strip timestamp and "normandy"
[
"unenroll",
"addon_study",
activeUninstalledStudy.slug,
{
addonId: activeUninstalledStudy.addonId,
addonVersion: activeUninstalledStudy.addonVersion,
reason: "uninstalled-sideload",
branch: AddonStudies.NO_BRANCHES_MARKER,
},
],
"AddonStudies.init() should send the correct telemetry event"
);
const newInactiveStudy = await AddonStudies.get(inactiveStudy.recipeId);
is(
newInactiveStudy.studyEndDate.getFullYear(),
2012,
"init does not modify inactive studies."
);
const newActiveInstalledStudy = await AddonStudies.get(
activeInstalledStudy.recipeId
);
Assert.deepEqual(
activeInstalledStudy,
newActiveInstalledStudy,
"init does not modify studies whose add-on is still installed."
);
// Only activeUninstalledStudy should have generated any events
ok(sendEventSpy.calledOnce, "no extra events should be generated");
// Clean up
const addon = await AddonManager.getAddonByID(addonId);
await addon.uninstall();
await TestUtils.topicObserved("shield-study-ended", (subject, message) => {
return message === `${activeInstalledStudy.recipeId}`;
});
}
);
// init should register telemetry experiments
decorate_task(
AddonStudies.withStudies([
branchedAddonStudyFactory({
active: true,
addonId: "installed1@example.com",
}),
branchedAddonStudyFactory({
active: true,
addonId: "installed2@example.com",
}),
]),
withInstalledWebExtensionSafe({ id: "installed1@example.com" }),
withInstalledWebExtension({ id: "installed2@example.com" }),
withStub(TelemetryEnvironment, "setExperimentActive"),
async function testInit({ addonStudies, setExperimentActiveStub }) {
await AddonStudies.init();
Assert.deepEqual(
setExperimentActiveStub.args,
[
[
addonStudies[0].slug,
addonStudies[0].branch,
{
type: "normandy-addonstudy",
},
],
[
addonStudies[1].slug,
addonStudies[1].branch,
{
type: "normandy-addonstudy",
},
],
],
"Add-on studies are registered in Telemetry by AddonStudies.init"
);
}
);
// Test that AddonStudies.init() ends studies that have been uninstalled
decorate_task(
AddonStudies.withStudies([
addonStudyFactory({
active: true,
addonId: "installed@example.com",
studyEndDate: null,
}),
]),
withInstalledWebExtension(
{ id: "installed@example.com" },
{ expectUninstall: true }
),
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}`;
});
const newStudy = await AddonStudies.get(study.recipeId);
ok(
!newStudy.active,
"Studies are marked as inactive when their add-on is uninstalled."
);
ok(
newStudy.studyEndDate,
"The study end date is set when the add-on for the study is uninstalled."
);
}
);
decorate_task(
AddonStudies.withStudies([
NormandyTestUtils.factories.addonStudyFactory({ active: true }),
NormandyTestUtils.factories.branchedAddonStudyFactory(),
]),
async function testRemoveOldAddonStudies({
addonStudies: [noBranchStudy, branchedStudy],
}) {
// pre check, both studies are active
const preActiveIds = (await AddonStudies.getAllActive()).map(
addon => addon.recipeId
);
Assert.deepEqual(
preActiveIds,
[noBranchStudy.recipeId, branchedStudy.recipeId],
"Both studies should be active"
);
// run the migration
await AddonStudies.migrations.migration02RemoveOldAddonStudyAction();
// The unbrached study should end
const postActiveIds = (await AddonStudies.getAllActive()).map(
addon => addon.recipeId
);
Assert.deepEqual(
postActiveIds,
[branchedStudy.recipeId],
"The unbranched study should end"
);
// But both studies should still be present
const postAllIds = (await AddonStudies.getAll()).map(
addon => addon.recipeId
);
Assert.deepEqual(
postAllIds,
[noBranchStudy.recipeId, branchedStudy.recipeId],
"Both studies should still be present"
);
}
);