mirror of
https://github.com/mozilla/gecko-dev.git
synced 2025-11-09 12:51:09 +02:00
521 lines
14 KiB
JavaScript
521 lines
14 KiB
JavaScript
"use strict";
|
|
|
|
const { ExperimentFakes } = ChromeUtils.import(
|
|
"resource://testing-common/NimbusTestUtils.jsm"
|
|
);
|
|
const { ExperimentStore } = ChromeUtils.import(
|
|
"resource://nimbus/lib/ExperimentStore.jsm"
|
|
);
|
|
|
|
const { SYNC_DATA_PREF_BRANCH, SYNC_DEFAULTS_PREF_BRANCH } = ExperimentStore;
|
|
const { cleanupStorePrefCache } = ExperimentFakes;
|
|
|
|
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 = ExperimentFakes.store();
|
|
const experiment = ExperimentFakes.experiment("foo", {
|
|
branch: {
|
|
slug: "variant",
|
|
feature: { featureId: "purple", enabled: true },
|
|
},
|
|
});
|
|
|
|
Assert.equal(store.getAll().length, 0, "It should not fail");
|
|
|
|
await store.init();
|
|
store.addExperiment(experiment);
|
|
|
|
Assert.equal(
|
|
store.getExperimentForFeature("purple"),
|
|
experiment,
|
|
"should return a matching experiment for the given feature"
|
|
);
|
|
});
|
|
|
|
add_task(async function test_event_add_experiment() {
|
|
const sandbox = sinon.createSandbox();
|
|
const store = ExperimentFakes.store();
|
|
const expected = ExperimentFakes.experiment("foo");
|
|
const updateEventCbStub = sandbox.stub();
|
|
|
|
// Setup ExperimentManager and child store for ExperimentAPI
|
|
await store.init();
|
|
|
|
// Set update cb
|
|
store.on("update:foo", updateEventCbStub);
|
|
|
|
// Add some data
|
|
store.addExperiment(expected);
|
|
|
|
Assert.equal(updateEventCbStub.callCount, 1, "Called once for add");
|
|
|
|
store.off("update:foo", updateEventCbStub);
|
|
});
|
|
|
|
add_task(async function test_event_updates_main() {
|
|
const sandbox = sinon.createSandbox();
|
|
const store = ExperimentFakes.store();
|
|
const experiment = ExperimentFakes.experiment("foo");
|
|
const updateEventCbStub = sandbox.stub();
|
|
|
|
// Setup ExperimentManager and child store for ExperimentAPI
|
|
await store.init();
|
|
|
|
// Set update cb
|
|
store.on(`update:${experiment.branch.feature.featureId}`, updateEventCbStub);
|
|
|
|
store.addExperiment(experiment);
|
|
store.updateExperiment("foo", { active: false });
|
|
|
|
Assert.equal(
|
|
updateEventCbStub.callCount,
|
|
2,
|
|
"Should be called twice: add, update"
|
|
);
|
|
Assert.equal(
|
|
updateEventCbStub.secondCall.args[1].active,
|
|
false,
|
|
"Should be called with updated experiment status"
|
|
);
|
|
|
|
store.off(`update:${experiment.branch.feature.featureId}`, updateEventCbStub);
|
|
});
|
|
|
|
add_task(async function test_getExperimentForGroup() {
|
|
const store = ExperimentFakes.store();
|
|
const experiment = ExperimentFakes.experiment("foo", {
|
|
branch: {
|
|
slug: "variant",
|
|
feature: { featureId: "purple", enabled: true },
|
|
},
|
|
});
|
|
|
|
await store.init();
|
|
store.addExperiment(ExperimentFakes.experiment("bar"));
|
|
store.addExperiment(experiment);
|
|
|
|
Assert.equal(
|
|
store.getExperimentForFeature("purple"),
|
|
experiment,
|
|
"should return a matching experiment for the given feature"
|
|
);
|
|
});
|
|
|
|
add_task(async function test_hasExperimentForFeature() {
|
|
const store = ExperimentFakes.store();
|
|
|
|
await store.init();
|
|
store.addExperiment(
|
|
ExperimentFakes.experiment("foo", {
|
|
branch: {
|
|
slug: "variant",
|
|
feature: { featureId: "green", enabled: true },
|
|
},
|
|
})
|
|
);
|
|
store.addExperiment(
|
|
ExperimentFakes.experiment("foo2", {
|
|
branch: {
|
|
slug: "variant",
|
|
feature: { featureId: "yellow", enabled: true },
|
|
},
|
|
})
|
|
);
|
|
store.addExperiment(
|
|
ExperimentFakes.experiment("bar_expired", {
|
|
active: false,
|
|
branch: {
|
|
slug: "variant",
|
|
feature: { featureId: "purple", enabled: true },
|
|
},
|
|
})
|
|
);
|
|
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"
|
|
);
|
|
});
|
|
|
|
add_task(async function test_getAll_getAllActive() {
|
|
const store = ExperimentFakes.store();
|
|
|
|
await store.init();
|
|
["foo", "bar", "baz"].forEach(slug =>
|
|
store.addExperiment(ExperimentFakes.experiment(slug, { active: false }))
|
|
);
|
|
store.addExperiment(ExperimentFakes.experiment("qux", { active: true }));
|
|
|
|
Assert.deepEqual(
|
|
store.getAll().map(e => e.slug),
|
|
["foo", "bar", "baz", "qux"],
|
|
".getAll() should return all experiments"
|
|
);
|
|
Assert.deepEqual(
|
|
store.getAllActive().map(e => e.slug),
|
|
["qux"],
|
|
".getAllActive() should return all experiments that are active"
|
|
);
|
|
});
|
|
|
|
add_task(async function test_addExperiment() {
|
|
const store = ExperimentFakes.store();
|
|
const exp = ExperimentFakes.experiment("foo");
|
|
|
|
await store.init();
|
|
store.addExperiment(exp);
|
|
|
|
Assert.equal(store.get("foo"), exp, "should save experiment by slug");
|
|
});
|
|
|
|
add_task(async function test_updateExperiment() {
|
|
const feature = { featureId: "cfr", enabled: true };
|
|
const experiment = Object.freeze(
|
|
ExperimentFakes.experiment("foo", { feature, active: true })
|
|
);
|
|
const store = ExperimentFakes.store();
|
|
|
|
await store.init();
|
|
store.addExperiment(experiment);
|
|
store.updateExperiment("foo", { active: false });
|
|
|
|
const actual = store.get("foo");
|
|
Assert.equal(actual.active, false, "should change updated props");
|
|
Assert.deepEqual(
|
|
actual.branch.feature,
|
|
feature,
|
|
"should not update other props"
|
|
);
|
|
});
|
|
|
|
add_task(async function test_sync_access_before_init() {
|
|
cleanupStorePrefCache();
|
|
|
|
let store = ExperimentFakes.store();
|
|
|
|
Assert.equal(store.getAll().length, 0, "Start with an empty store");
|
|
|
|
const syncAccessExp = ExperimentFakes.experiment("foo", {
|
|
feature: { featureId: "newtab", enabled: "true" },
|
|
});
|
|
await store.init();
|
|
store.addExperiment(syncAccessExp);
|
|
|
|
let prefValue;
|
|
try {
|
|
prefValue = JSON.parse(
|
|
Services.prefs.getStringPref(`${SYNC_DATA_PREF_BRANCH}newtab`)
|
|
);
|
|
} catch (e) {
|
|
Assert.ok(false, "Failed to parse pref value");
|
|
}
|
|
|
|
Assert.ok(prefValue, "Parsed stored experiment");
|
|
Assert.equal(prefValue.slug, syncAccessExp.slug, "Got back the experiment");
|
|
|
|
// New un-initialized store that should read the pref value
|
|
store = ExperimentFakes.store();
|
|
|
|
Assert.equal(
|
|
store.getExperimentForFeature("newtab").slug,
|
|
"foo",
|
|
"Returns experiment from pref"
|
|
);
|
|
});
|
|
|
|
add_task(async function test_sync_access_update() {
|
|
cleanupStorePrefCache();
|
|
|
|
let store = ExperimentFakes.store();
|
|
let experiment = ExperimentFakes.experiment("foo", {
|
|
feature: { featureId: "aboutwelcome", enabled: true },
|
|
});
|
|
|
|
await store.init();
|
|
|
|
store.addExperiment(experiment);
|
|
store.updateExperiment("foo", {
|
|
branch: {
|
|
...experiment.branch,
|
|
feature: { featureId: "aboutwelcome", enabled: true, value: "bar" },
|
|
},
|
|
});
|
|
|
|
store = ExperimentFakes.store();
|
|
let cachedExperiment = store.getExperimentForFeature("aboutwelcome");
|
|
|
|
Assert.ok(cachedExperiment, "Got back 1 experiment");
|
|
Assert.equal(
|
|
cachedExperiment.branch.feature.value,
|
|
"bar",
|
|
"Got updated value"
|
|
);
|
|
});
|
|
|
|
add_task(async function test_sync_features_only() {
|
|
cleanupStorePrefCache();
|
|
|
|
let store = ExperimentFakes.store();
|
|
let experiment = ExperimentFakes.experiment("foo", {
|
|
feature: { featureId: "cfr", enabled: true },
|
|
});
|
|
|
|
await store.init();
|
|
|
|
store.addExperiment(experiment);
|
|
store = ExperimentFakes.store();
|
|
|
|
Assert.equal(store.getAll().length, 0, "cfr is not a sync access experiment");
|
|
});
|
|
|
|
add_task(async function test_sync_features_remotely() {
|
|
cleanupStorePrefCache();
|
|
|
|
let store = ExperimentFakes.store();
|
|
let experiment = ExperimentFakes.experiment("foo", {
|
|
feature: { featureId: "cfr", enabled: true, isEarlyStartup: true },
|
|
});
|
|
|
|
await store.init();
|
|
|
|
store.addExperiment(experiment);
|
|
store = ExperimentFakes.store();
|
|
|
|
Assert.ok(
|
|
Services.prefs.prefHasUserValue("nimbus.syncdatastore.cfr"),
|
|
"The cfr feature was stored as early access in prefs"
|
|
);
|
|
Assert.equal(store.getAll().length, 0, "Featre restored from prefs");
|
|
});
|
|
|
|
add_task(async function test_sync_access_unenroll() {
|
|
cleanupStorePrefCache();
|
|
|
|
let store = ExperimentFakes.store();
|
|
let experiment = ExperimentFakes.experiment("foo", {
|
|
feature: { featureId: "aboutwelcome", enabled: true },
|
|
active: true,
|
|
});
|
|
|
|
await store.init();
|
|
|
|
store.addExperiment(experiment);
|
|
store.updateExperiment("foo", { active: false });
|
|
|
|
store = ExperimentFakes.store();
|
|
let experiments = store.getAll();
|
|
|
|
Assert.equal(experiments.length, 0, "Unenrolled experiment is deleted");
|
|
});
|
|
|
|
add_task(async function test_sync_access_unenroll_2() {
|
|
cleanupStorePrefCache();
|
|
|
|
let store = ExperimentFakes.store();
|
|
let experiment1 = ExperimentFakes.experiment("foo", {
|
|
feature: { featureId: "newtab", enabled: true },
|
|
});
|
|
let experiment2 = ExperimentFakes.experiment("bar", {
|
|
feature: { featureId: "aboutwelcome", enabled: true },
|
|
});
|
|
|
|
await store.init();
|
|
|
|
store.addExperiment(experiment1);
|
|
store.addExperiment(experiment2);
|
|
|
|
Assert.equal(store.getAll().length, 2, "2/2 experiments");
|
|
|
|
let other_store = ExperimentFakes.store();
|
|
|
|
Assert.ok(
|
|
other_store.getExperimentForFeature("aboutwelcome"),
|
|
"Fetches experiment from pref cache even before init (aboutwelcome)"
|
|
);
|
|
|
|
store.updateExperiment("bar", { active: false });
|
|
|
|
Assert.ok(
|
|
other_store.getExperimentForFeature("newtab").slug,
|
|
"Fetches experiment from pref cache even before init (newtab)"
|
|
);
|
|
Assert.ok(
|
|
!other_store.getExperimentForFeature("aboutwelcome")?.slug,
|
|
"Experiment was updated and should not be found"
|
|
);
|
|
|
|
store.updateExperiment("foo", { active: false });
|
|
Assert.ok(
|
|
!other_store.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"
|
|
);
|
|
});
|
|
|
|
add_task(async function test_syncDataStore_setDefault() {
|
|
cleanupStorePrefCache();
|
|
const store = ExperimentFakes.store();
|
|
|
|
await store.init();
|
|
|
|
Assert.equal(
|
|
Services.prefs.getStringPref(
|
|
`${SYNC_DEFAULTS_PREF_BRANCH}aboutwelcome`,
|
|
""
|
|
),
|
|
"",
|
|
"Pref is empty"
|
|
);
|
|
|
|
store.updateRemoteConfigs("aboutwelcome", { remote: true });
|
|
|
|
Assert.ok(
|
|
Services.prefs.getStringPref(`${SYNC_DEFAULTS_PREF_BRANCH}aboutwelcome`),
|
|
"Stored in pref"
|
|
);
|
|
|
|
cleanupStorePrefCache();
|
|
});
|
|
|
|
add_task(async function test_syncDataStore_getDefault() {
|
|
cleanupStorePrefCache();
|
|
const store = ExperimentFakes.store();
|
|
|
|
Services.prefs.setStringPref(
|
|
`${SYNC_DEFAULTS_PREF_BRANCH}aboutwelcome`,
|
|
JSON.stringify({ remote: true })
|
|
);
|
|
|
|
let data = store.getRemoteConfig("aboutwelcome");
|
|
|
|
Assert.ok(data.remote, "Restore data from pref");
|
|
|
|
cleanupStorePrefCache();
|
|
});
|
|
|
|
add_task(async function test_updateRemoteConfigs() {
|
|
const sandbox = sinon.createSandbox();
|
|
const store = ExperimentFakes.store();
|
|
const stub = sandbox.stub();
|
|
const value = { bar: true };
|
|
|
|
store._onFeatureUpdate("featureId", stub);
|
|
|
|
await store.init();
|
|
store.updateRemoteConfigs("featureId", value);
|
|
|
|
Assert.deepEqual(
|
|
store.getRemoteConfig("featureId"),
|
|
value,
|
|
"should return the stored value"
|
|
);
|
|
Assert.equal(stub.callCount, 1, "Called once on update");
|
|
Assert.equal(
|
|
stub.firstCall.args[1],
|
|
"remote-defaults-update",
|
|
"Called for correct reason"
|
|
);
|
|
});
|
|
|
|
add_task(async function test_finalizaRemoteConfigs_cleanup() {
|
|
cleanupStorePrefCache();
|
|
const store = ExperimentFakes.store();
|
|
|
|
Services.prefs.setStringPref(
|
|
`${SYNC_DEFAULTS_PREF_BRANCH}unit-test-feature`,
|
|
JSON.stringify({ remote: true })
|
|
);
|
|
|
|
// We are able to sync-read data without needing to initialize the store
|
|
let data = store.getRemoteConfig("unit-test-feature");
|
|
Assert.ok(data.remote, "Restore data from pref");
|
|
|
|
// We need to initialize the store for the cleanup step
|
|
await store.init();
|
|
|
|
store.finalizeRemoteConfigs([]);
|
|
data = store.getRemoteConfig("unit-test-feature");
|
|
|
|
Assert.ok(!data, `Data was removed ${JSON.stringify(data)}`);
|
|
|
|
cleanupStorePrefCache();
|
|
});
|
|
|
|
add_task(async function test_finalizaRemoteConfigs_cleanup() {
|
|
cleanupStorePrefCache();
|
|
const store = ExperimentFakes.store();
|
|
await store.init();
|
|
|
|
store.updateRemoteConfigs("aboutwelcome", { remote: true });
|
|
|
|
let data = store.getRemoteConfig("aboutwelcome");
|
|
Assert.ok(data.remote, "Restore data from pref");
|
|
|
|
store.finalizeRemoteConfigs(["aboutwelcome"]);
|
|
data = store.getRemoteConfig("aboutwelcome");
|
|
|
|
Assert.ok(data.remote, "Data was kept");
|
|
|
|
cleanupStorePrefCache();
|
|
});
|
|
|
|
add_task(async function test_getAllExistingRemoteConfigIds() {
|
|
cleanupStorePrefCache();
|
|
const store = ExperimentFakes.store();
|
|
|
|
Services.prefs.setStringPref(
|
|
`${SYNC_DEFAULTS_PREF_BRANCH}unit-test-feature`,
|
|
JSON.stringify({ remote: true })
|
|
);
|
|
|
|
await store.init();
|
|
|
|
store.updateRemoteConfigs("aboutwelcome", { remote: true });
|
|
store.updateRemoteConfigs("unit-test-feature", { remote: true });
|
|
|
|
let data = store.getAllExistingRemoteConfigIds();
|
|
|
|
Assert.deepEqual(
|
|
data,
|
|
["aboutwelcome", "unit-test-feature"],
|
|
"Should return ids from sync pref cache and in memory store without duplication"
|
|
);
|
|
|
|
cleanupStorePrefCache();
|
|
});
|