Source code

Revision control

Copy as Markdown

Other Tools

Test Info:

"use strict";
const { AddonRolloutAction } = ChromeUtils.importESModule(
"resource://normandy/actions/AddonRolloutAction.sys.mjs"
);
const { BaseAction } = ChromeUtils.importESModule(
"resource://normandy/actions/BaseAction.sys.mjs"
);
const { AddonRollouts } = ChromeUtils.importESModule(
"resource://normandy/lib/AddonRollouts.sys.mjs"
);
const { NormandyTestUtils } = ChromeUtils.importESModule(
);
// Test that a simple recipe enrolls as expected
decorate_task(
AddonRollouts.withTestMock(),
ensureAddonCleanup(),
withMockNormandyApi(),
withStub(TelemetryEnvironment, "setExperimentActive"),
withSendEventSpy(),
async function simple_recipe_enrollment({
mockNormandyApi,
setExperimentActiveStub,
sendEventSpy,
}) {
const recipe = {
id: 1,
arguments: {
slug: "test-rollout",
extensionApiId: 1,
},
};
mockNormandyApi.extensionDetails = {
[recipe.arguments.extensionApiId]: extensionDetailsFactory({
id: recipe.arguments.extensionApiId,
}),
};
const webExtStartupPromise =
AddonTestUtils.promiseWebExtensionStartup(FIXTURE_ADDON_ID);
const action = new AddonRolloutAction();
await action.processRecipe(recipe, BaseAction.suitability.FILTER_MATCH);
is(action.lastError, null, "lastError should be null");
await webExtStartupPromise;
// addon was installed
const addon = await AddonManager.getAddonByID(FIXTURE_ADDON_ID);
is(addon.id, FIXTURE_ADDON_ID, "addon should be installed");
// rollout was stored
const rollouts = await AddonRollouts.getAll();
Assert.deepEqual(
rollouts,
[
{
recipeId: recipe.id,
slug: "test-rollout",
state: AddonRollouts.STATE_ACTIVE,
extensionApiId: 1,
addonId: FIXTURE_ADDON_ID,
addonVersion: "1.0",
xpiUrl: FIXTURE_ADDON_DETAILS["normandydriver-a-1.0"].url,
xpiHash: FIXTURE_ADDON_DETAILS["normandydriver-a-1.0"].hash,
xpiHashAlgorithm: "sha256",
},
],
"Rollout should be stored in db"
);
sendEventSpy.assertEvents([
["enroll", "addon_rollout", recipe.arguments.slug],
]);
ok(
setExperimentActiveStub.calledWithExactly("test-rollout", "active", {
type: "normandy-addonrollout",
}),
"a telemetry experiment should be activated"
);
// cleanup installed addon
await addon.uninstall();
}
);
// Test that a rollout can update the addon
decorate_task(
AddonRollouts.withTestMock(),
ensureAddonCleanup(),
withMockNormandyApi(),
withSendEventSpy(),
async function update_rollout({ mockNormandyApi, sendEventSpy }) {
// first enrollment
const recipe = {
id: 1,
arguments: {
slug: "test-rollout",
extensionApiId: 1,
},
};
mockNormandyApi.extensionDetails = {
[recipe.arguments.extensionApiId]: extensionDetailsFactory({
id: recipe.arguments.extensionApiId,
}),
2: extensionDetailsFactory({
id: 2,
xpi: FIXTURE_ADDON_DETAILS["normandydriver-a-2.0"].url,
version: "2.0",
hash: FIXTURE_ADDON_DETAILS["normandydriver-a-2.0"].hash,
}),
};
let webExtStartupPromise =
AddonTestUtils.promiseWebExtensionStartup(FIXTURE_ADDON_ID);
let action = new AddonRolloutAction();
await action.processRecipe(recipe, BaseAction.suitability.FILTER_MATCH);
is(action.lastError, null, "lastError should be null");
await webExtStartupPromise;
// addon was installed
let addon = await AddonManager.getAddonByID(FIXTURE_ADDON_ID);
is(addon.id, FIXTURE_ADDON_ID, "addon should be installed");
is(addon.version, "1.0", "addon should be the correct version");
// update existing enrollment
recipe.arguments.extensionApiId = 2;
webExtStartupPromise =
AddonTestUtils.promiseWebExtensionStartup(FIXTURE_ADDON_ID);
action = new AddonRolloutAction();
await action.processRecipe(recipe, BaseAction.suitability.FILTER_MATCH);
is(action.lastError, null, "lastError should be null");
await webExtStartupPromise;
addon = await AddonManager.getAddonByID(FIXTURE_ADDON_ID);
is(addon.id, FIXTURE_ADDON_ID, "addon should still be installed");
is(addon.version, "2.0", "addon should be the correct version");
// rollout in the DB has been updated
const rollouts = await AddonRollouts.getAll();
Assert.deepEqual(
rollouts,
[
{
recipeId: recipe.id,
slug: "test-rollout",
state: AddonRollouts.STATE_ACTIVE,
extensionApiId: 2,
addonId: FIXTURE_ADDON_ID,
addonVersion: "2.0",
xpiUrl: FIXTURE_ADDON_DETAILS["normandydriver-a-2.0"].url,
xpiHash: FIXTURE_ADDON_DETAILS["normandydriver-a-2.0"].hash,
xpiHashAlgorithm: "sha256",
},
],
"Rollout should be stored in db"
);
sendEventSpy.assertEvents([
["enroll", "addon_rollout", "test-rollout"],
["update", "addon_rollout", "test-rollout"],
]);
// Cleanup
await addon.uninstall();
}
);
// Re-running a recipe does nothing
decorate_task(
AddonRollouts.withTestMock(),
ensureAddonCleanup(),
withMockNormandyApi(),
withSendEventSpy(),
async function rerun_recipe({ mockNormandyApi, sendEventSpy }) {
const recipe = {
id: 1,
arguments: {
slug: "test-rollout",
extensionApiId: 1,
},
};
mockNormandyApi.extensionDetails = {
[recipe.arguments.extensionApiId]: extensionDetailsFactory({
id: recipe.arguments.extensionApiId,
}),
};
const webExtStartupPromise =
AddonTestUtils.promiseWebExtensionStartup(FIXTURE_ADDON_ID);
let action = new AddonRolloutAction();
await action.processRecipe(recipe, BaseAction.suitability.FILTER_MATCH);
is(action.lastError, null, "lastError should be null");
await webExtStartupPromise;
// addon was installed
let addon = await AddonManager.getAddonByID(FIXTURE_ADDON_ID);
is(addon.id, FIXTURE_ADDON_ID, "addon should be installed");
is(addon.version, "1.0", "addon should be the correct version");
// re-run the same recipe
action = new AddonRolloutAction();
await action.processRecipe(recipe, BaseAction.suitability.FILTER_MATCH);
is(action.lastError, null, "lastError should be null");
addon = await AddonManager.getAddonByID(FIXTURE_ADDON_ID);
is(addon.id, FIXTURE_ADDON_ID, "addon should still be installed");
is(addon.version, "1.0", "addon should be the correct version");
// rollout in the DB has not been updated
const rollouts = await AddonRollouts.getAll();
Assert.deepEqual(
rollouts,
[
{
recipeId: recipe.id,
slug: "test-rollout",
state: AddonRollouts.STATE_ACTIVE,
extensionApiId: 1,
addonId: FIXTURE_ADDON_ID,
addonVersion: "1.0",
xpiUrl: FIXTURE_ADDON_DETAILS["normandydriver-a-1.0"].url,
xpiHash: FIXTURE_ADDON_DETAILS["normandydriver-a-1.0"].hash,
xpiHashAlgorithm: "sha256",
},
],
"Rollout should be stored in db"
);
sendEventSpy.assertEvents([["enroll", "addon_rollout", "test-rollout"]]);
// Cleanup
await addon.uninstall();
}
);
// Conflicting rollouts
decorate_task(
AddonRollouts.withTestMock(),
ensureAddonCleanup(),
withMockNormandyApi(),
withSendEventSpy(),
async function conflicting_rollout({ mockNormandyApi, sendEventSpy }) {
const recipe = {
id: 1,
arguments: {
slug: "test-rollout",
extensionApiId: 1,
},
};
mockNormandyApi.extensionDetails = {
[recipe.arguments.extensionApiId]: extensionDetailsFactory({
id: recipe.arguments.extensionApiId,
}),
};
const webExtStartupPromise =
AddonTestUtils.promiseWebExtensionStartup(FIXTURE_ADDON_ID);
let action = new AddonRolloutAction();
await action.processRecipe(recipe, BaseAction.suitability.FILTER_MATCH);
is(action.lastError, null, "lastError should be null");
await webExtStartupPromise;
// addon was installed
let addon = await AddonManager.getAddonByID(FIXTURE_ADDON_ID);
is(addon.id, FIXTURE_ADDON_ID, "addon should be installed");
is(addon.version, "1.0", "addon should be the correct version");
// update existing enrollment
action = new AddonRolloutAction();
await action.processRecipe(
{
...recipe,
id: 2,
arguments: {
...recipe.arguments,
slug: "test-conflict",
},
},
BaseAction.suitability.FILTER_MATCH
);
is(action.lastError, null, "lastError should be null");
addon = await AddonManager.getAddonByID(FIXTURE_ADDON_ID);
is(addon.id, FIXTURE_ADDON_ID, "addon should still be installed");
is(addon.version, "1.0", "addon should be the correct version");
// rollout in the DB has not been updated
const rollouts = await AddonRollouts.getAll();
Assert.deepEqual(
rollouts,
[
{
recipeId: recipe.id,
slug: "test-rollout",
state: AddonRollouts.STATE_ACTIVE,
extensionApiId: 1,
addonId: FIXTURE_ADDON_ID,
addonVersion: "1.0",
xpiUrl: FIXTURE_ADDON_DETAILS["normandydriver-a-1.0"].url,
xpiHash: FIXTURE_ADDON_DETAILS["normandydriver-a-1.0"].hash,
xpiHashAlgorithm: "sha256",
},
],
"Rollout should be stored in db"
);
sendEventSpy.assertEvents([
[
"enroll",
"addon_rollout",
"test-rollout",
{ addonId: FIXTURE_ADDON_ID },
],
[
"enrollFailed",
"addon_rollout",
"test-conflict",
{ reason: "conflict" },
],
]);
// Cleanup
await addon.uninstall();
}
);
// Add-on ID changed
decorate_task(
AddonRollouts.withTestMock(),
ensureAddonCleanup(),
withMockNormandyApi(),
withSendEventSpy(),
async function enroll_failed_addon_id_changed({
mockNormandyApi,
sendEventSpy,
}) {
const recipe = {
id: 1,
arguments: {
slug: "test-rollout",
extensionApiId: 1,
},
};
mockNormandyApi.extensionDetails = {
[recipe.arguments.extensionApiId]: extensionDetailsFactory({
id: recipe.arguments.extensionApiId,
}),
2: extensionDetailsFactory({
id: 2,
extension_id: "normandydriver-b@example.com",
xpi: FIXTURE_ADDON_DETAILS["normandydriver-b-1.0"].url,
version: "1.0",
hash: FIXTURE_ADDON_DETAILS["normandydriver-b-1.0"].hash,
}),
};
const webExtStartupPromise =
AddonTestUtils.promiseWebExtensionStartup(FIXTURE_ADDON_ID);
let action = new AddonRolloutAction();
await action.processRecipe(recipe, BaseAction.suitability.FILTER_MATCH);
is(action.lastError, null, "lastError should be null");
await webExtStartupPromise;
// addon was installed
let addon = await AddonManager.getAddonByID(FIXTURE_ADDON_ID);
is(addon.id, FIXTURE_ADDON_ID, "addon should be installed");
is(addon.version, "1.0", "addon should be the correct version");
// update existing enrollment
recipe.arguments.extensionApiId = 2;
action = new AddonRolloutAction();
await action.processRecipe(recipe, BaseAction.suitability.FILTER_MATCH);
is(action.lastError, null, "lastError should be null");
addon = await AddonManager.getAddonByID(FIXTURE_ADDON_ID);
is(addon.id, FIXTURE_ADDON_ID, "addon should still be installed");
is(addon.version, "1.0", "addon should be the correct version");
// rollout in the DB has not been updated
const rollouts = await AddonRollouts.getAll();
Assert.deepEqual(
rollouts,
[
{
recipeId: recipe.id,
slug: "test-rollout",
state: AddonRollouts.STATE_ACTIVE,
extensionApiId: 1,
addonId: FIXTURE_ADDON_ID,
addonVersion: "1.0",
xpiUrl: FIXTURE_ADDON_DETAILS["normandydriver-a-1.0"].url,
xpiHash: FIXTURE_ADDON_DETAILS["normandydriver-a-1.0"].hash,
xpiHashAlgorithm: "sha256",
},
],
"Rollout should be stored in db"
);
sendEventSpy.assertEvents([
["enroll", "addon_rollout", "test-rollout"],
[
"updateFailed",
"addon_rollout",
"test-rollout",
{ reason: "addon-id-changed" },
],
]);
// Cleanup
await addon.uninstall();
}
);
// Add-on upgrade required
decorate_task(
AddonRollouts.withTestMock(),
ensureAddonCleanup(),
withMockNormandyApi(),
withSendEventSpy(),
async function enroll_failed_upgrade_required({
mockNormandyApi,
sendEventSpy,
}) {
const recipe = {
id: 1,
arguments: {
slug: "test-rollout",
extensionApiId: 1,
},
};
mockNormandyApi.extensionDetails = {
[recipe.arguments.extensionApiId]: extensionDetailsFactory({
id: recipe.arguments.extensionApiId,
xpi: FIXTURE_ADDON_DETAILS["normandydriver-a-2.0"].url,
version: "2.0",
hash: FIXTURE_ADDON_DETAILS["normandydriver-a-2.0"].hash,
}),
2: extensionDetailsFactory({
id: 2,
}),
};
const webExtStartupPromise =
AddonTestUtils.promiseWebExtensionStartup(FIXTURE_ADDON_ID);
let action = new AddonRolloutAction();
await action.processRecipe(recipe, BaseAction.suitability.FILTER_MATCH);
is(action.lastError, null, "lastError should be null");
await webExtStartupPromise;
// addon was installed
let addon = await AddonManager.getAddonByID(FIXTURE_ADDON_ID);
is(addon.id, FIXTURE_ADDON_ID, "addon should be installed");
is(addon.version, "2.0", "addon should be the correct version");
// update existing enrollment
recipe.arguments.extensionApiId = 2;
action = new AddonRolloutAction();
await action.processRecipe(recipe, BaseAction.suitability.FILTER_MATCH);
is(action.lastError, null, "lastError should be null");
addon = await AddonManager.getAddonByID(FIXTURE_ADDON_ID);
is(addon.id, FIXTURE_ADDON_ID, "addon should still be installed");
is(addon.version, "2.0", "addon should be the correct version");
// rollout in the DB has not been updated
const rollouts = await AddonRollouts.getAll();
Assert.deepEqual(
rollouts,
[
{
recipeId: recipe.id,
slug: "test-rollout",
state: AddonRollouts.STATE_ACTIVE,
extensionApiId: 1,
addonId: FIXTURE_ADDON_ID,
addonVersion: "2.0",
xpiUrl: FIXTURE_ADDON_DETAILS["normandydriver-a-2.0"].url,
xpiHash: FIXTURE_ADDON_DETAILS["normandydriver-a-2.0"].hash,
xpiHashAlgorithm: "sha256",
},
],
"Rollout should be stored in db"
);
sendEventSpy.assertEvents([
["enroll", "addon_rollout", "test-rollout"],
[
"updateFailed",
"addon_rollout",
"test-rollout",
{ reason: "upgrade-required" },
],
]);
// Cleanup
await addon.uninstall();
}
);