1
0
Fork 0
firefox/toolkit/components/nimbus/test/unit/test_ExperimentStore.js
Daniel Baumann 5e9a113729
Adding upstream version 140.0.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
2025-06-25 09:37:52 +02:00

1045 lines
27 KiB
JavaScript

"use strict";
const { ExperimentStore } = ChromeUtils.importESModule(
"resource://nimbus/lib/ExperimentStore.sys.mjs"
);
const { ProfilesDatastoreService } = ChromeUtils.importESModule(
"moz-src:///toolkit/profile/ProfilesDatastoreService.sys.mjs"
);
const { SYNC_DATA_PREF_BRANCH, SYNC_DEFAULTS_PREF_BRANCH } = ExperimentStore;
add_setup(function () {
Services.fog.initializeFOG();
});
async function setupTest({ ...args } = {}) {
const ctx = await NimbusTestUtils.setupTest({ ...args });
return {
...ctx,
store: ctx.manager.store,
};
}
add_task(async function test_sharedDataMap_key() {
const store = new ExperimentStore();
// Outside of tests we use sharedDataKey for the profile dir filepath
// where we store experiments
Assert.ok(store._sharedDataKey, "Make sure it's defined");
});
add_task(async function test_usageBeforeInitialization() {
const { store, initExperimentAPI, cleanup } = await setupTest({
init: false,
});
const experiment = NimbusTestUtils.factories.experiment("foo");
Assert.equal(store.getAll().length, 0, "It should not fail");
await initExperimentAPI();
store.addEnrollment(experiment);
Assert.equal(
store.getExperimentForFeature("testFeature"),
experiment,
"should return a matching experiment for the given feature"
);
store.updateExperiment(experiment.slug, { active: false });
await cleanup();
});
add_task(async function test_initOnUpdateEventsFire() {
let storePath;
{
const store = NimbusTestUtils.stubs.store();
await store.init();
store.addEnrollment(
NimbusTestUtils.factories.experiment.withFeatureConfig("testFeature-1", {
featureId: "testFeature",
})
);
store.addEnrollment(
NimbusTestUtils.factories.rollout.withFeatureConfig("testFeature-2", {
featureId: "testFeature",
})
);
store.addEnrollment(
NimbusTestUtils.factories.experiment.withFeatureConfig(
"nimbus-qa-1",
{ featureId: "nimbus-qa-1" },
{ active: false }
)
);
store.addEnrollment(
NimbusTestUtils.factories.rollout.withFeatureConfig(
"nimbus-qa-2",
{ featureId: "nimbus-qa-2" },
{ active: false }
)
);
store.addEnrollment(
NimbusTestUtils.factories.experiment.withFeatureConfig("coenroll-1", {
featureId: "no-feature-firefox-desktop",
})
);
store.addEnrollment(
NimbusTestUtils.factories.experiment.withFeatureConfig("coenroll-2", {
featureId: "no-feature-firefox-desktop",
})
);
store.addEnrollment(
NimbusTestUtils.factories.rollout.withFeatureConfig("coenroll-3", {
featureId: "no-feature-firefox-desktop",
})
);
store.addEnrollment(
NimbusTestUtils.factories.rollout.withFeatureConfig("coenroll-4", {
featureId: "no-feature-firefox-desktop",
})
);
storePath = await NimbusTestUtils.saveStore(store);
}
const { sandbox, initExperimentAPI, cleanup } = await setupTest({
storePath,
init: false,
});
const onFeatureUpdate = sandbox.stub();
NimbusFeatures.testFeature.onUpdate(onFeatureUpdate);
NimbusFeatures["nimbus-qa-1"].onUpdate(onFeatureUpdate);
NimbusFeatures["nimbus-qa-2"].onUpdate(onFeatureUpdate);
NimbusFeatures["no-feature-firefox-desktop"].onUpdate(onFeatureUpdate);
await initExperimentAPI();
Assert.ok(
onFeatureUpdate.calledWithExactly(
"featureUpdate:testFeature",
"feature-enrollments-loaded"
)
);
Assert.ok(
onFeatureUpdate.calledWithExactly(
"featureUpdate:no-feature-firefox-desktop",
"feature-enrollments-loaded"
)
);
Assert.equal(
onFeatureUpdate.callCount,
2,
"onFeatureUpdate called once per active feature ID"
);
NimbusFeatures.testFeature.offUpdate(onFeatureUpdate);
await NimbusTestUtils.cleanupManager([
"testFeature-1",
"testFeature-2",
"coenroll-1",
"coenroll-2",
"coenroll-3",
"coenroll-4",
]);
await cleanup();
});
add_task(async function test_getExperimentForGroup() {
const { store, cleanup } = await setupTest();
const experiment = NimbusTestUtils.factories.experiment.withFeatureConfig(
"foo",
{
featureId: "purple",
}
);
store.addEnrollment(NimbusTestUtils.factories.experiment("bar"));
store.addEnrollment(experiment);
Assert.equal(
store.getExperimentForFeature("purple"),
experiment,
"should return a matching experiment for the given feature"
);
store.updateExperiment("foo", { active: false });
store.updateExperiment("bar", { active: false });
await cleanup();
});
add_task(async function test_hasExperimentForFeature() {
const { store, cleanup } = await setupTest();
store.addEnrollment(
NimbusTestUtils.factories.experiment.withFeatureConfig("foo", {
featureId: "green",
})
);
store.addEnrollment(
NimbusTestUtils.factories.experiment.withFeatureConfig("foo2", {
featureId: "yellow",
})
);
store.addEnrollment(
NimbusTestUtils.factories.experiment.withFeatureConfig(
"bar_expired",
{ featureId: "purple" },
{
active: false,
}
)
);
Assert.equal(
store.hasExperimentForFeature(),
false,
"should return false if the input is empty"
);
Assert.equal(
store.hasExperimentForFeature(undefined),
false,
"should return false if the input is undefined"
);
Assert.equal(
store.hasExperimentForFeature("green"),
true,
"should return true if there is an experiment with any of the given groups"
);
Assert.equal(
store.hasExperimentForFeature("purple"),
false,
"should return false if there is a non-active experiment with the given groups"
);
store.updateExperiment("foo", { active: false });
store.updateExperiment("foo2", { active: false });
await cleanup();
});
add_task(async function test_getAll() {
const { store, cleanup } = await setupTest();
store.addEnrollment(
NimbusTestUtils.factories.experiment("foo", { active: false })
);
store.addEnrollment(
NimbusTestUtils.factories.experiment("bar", { active: false })
);
store.addEnrollment(
NimbusTestUtils.factories.experiment("baz", { active: false })
);
store.addEnrollment(
NimbusTestUtils.factories.experiment("qux", { active: true })
);
store.addEnrollment(
NimbusTestUtils.factories.rollout("quux", { active: false })
);
store.addEnrollment(
NimbusTestUtils.factories.rollout("corge", { active: false })
);
store.addEnrollment(
NimbusTestUtils.factories.rollout("grault", { active: false })
);
store.addEnrollment(
NimbusTestUtils.factories.rollout("garply", { active: true })
);
Assert.deepEqual(
store.getAll().map(e => e.slug),
["foo", "bar", "baz", "qux", "quux", "corge", "grault", "garply"],
".getAll() should return all experiments"
);
Assert.deepEqual(
store.getAllActiveExperiments().map(e => e.slug),
["qux"],
"getAllActiveExperiments() should return all experiments that are active"
);
Assert.deepEqual(
store.getAllActiveRollouts().map(e => e.slug),
["garply"],
"getAllActiveRollouts() should return all experiments that are active"
);
store.updateExperiment("qux", { active: false });
store.updateExperiment("garply", { active: false });
await cleanup();
});
add_task(async function test_addEnrollment() {
const { store, cleanup } = await setupTest();
const experiment = NimbusTestUtils.factories.experiment("experiment");
const rollout = NimbusTestUtils.factories.experiment("rollout");
store.addEnrollment(experiment);
store.addEnrollment(rollout);
Assert.equal(
store.get("experiment"),
experiment,
"should save experiment by slug"
);
Assert.equal(store.get("rollout"), rollout, "should save experiment by slug");
store.updateExperiment("experiment", { active: false });
store.updateExperiment("rollout", { active: false });
await cleanup();
});
add_task(async function test_updateExperiment() {
const { store, cleanup } = await setupTest();
const features = [{ featureId: "cfr", value: {} }];
const experiment = Object.freeze(
NimbusTestUtils.factories.experiment("foo", {
branch: {
slug: "treatment",
ratio: 1,
features,
},
active: true,
})
);
store.addEnrollment(experiment);
store.updateExperiment(experiment.slug, { active: false });
const actual = store.get("foo");
Assert.equal(actual.active, false, "should change updated props");
Assert.deepEqual(
actual.branch.features,
features,
"should not update other props"
);
store.updateExperiment("foo", { active: false });
await cleanup();
});
add_task(async function test_sync_access_before_init() {
const { store, cleanup } = await setupTest();
Assert.equal(store.getAll().length, 0, "Start with an empty store");
const experiment = NimbusTestUtils.factories.experiment.withFeatureConfig(
"foo",
{
featureId: "newtab",
}
);
store.addEnrollment(experiment);
const prefValue = JSON.parse(
Services.prefs.getStringPref(`${SYNC_DATA_PREF_BRANCH}newtab`)
);
Assert.ok(prefValue, "Parsed stored experiment");
Assert.equal(prefValue.slug, experiment.slug, "Got back the experiment");
// New un-initialized store that should read the pref value
const newStore = NimbusTestUtils.stubs.store();
Assert.equal(
newStore.getExperimentForFeature("newtab").slug,
"foo",
"Returns experiment from pref"
);
store.updateExperiment("foo", { active: false });
await cleanup();
await NimbusTestUtils.assert.storeIsEmpty(newStore);
});
add_task(async function test_sync_access_update() {
const { store, cleanup } = await setupTest();
const experiment = NimbusTestUtils.factories.experiment.withFeatureConfig(
"foo",
{
featureId: "aboutwelcome",
}
);
store.addEnrollment(experiment);
store.updateExperiment("foo", {
branch: {
...experiment.branch,
features: [
{
featureId: "aboutwelcome",
value: { bar: "bar", enabled: true },
},
],
},
});
const newStore = NimbusTestUtils.stubs.store();
const cachedExperiment = newStore.getExperimentForFeature("aboutwelcome");
Assert.ok(cachedExperiment, "Got back 1 experiment");
Assert.deepEqual(
// `branch.feature` and not `features` because for sync access (early startup)
// experiments we only store the `isEarlyStartup` feature
cachedExperiment.branch.features[0].value,
{ bar: "bar", enabled: true },
"Got updated value"
);
store.updateExperiment("foo", { active: false });
await cleanup();
await NimbusTestUtils.assert.storeIsEmpty(newStore);
});
add_task(async function test_sync_features_only() {
const { store, cleanup } = await setupTest();
store.addEnrollment(
NimbusTestUtils.factories.experiment.withFeatureConfig("foo", {
featureId: "cfr",
})
);
const newStore = NimbusTestUtils.stubs.store();
Assert.equal(
newStore.getAll().length,
0,
"cfr is not a sync access experiment"
);
store.updateExperiment("foo", { active: false });
await cleanup();
});
add_task(async function test_sync_access_unenroll() {
const { store, cleanup } = await setupTest();
let experiment = NimbusTestUtils.factories.experiment.withFeatureConfig(
"foo",
{
featureId: "aboutwelcome",
}
);
await store.init();
store.addEnrollment(experiment);
store.updateExperiment("foo", { active: false });
const newStore = NimbusTestUtils.stubs.store();
Assert.equal(newStore.getAll().length, 0, "Unenrolled experiment is deleted");
await cleanup();
});
add_task(async function test_sync_access_unenroll_2() {
const { store, cleanup } = await setupTest();
let experiment1 = NimbusTestUtils.factories.experiment.withFeatureConfig(
"foo",
{ featureId: "newtab" }
);
let experiment2 = NimbusTestUtils.factories.experiment.withFeatureConfig(
"bar",
{ featureId: "aboutwelcome" }
);
await store.init();
store.addEnrollment(experiment1);
store.addEnrollment(experiment2);
Assert.equal(store.getAll().length, 2, "2/2 experiments");
const newStore = NimbusTestUtils.stubs.store();
Assert.ok(
newStore.getExperimentForFeature("aboutwelcome"),
"Fetches experiment from pref cache even before init (aboutwelcome)"
);
store.updateExperiment("bar", { active: false });
Assert.ok(
newStore.getExperimentForFeature("newtab").slug,
"Fetches experiment from pref cache even before init (newtab)"
);
Assert.ok(
!newStore.getExperimentForFeature("aboutwelcome")?.slug,
"Experiment was updated and should not be found"
);
store.updateExperiment("foo", { active: false });
Assert.ok(
!newStore.getExperimentForFeature("newtab")?.slug,
"Unenrolled from 2/2 experiments"
);
Assert.equal(
Services.prefs.getStringPref(`${SYNC_DATA_PREF_BRANCH}newtab`, "").length,
0,
"Cleared pref 1"
);
Assert.equal(
Services.prefs.getStringPref(`${SYNC_DATA_PREF_BRANCH}aboutwelcome`, "")
.length,
0,
"Cleared pref 2"
);
await cleanup();
});
add_task(async function test_getRolloutForFeature_fromStore() {
const { store, cleanup } = await setupTest();
const rollout = NimbusTestUtils.factories.rollout("foo");
await store.init();
store.addEnrollment(rollout);
Assert.deepEqual(
store.getRolloutForFeature(rollout.featureIds[0]),
rollout,
"Should return back the same rollout"
);
store.updateExperiment("foo", { active: false });
await cleanup();
});
add_task(async function test_getRolloutForFeature_fromSyncCache() {
const { store, cleanup } = await setupTest();
const rollout = NimbusTestUtils.factories.rollout.withFeatureConfig("foo", {
featureId: "aboutwelcome",
value: { enabled: true },
});
store.addEnrollment(rollout);
// New uninitialized store will return data from sync cache
// before init
const newStore = NimbusTestUtils.stubs.store();
Assert.ok(
Services.prefs.getStringPref(`${SYNC_DEFAULTS_PREF_BRANCH}aboutwelcome`),
"Sync cache is set"
);
Assert.equal(
newStore.getRolloutForFeature(rollout.featureIds[0]).slug,
rollout.slug,
"Should return back the same rollout"
);
Assert.deepEqual(
newStore.getRolloutForFeature(rollout.featureIds[0]).branch.features[0],
rollout.branch.features[0],
"Should return back the same feature"
);
store.updateExperiment("foo", { active: false });
await cleanup();
});
add_task(async function test_remoteRollout() {
const { store, initExperimentAPI, cleanup } = await setupTest({
init: false,
});
const featureUpdateStub = sinon.stub();
const rollout = NimbusTestUtils.factories.rollout.withFeatureConfig("foo", {
featureId: "aboutwelcome",
value: { enabled: true },
});
store.on("featureUpdate:aboutwelcome", featureUpdateStub);
await initExperimentAPI();
store.addEnrollment(rollout);
Assert.ok(
Services.prefs.getStringPref(`${SYNC_DEFAULTS_PREF_BRANCH}aboutwelcome`),
"Sync cache is set"
);
store.updateExperiment(rollout.slug, { active: false });
Assert.ok(featureUpdateStub.calledTwice, "Called for add and remove");
Assert.ok(
store.get(rollout.slug),
"Rollout is still in the store just not active"
);
Assert.ok(
!store.getRolloutForFeature("aboutwelcome"),
"Feature rollout should not exist"
);
Assert.ok(
!Services.prefs.getStringPref(
`${SYNC_DEFAULTS_PREF_BRANCH}aboutwelcome`,
""
),
"Sync cache is cleared"
);
await cleanup();
});
add_task(async function test_syncDataStore_setDefault() {
const { store, cleanup } = await setupTest();
Assert.equal(
Services.prefs.getStringPref(
`${SYNC_DEFAULTS_PREF_BRANCH}aboutwelcome`,
""
),
"",
"Pref is empty"
);
const rollout = NimbusTestUtils.factories.rollout.withFeatureConfig("foo", {
featureId: "aboutwelcome",
value: { remote: true },
});
store.addEnrollment(rollout);
Assert.ok(
Services.prefs.getStringPref(`${SYNC_DEFAULTS_PREF_BRANCH}aboutwelcome`),
"Stored in pref"
);
store.updateExperiment("foo", { active: false });
await cleanup();
});
add_task(async function test_syncDataStore_getDefault() {
const { store, cleanup } = await setupTest();
const rollout = NimbusTestUtils.factories.rollout.withFeatureConfig(
"aboutwelcome-slug",
{ featureId: "aboutwelcome", value: { remote: true } }
);
await store.addEnrollment(rollout);
Assert.ok(
Services.prefs.getStringPref(`${SYNC_DEFAULTS_PREF_BRANCH}aboutwelcome`)
);
const restoredRollout = store.getRolloutForFeature("aboutwelcome");
Assert.ok(restoredRollout);
Assert.ok(
restoredRollout.branch.features[0].value.remote,
"Restore data from pref"
);
store.updateExperiment(rollout.slug, { active: false });
await cleanup();
});
add_task(async function test_addEnrollment_rollout() {
const { sandbox, store, initExperimentAPI, cleanup } = await setupTest({
init: false,
});
const stub = sandbox.stub();
const value = { bar: true };
const rollout = NimbusTestUtils.factories.rollout.withFeatureConfig("foo", {
featureId: "aboutwelcome",
value,
});
store._onFeatureUpdate("aboutwelcome", stub);
await initExperimentAPI();
store.addEnrollment(rollout);
Assert.deepEqual(
store.getRolloutForFeature("aboutwelcome"),
rollout,
"should return the stored value"
);
Assert.equal(stub.callCount, 1, "Called once on update");
Assert.equal(
stub.firstCall.args[1],
"rollout-updated",
"Called for correct reason"
);
store.updateExperiment("foo", { active: false });
await cleanup();
});
add_task(async function test_storeValuePerPref_returnsSameValue_allTypes() {
const cleanupFeature = NimbusTestUtils.addTestFeatures(
new ExperimentFeature("purple", {
isEarlyStartup: true,
variables: {
string: { type: "string" },
bool: { type: "boolean" },
array: { type: "json" },
number1: { type: "int" },
number2: { type: "int" },
number3: { type: "int" },
json: { type: "json" },
},
})
);
const { store, cleanup } = await setupTest();
const experiment = NimbusTestUtils.factories.experiment.withFeatureConfig(
"foo",
{
// Ensure it gets saved to prefs
featureId: "purple",
value: {
string: "string",
bool: true,
array: [1, 2, 3],
number1: 42,
number2: 0,
number3: -5,
json: { jsonValue: true },
},
}
);
store.addEnrollment(experiment);
const branch = Services.prefs.getBranch(`${SYNC_DATA_PREF_BRANCH}purple.`);
const newStore = NimbusTestUtils.stubs.store();
Assert.deepEqual(
newStore.getExperimentForFeature("purple").branch.features[0].value,
experiment.branch.features[0].value,
"Returns the same value"
);
// Cleanup
store.updateExperiment(experiment.slug, { active: false });
Assert.ok(
!Services.prefs.getStringPref(`${SYNC_DATA_PREF_BRANCH}purple`, ""),
"Experiment cleanup"
);
Assert.deepEqual(branch.getChildList(""), [], "Variables are also removed");
await cleanup();
cleanupFeature();
});
add_task(async function test_cleanupOldRecipes() {
const store = NimbusTestUtils.stubs.store();
await store.init({ cleanupOldRecipes: false });
const NOW = Date.now();
const SIX_HOURS = 6 * 3600 * 1000;
const ONE_DAY = 4 * SIX_HOURS;
const ONE_YEAR = 365.25 * 24 * 3600 * 1000;
const ONE_MONTH = Math.floor(ONE_YEAR / 12);
const active = NimbusTestUtils.factories.experiment("active-6hrs", {
active: true,
lastSeen: new Date(NOW - SIX_HOURS).toJSON(),
});
const inactiveToday = NimbusTestUtils.factories.experiment(
"inactive-recent",
{
active: false,
unenrollReason: "unknown",
lastSeen: new Date(NOW - SIX_HOURS).toJSON(),
}
);
const inactiveSixMonths = NimbusTestUtils.factories.experiment(
"inactive-6mo",
{
active: false,
unenrollReason: "unknown",
lastSeen: new Date(NOW - 6 * ONE_MONTH).toJSON(),
}
);
const inactiveUnderTwelveMonths = NimbusTestUtils.factories.experiment(
"inactive-under-12mo",
{
active: false,
unenrollReason: "unknown",
lastSeen: new Date(NOW - ONE_YEAR + ONE_DAY).toJSON(),
}
);
const inactiveOverTwelveMonths = NimbusTestUtils.factories.experiment(
"inactive-over-12mo",
{
active: false,
unenrollReason: "unknown",
lastSeen: new Date(NOW - ONE_YEAR - ONE_DAY).toJSON(),
}
);
const inactiveNoLastSeen = NimbusTestUtils.factories.experiment(
"inactive-unknown",
{
active: false,
unenrollReason: "unknown",
}
);
delete inactiveNoLastSeen.lastSeen;
store.addEnrollment(active);
await store._addEnrollmentToDatabase(
active,
NimbusTestUtils.factories.recipe.withFeatureConfig(
active.slug,
active.branch.features[0]
)
);
store.addEnrollment(inactiveToday);
await store._addEnrollmentToDatabase(
inactiveToday,
NimbusTestUtils.factories.recipe.withFeatureConfig(
inactiveToday.slug,
inactiveToday.branch.features[0]
)
);
store.addEnrollment(inactiveSixMonths);
await store._addEnrollmentToDatabase(
inactiveSixMonths,
NimbusTestUtils.factories.recipe.withFeatureConfig(
inactiveSixMonths.slug,
inactiveSixMonths.branch.features[0]
)
);
store.addEnrollment(inactiveUnderTwelveMonths);
await store._addEnrollmentToDatabase(
inactiveUnderTwelveMonths,
NimbusTestUtils.factories.recipe.withFeatureConfig(
inactiveUnderTwelveMonths.slug,
inactiveUnderTwelveMonths.branch.features[0]
)
);
store.addEnrollment(inactiveOverTwelveMonths);
await store._addEnrollmentToDatabase(
inactiveOverTwelveMonths,
NimbusTestUtils.factories.recipe.withFeatureConfig(
inactiveOverTwelveMonths.slug,
inactiveOverTwelveMonths.branch.features[0]
)
);
// There is a NOT NULL constraint that prevents adding this enrollment to the
// database.
store.addEnrollment(inactiveNoLastSeen);
// Insert a row belonging to another profile.
const otherProfileId = Services.uuid.generateUUID().toString().slice(1, -1);
const conn = await ProfilesDatastoreService.getConnection();
await conn.execute(
`
INSERT INTO NimbusEnrollments VALUES(
null,
:profileId,
:slug,
:branchSlug,
null,
:active,
:unenrollReason,
:lastSeen,
null,
null,
:source
);
`,
{
profileId: otherProfileId,
slug: inactiveOverTwelveMonths.slug,
branchSlug: inactiveOverTwelveMonths.branch.slug,
active: false,
unenrollReason: inactiveOverTwelveMonths.unenrollReason,
lastSeen: inactiveOverTwelveMonths.lastSeen,
source: inactiveOverTwelveMonths.source,
}
);
await store._cleanupOldRecipes();
Assert.equal(
store.get(inactiveOverTwelveMonths.slug),
null,
"Expired enrollment removed from in memory store"
);
Assert.equal(
store.get(inactiveNoLastSeen.slug),
null,
"invalid enrollment removed from the store"
);
await NimbusTestUtils.assert.enrollmentExists(active.slug, { active: true });
await NimbusTestUtils.assert.enrollmentExists(inactiveToday.slug, {
active: false,
});
await NimbusTestUtils.assert.enrollmentExists(inactiveSixMonths.slug, {
active: false,
});
await NimbusTestUtils.assert.enrollmentExists(
inactiveUnderTwelveMonths.slug,
{ active: false }
);
await NimbusTestUtils.assert.enrollmentDoesNotExist(
inactiveOverTwelveMonths.slug
);
// Rows in the other profile should not have been changed.
await NimbusTestUtils.assert.enrollmentExists(inactiveOverTwelveMonths.slug, {
active: false,
profileId: otherProfileId,
});
store.updateExperiment(active.slug, { active: false });
await store._deactivateEnrollmentInDatabase(active.slug);
await NimbusTestUtils.assert.storeIsEmpty(store);
});
add_task(async function test_restore() {
let storePath;
{
const store = NimbusTestUtils.stubs.store();
await store.init();
store.addEnrollment(NimbusTestUtils.factories.experiment("experiment"));
store.addEnrollment(
NimbusTestUtils.factories.rollout("rollout", { active: true })
);
storePath = await NimbusTestUtils.saveStore(store);
}
const { store, cleanup } = await setupTest({ storePath });
Assert.ok(store.get("experiment"));
Assert.ok(store.get("rollout"));
await NimbusTestUtils.cleanupManager(["experiment", "rollout"]);
await cleanup();
});
add_task(async function test_restoreDatabaseConsistency() {
Services.fog.testResetFOG();
let storePath;
{
const store = await NimbusTestUtils.stubs.store();
await store.init();
const experimentRecipe = NimbusTestUtils.factories.recipe.withFeatureConfig(
"experiment",
{ featureId: "no-feature-firefox-desktop" }
);
const experimentEnrollment =
NimbusTestUtils.factories.experiment.withFeatureConfig("experiment", {
featureId: "no-feature-firefox-desktop",
});
const rolloutRecipe = NimbusTestUtils.factories.recipe.withFeatureConfig(
"rollout",
{ featureId: "no-feature-firefox-desktop" },
{ isRollout: true }
);
const rolloutEnrollment =
NimbusTestUtils.factories.experiment.withFeatureConfig(
"rollout",
{ featureId: "no-feature-firefox-desktop" },
{ isRollout: true }
);
const inactiveExperimentEnrollment =
NimbusTestUtils.factories.experiment.withFeatureConfig(
"inactive",
{ featureId: "no-feature-firefox-desktop" },
{ active: false }
);
store.addEnrollment(experimentEnrollment);
await store._addEnrollmentToDatabase(
experimentEnrollment,
experimentRecipe
);
store.addEnrollment(rolloutEnrollment);
await store._addEnrollmentToDatabase(rolloutEnrollment, rolloutRecipe);
store.addEnrollment(inactiveExperimentEnrollment);
await store._addEnrollmentToDatabase(inactiveExperimentEnrollment, null);
// We should expect to see three successful databaseWrite events.
const events = Glean.nimbusEvents.databaseWrite
.testGetValue("events")
.map(ev => ev.extra);
Assert.deepEqual(events, [
{ success: "true" },
{ success: "true" },
{ success: "true" },
]);
storePath = await NimbusTestUtils.saveStore(store);
}
// Initializing the store above will submit the event we care about. Disregard
// any metrics previously recorded.
Services.fog.testResetFOG();
const { cleanup } = await NimbusTestUtils.setupTest({
storePath,
clearTelemetry: true,
});
const events = Glean.nimbusEvents.startupDatabaseConsistency
.testGetValue("events")
.map(ev => ev.extra);
Assert.deepEqual(events, [
{
total_db_count: "3",
total_store_count: "3",
db_active_count: "2",
store_active_count: "2",
},
]);
await NimbusTestUtils.cleanupManager(["rollout", "experiment"]);
await cleanup();
});