зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1716736 - Add schema validation for experiment enrollments in tests r=k88hudson
Differential Revision: https://phabricator.services.mozilla.com/D118367
This commit is contained in:
Родитель
2249630d55
Коммит
a66714326b
|
@ -111,8 +111,8 @@ add_task(async function test_remote_configuration() {
|
|||
feature: NimbusFeatures.aboutwelcome,
|
||||
configuration: {
|
||||
slug: "about:studies-configuration-slug",
|
||||
enabled: true,
|
||||
variables: {},
|
||||
variables: { enabled: true },
|
||||
targeting: "true",
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
@ -26,7 +26,11 @@ add_task(async function remote_disable() {
|
|||
|
||||
await ExperimentFakes.remoteDefaultsHelper({
|
||||
feature: NimbusFeatures.shellService,
|
||||
configuration: { variables: { disablePin: true } },
|
||||
configuration: {
|
||||
slug: "shellService_remoteDisable",
|
||||
variables: { disablePin: true, enabled: true },
|
||||
targeting: "true",
|
||||
},
|
||||
});
|
||||
|
||||
Assert.equal(
|
||||
|
@ -42,10 +46,7 @@ add_task(async function restore_default() {
|
|||
return;
|
||||
}
|
||||
|
||||
await ExperimentFakes.remoteDefaultsHelper({
|
||||
feature: NimbusFeatures.shellService,
|
||||
configuration: {},
|
||||
});
|
||||
ExperimentAPI._store._deleteForTests("shellService");
|
||||
|
||||
Assert.equal(
|
||||
await ShellService.doesAppNeedPin(),
|
||||
|
|
|
@ -48,7 +48,14 @@ add_task(async function remote_disable() {
|
|||
setDefaultStub.resetHistory();
|
||||
await ExperimentFakes.remoteDefaultsHelper({
|
||||
feature: NimbusFeatures.shellService,
|
||||
configuration: { variables: { setDefaultBrowserUserChoice: false } },
|
||||
configuration: {
|
||||
slug: "shellService_remoteDisable",
|
||||
variables: {
|
||||
setDefaultBrowserUserChoice: false,
|
||||
enabled: true,
|
||||
},
|
||||
targeting: "true",
|
||||
},
|
||||
});
|
||||
|
||||
ShellService.setDefaultBrowser();
|
||||
|
@ -68,10 +75,7 @@ add_task(async function restore_default() {
|
|||
|
||||
userChoiceStub.resetHistory();
|
||||
setDefaultStub.resetHistory();
|
||||
await ExperimentFakes.remoteDefaultsHelper({
|
||||
feature: NimbusFeatures.shellService,
|
||||
configuration: {},
|
||||
});
|
||||
ExperimentAPI._store._deleteForTests("shellService");
|
||||
|
||||
ShellService.setDefaultBrowser();
|
||||
|
||||
|
|
|
@ -360,7 +360,11 @@ add_task(async function remote_disabled() {
|
|||
await ExperimentAPI.ready();
|
||||
await ExperimentFakes.remoteDefaultsHelper({
|
||||
feature: NimbusFeatures.upgradeDialog,
|
||||
configuration: { enabled: false, variables: {} },
|
||||
configuration: {
|
||||
slug: "upgradeDialog_remoteDisabled",
|
||||
variables: { enabled: false },
|
||||
targeting: "true",
|
||||
},
|
||||
});
|
||||
|
||||
// Simulate starting from a previous version.
|
||||
|
@ -380,7 +384,11 @@ add_task(async function remote_disabled() {
|
|||
// Re-enable back
|
||||
await ExperimentFakes.remoteDefaultsHelper({
|
||||
feature: NimbusFeatures.upgradeDialog,
|
||||
configuration: { enabled: true, variables: {} },
|
||||
configuration: {
|
||||
slug: "upgradeDialog_remoteEnabled",
|
||||
variables: { enabled: true },
|
||||
targeting: "true",
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -183,9 +183,7 @@ const ExperimentAPI = {
|
|||
}
|
||||
let fullEventName = `${eventName}:${options.slug || options.featureId}`;
|
||||
|
||||
// The update event will always fire after the event listener is added, either
|
||||
// immediately if it is already ready, or on ready
|
||||
this._store.ready().then(() => {
|
||||
if (this._store._isReady) {
|
||||
let experiment = this.getExperiment(options);
|
||||
// Only if we have an experiment that matches what the caller requested
|
||||
if (experiment) {
|
||||
|
@ -194,7 +192,7 @@ const ExperimentAPI = {
|
|||
// are attached later than the `update` events.
|
||||
callback(fullEventName, experiment);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
this._store.on(fullEventName, callback);
|
||||
},
|
||||
|
|
|
@ -30,6 +30,7 @@ SPHINX_TREES["docs"] = "docs"
|
|||
TESTING_JS_MODULES += [
|
||||
"schemas/ExperimentFeatureManifest.schema.json",
|
||||
"schemas/ExperimentFeatureRemote.schema.json",
|
||||
"schemas/NimbusEnrollment.schema.json",
|
||||
"schemas/NimbusExperiment.schema.json",
|
||||
"test/NimbusTestUtils.jsm",
|
||||
]
|
||||
|
|
|
@ -79,6 +79,74 @@
|
|||
},
|
||||
"required": ["id", "configurations"],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"RemoteFeatureConfiguration": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"slug": {
|
||||
"type": "string",
|
||||
"description": "Configuration identifier that will be included in Telemetry."
|
||||
},
|
||||
"isEarlyStartup": {
|
||||
"type": "boolean",
|
||||
"description": "If the feature values should be cached in prefs for fast early startup."
|
||||
},
|
||||
"variables": {
|
||||
"type": "object",
|
||||
"description": "Key value pairs that should match the feature manifest definition.",
|
||||
"properties": {
|
||||
"enabled": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"required": ["enabled"]
|
||||
},
|
||||
"targeting": {
|
||||
"type": "string",
|
||||
"description": "Target the configuration only to specific clients."
|
||||
},
|
||||
"bucketConfig": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"randomizationUnit": {
|
||||
"type": "string",
|
||||
"description": "A unique, stable identifier for the user used as an input to bucket hashing"
|
||||
},
|
||||
"namespace": {
|
||||
"type": "string",
|
||||
"description": "Additional inputs to the hashing function"
|
||||
},
|
||||
"start": {
|
||||
"type": "number",
|
||||
"description": "Index of start of the range of buckets"
|
||||
},
|
||||
"count": {
|
||||
"type": "number",
|
||||
"description": "Number of buckets to check"
|
||||
},
|
||||
"total": {
|
||||
"type": "number",
|
||||
"description": "Total number of buckets",
|
||||
"default": 10000
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"randomizationUnit",
|
||||
"namespace",
|
||||
"start",
|
||||
"count",
|
||||
"total"
|
||||
],
|
||||
"additionalProperties": false,
|
||||
"description": "Bucketing configuration"
|
||||
},
|
||||
"description": {
|
||||
"type": "string",
|
||||
"description": "Explanation for configuration and targeting"
|
||||
}
|
||||
},
|
||||
"required": ["variables", "targeting", "slug"],
|
||||
"additionalProperties": false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,91 @@
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"$ref": "#/definitions/NimbusExperiment",
|
||||
"definitions": {
|
||||
"NimbusExperiment": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"slug": {
|
||||
"type": "string"
|
||||
},
|
||||
"branch": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"feature": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"featureId": {
|
||||
"type": "string",
|
||||
"description": "The identifier for the feature flag"
|
||||
},
|
||||
"value": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "object",
|
||||
"additionalProperties": {}
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"description": "Optional extra params for the feature (this should be validated against a schema)"
|
||||
},
|
||||
"enabled": {
|
||||
"type": "boolean",
|
||||
"description": "(deprecated)"
|
||||
}
|
||||
},
|
||||
"required": ["featureId", "value"],
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"required": ["feature"]
|
||||
},
|
||||
"active": {
|
||||
"type": "boolean",
|
||||
"description": "Experiment status"
|
||||
},
|
||||
"enrollmentId": {
|
||||
"type": "string",
|
||||
"description": "Unique identifier used in telemetry"
|
||||
},
|
||||
"experimentType": {
|
||||
"type": "string"
|
||||
},
|
||||
"isEnrollmentPaused": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"source": {
|
||||
"type": "string",
|
||||
"description": "What triggered the enrollment"
|
||||
},
|
||||
"userFacingName": {
|
||||
"type": "string"
|
||||
},
|
||||
"userFacingDescription": {
|
||||
"type": "string"
|
||||
},
|
||||
"lastSeen": {
|
||||
"type": "string",
|
||||
"description": "When was the enrollment made"
|
||||
},
|
||||
"force": {
|
||||
"type": "boolean",
|
||||
"description": "(debug) If the enrollment happened naturally or through devtools"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"slug",
|
||||
"branch",
|
||||
"active",
|
||||
"enrollmentId",
|
||||
"experimentType",
|
||||
"source",
|
||||
"userFacingName",
|
||||
"userFacingDescription"
|
||||
],
|
||||
"additionalProperties": false,
|
||||
"description": "The experiment definition accessible to:\n1. The Nimbus SDK via Remote Settings\n2. Jetstream via the Experimenter API"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -20,47 +20,94 @@ XPCOMUtils.defineLazyModuleGetters(this, {
|
|||
_RemoteSettingsExperimentLoader:
|
||||
"resource://nimbus/lib/RemoteSettingsExperimentLoader.jsm",
|
||||
Ajv: "resource://testing-common/ajv-4.1.1.js",
|
||||
sinon: "resource://testing-common/Sinon.jsm",
|
||||
});
|
||||
|
||||
const { SYNC_DATA_PREF_BRANCH, SYNC_DEFAULTS_PREF_BRANCH } = ExperimentStore;
|
||||
|
||||
const PATH = FileTestUtils.getTempFile("shared-data-map").path;
|
||||
|
||||
XPCOMUtils.defineLazyGetter(this, "fetchExperimentSchema", async () => {
|
||||
const response = await fetch(
|
||||
"resource://testing-common/NimbusExperiment.schema.json"
|
||||
);
|
||||
async function fetchSchema(url) {
|
||||
const response = await fetch(url);
|
||||
const schema = await response.json();
|
||||
if (!schema) {
|
||||
throw new Error("Failed to load NimbusSchema");
|
||||
throw new Error(`Failed to load ${url}`);
|
||||
}
|
||||
return schema.definitions.NimbusExperiment;
|
||||
});
|
||||
return schema.definitions;
|
||||
}
|
||||
|
||||
const EXPORTED_SYMBOLS = ["ExperimentTestUtils", "ExperimentFakes"];
|
||||
|
||||
const ExperimentTestUtils = {
|
||||
/**
|
||||
* Checks if an experiment is valid acording to existing schema
|
||||
* @param {NimbusExperiment} experiment
|
||||
*/
|
||||
async validateExperiment(experiment) {
|
||||
const schema = await fetchExperimentSchema;
|
||||
_validator(schema, value, errorMsg) {
|
||||
const ajv = new Ajv({ async: "co*", allErrors: true });
|
||||
const validator = ajv.compile(schema);
|
||||
validator(experiment);
|
||||
validator(value);
|
||||
if (validator.errors?.length) {
|
||||
throw new Error(
|
||||
"Experiment not valid:" + JSON.stringify(validator.errors, undefined, 2)
|
||||
`${errorMsg}: ${JSON.stringify(validator.errors, undefined, 2)}`
|
||||
);
|
||||
}
|
||||
return experiment;
|
||||
return value;
|
||||
},
|
||||
|
||||
/**
|
||||
* Checks if an experiment is valid acording to existing schema
|
||||
*/
|
||||
async validateExperiment(experiment) {
|
||||
const schema = (
|
||||
await fetchSchema(
|
||||
"resource://testing-common/NimbusExperiment.schema.json"
|
||||
)
|
||||
).NimbusExperiment;
|
||||
|
||||
return this._validator(
|
||||
schema,
|
||||
experiment,
|
||||
`Experiment ${experiment.slug} not valid`
|
||||
);
|
||||
},
|
||||
async validateEnrollment(enrollment) {
|
||||
const schema = (
|
||||
await fetchSchema(
|
||||
"resource://testing-common/NimbusEnrollment.schema.json"
|
||||
)
|
||||
).NimbusExperiment;
|
||||
|
||||
return this._validator(
|
||||
schema,
|
||||
enrollment,
|
||||
`Enrollment ${enrollment.slug} is not valid`
|
||||
);
|
||||
},
|
||||
async validateRollouts(rollout) {
|
||||
const schema = (
|
||||
await fetchSchema(
|
||||
"resource://testing-common/ExperimentFeatureRemote.schema.json"
|
||||
)
|
||||
).RemoteFeatureConfiguration;
|
||||
|
||||
return this._validator(
|
||||
schema,
|
||||
rollout,
|
||||
`Rollout configuration ${rollout.slug} is not valid`
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
const ExperimentFakes = {
|
||||
manager(store) {
|
||||
return new _ExperimentManager({ store: store || this.store() });
|
||||
let sandbox = sinon.createSandbox();
|
||||
let manager = new _ExperimentManager({ store: store || this.store() });
|
||||
// We want calls to `store.addExperiment` to implicitly validate the
|
||||
// enrollment before saving to store
|
||||
let origAddExperiment = manager.store.addExperiment.bind(manager.store);
|
||||
sandbox.stub(manager.store, "addExperiment").callsFake(async enrollment => {
|
||||
await ExperimentTestUtils.validateEnrollment(enrollment);
|
||||
return origAddExperiment(enrollment);
|
||||
});
|
||||
|
||||
return manager;
|
||||
},
|
||||
store() {
|
||||
return new ExperimentStore("FakeStore", { path: PATH, isParent: true });
|
||||
|
@ -72,7 +119,7 @@ const ExperimentFakes = {
|
|||
|
||||
return new Promise(resolve => ExperimentAPI.on("update", options, resolve));
|
||||
},
|
||||
remoteDefaultsHelper({
|
||||
async remoteDefaultsHelper({
|
||||
feature,
|
||||
store = ExperimentManager.store,
|
||||
configuration,
|
||||
|
@ -80,9 +127,14 @@ const ExperimentFakes = {
|
|||
if (!store._isReady) {
|
||||
throw new Error("Store not ready, need to `await ExperimentAPI.ready()`");
|
||||
}
|
||||
store.updateRemoteConfigs(feature.featureId, configuration);
|
||||
|
||||
return feature.ready().then(() => store._syncToChildren({ flush: true }));
|
||||
await ExperimentTestUtils.validateRollouts(configuration);
|
||||
// After storing the remote configuration to store and updating the feature
|
||||
// we want to flush so that NimbusFeature usage in content process also
|
||||
// receives the update
|
||||
store.updateRemoteConfigs(feature.featureId, configuration);
|
||||
await feature.ready();
|
||||
store._syncToChildren({ flush: true });
|
||||
},
|
||||
async enrollWithFeatureConfig(
|
||||
featureConfig,
|
||||
|
@ -152,7 +204,7 @@ const ExperimentFakes = {
|
|||
if (!manager.store._isReady) {
|
||||
throw new Error("Manager store not ready, call `manager.onStartup`");
|
||||
}
|
||||
manager.enroll(recipe);
|
||||
manager.enroll(recipe, "enrollmentHelper");
|
||||
}
|
||||
|
||||
return { enrollmentPromise, doExperimentCleanup };
|
||||
|
@ -193,8 +245,11 @@ const ExperimentFakes = {
|
|||
},
|
||||
...props,
|
||||
},
|
||||
source: "test",
|
||||
source: "NimbusTestUtils",
|
||||
isEnrollmentPaused: true,
|
||||
experimentType: "NimbusTestUtils",
|
||||
userFacingName: "NimbusTestUtils",
|
||||
userFacingDescription: "NimbusTestUtils",
|
||||
...props,
|
||||
};
|
||||
},
|
||||
|
|
|
@ -75,10 +75,26 @@ add_task(async function test_evaluate_active_experiments_activeExperiments() {
|
|||
const slug = "foo" + Math.random();
|
||||
// Init the store before we use it
|
||||
await ExperimentManager.onStartup();
|
||||
ExperimentManager.store.addExperiment(ExperimentFakes.experiment(slug));
|
||||
registerCleanupFunction(() => {
|
||||
ExperimentManager.store._deleteForTests(slug);
|
||||
});
|
||||
let {
|
||||
enrollmentPromise,
|
||||
doExperimentCleanup,
|
||||
} = ExperimentFakes.enrollmentHelper(
|
||||
ExperimentFakes.recipe(slug, {
|
||||
branches: [
|
||||
{
|
||||
slug: "mochitest-active-foo",
|
||||
feature: {
|
||||
enabled: true,
|
||||
featureId: "foo",
|
||||
value: null,
|
||||
},
|
||||
},
|
||||
],
|
||||
active: true,
|
||||
})
|
||||
);
|
||||
|
||||
await enrollmentPromise;
|
||||
|
||||
Assert.equal(
|
||||
await RemoteSettingsExperimentLoader.evaluateJexl(
|
||||
|
@ -97,4 +113,6 @@ add_task(async function test_evaluate_active_experiments_activeExperiments() {
|
|||
false,
|
||||
"should not find an experiment that doesn't exist"
|
||||
);
|
||||
|
||||
await doExperimentCleanup();
|
||||
});
|
||||
|
|
|
@ -48,14 +48,10 @@ add_task(async function test_double_feature_enrollment() {
|
|||
let enrollPromise1 = ExperimentFakes.waitForExperimentUpdate(ExperimentAPI, {
|
||||
slug: recipe1.slug,
|
||||
});
|
||||
let enrollPromise2 = ExperimentFakes.waitForExperimentUpdate(ExperimentAPI, {
|
||||
slug: recipe2.slug,
|
||||
});
|
||||
|
||||
ExperimentManager.enroll(recipe1);
|
||||
ExperimentManager.enroll(recipe2);
|
||||
|
||||
await Promise.any([enrollPromise1, enrollPromise2]);
|
||||
ExperimentManager.enroll(recipe1, "test_double_feature_enrollment");
|
||||
await enrollPromise1;
|
||||
ExperimentManager.enroll(recipe2, "test_double_feature_enrollment");
|
||||
|
||||
Assert.equal(
|
||||
ExperimentManager.store.getAllActive().length,
|
||||
|
|
|
@ -23,9 +23,6 @@ const {
|
|||
const { BrowserTestUtils } = ChromeUtils.import(
|
||||
"resource://testing-common/BrowserTestUtils.jsm"
|
||||
);
|
||||
const { ExperimentFakes } = ChromeUtils.import(
|
||||
"resource://testing-common/NimbusTestUtils.jsm"
|
||||
);
|
||||
const { TelemetryEnvironment } = ChromeUtils.import(
|
||||
"resource://gre/modules/TelemetryEnvironment.jsm"
|
||||
);
|
||||
|
@ -36,8 +33,7 @@ const REMOTE_CONFIGURATION_AW = {
|
|||
configurations: [
|
||||
{
|
||||
slug: "a",
|
||||
variables: { remoteValue: 24 },
|
||||
enabled: false,
|
||||
variables: { remoteValue: 24, enabled: false },
|
||||
targeting: "false",
|
||||
bucketConfig: {
|
||||
namespace: "nimbus-test-utils",
|
||||
|
@ -49,8 +45,7 @@ const REMOTE_CONFIGURATION_AW = {
|
|||
},
|
||||
{
|
||||
slug: "b",
|
||||
variables: { remoteValue: 42 },
|
||||
enabled: true,
|
||||
variables: { remoteValue: 42, enabled: true },
|
||||
targeting: "true",
|
||||
bucketConfig: {
|
||||
namespace: "nimbus-test-utils",
|
||||
|
@ -68,8 +63,7 @@ const REMOTE_CONFIGURATION_NEWTAB = {
|
|||
configurations: [
|
||||
{
|
||||
slug: "a",
|
||||
variables: { remoteValue: 1 },
|
||||
enabled: false,
|
||||
variables: { remoteValue: 1, enabled: false },
|
||||
targeting: "false",
|
||||
bucketConfig: {
|
||||
namespace: "nimbus-test-utils",
|
||||
|
@ -81,8 +75,7 @@ const REMOTE_CONFIGURATION_NEWTAB = {
|
|||
},
|
||||
{
|
||||
slug: "b",
|
||||
variables: { remoteValue: 3 },
|
||||
enabled: true,
|
||||
variables: { remoteValue: 3, enabled: true },
|
||||
targeting: "true",
|
||||
bucketConfig: {
|
||||
namespace: "nimbus-test-utils",
|
||||
|
@ -94,8 +87,7 @@ const REMOTE_CONFIGURATION_NEWTAB = {
|
|||
},
|
||||
{
|
||||
slug: "c",
|
||||
variables: { remoteValue: 2 },
|
||||
enabled: false,
|
||||
variables: { remoteValue: 2, enabled: false },
|
||||
targeting: "false",
|
||||
bucketConfig: {
|
||||
namespace: "nimbus-test-utils",
|
||||
|
@ -487,7 +479,8 @@ add_task(async function remote_defaults_no_mutation() {
|
|||
|
||||
add_task(async function remote_defaults_active_experiments_check() {
|
||||
let barFeature = new ExperimentFeature("bar", {
|
||||
bar: { description: "mochitest" },
|
||||
description: "mochitest",
|
||||
variables: { enabled: { type: "boolean" } },
|
||||
});
|
||||
let experimentOnlyRemoteDefault = {
|
||||
id: "bar",
|
||||
|
@ -495,14 +488,12 @@ add_task(async function remote_defaults_active_experiments_check() {
|
|||
configurations: [
|
||||
{
|
||||
slug: "a",
|
||||
variables: {},
|
||||
enabled: false,
|
||||
variables: { enabled: false },
|
||||
targeting: "'mochitest-active-foo' in activeExperiments",
|
||||
},
|
||||
{
|
||||
slug: "b",
|
||||
variables: {},
|
||||
enabled: true,
|
||||
variables: { enabled: true },
|
||||
targeting: "true",
|
||||
},
|
||||
],
|
||||
|
@ -550,10 +541,12 @@ add_task(async function remote_defaults_active_remote_defaults() {
|
|||
ExperimentAPI._store._deleteForTests("foo");
|
||||
ExperimentAPI._store._deleteForTests("bar");
|
||||
let barFeature = new ExperimentFeature("bar", {
|
||||
bar: { description: "mochitest" },
|
||||
description: "mochitest",
|
||||
variables: { enabled: { type: "boolean" } },
|
||||
});
|
||||
let fooFeature = new ExperimentFeature("foo", {
|
||||
foo: { description: "mochitest" },
|
||||
description: "mochitest",
|
||||
variables: { enabled: { type: "boolean" } },
|
||||
});
|
||||
let remoteDefaults = [
|
||||
{
|
||||
|
@ -562,8 +555,7 @@ add_task(async function remote_defaults_active_remote_defaults() {
|
|||
configurations: [
|
||||
{
|
||||
slug: "a",
|
||||
variables: {},
|
||||
enabled: true,
|
||||
variables: { enabled: true },
|
||||
targeting: "true",
|
||||
},
|
||||
],
|
||||
|
@ -574,8 +566,7 @@ add_task(async function remote_defaults_active_remote_defaults() {
|
|||
configurations: [
|
||||
{
|
||||
slug: "b",
|
||||
variables: {},
|
||||
enabled: true,
|
||||
variables: { enabled: true },
|
||||
targeting: "'bar' in activeRemoteDefaults",
|
||||
},
|
||||
],
|
||||
|
@ -647,14 +638,12 @@ add_task(async function test_remote_defaults_no_bucketConfig() {
|
|||
configurations: [
|
||||
{
|
||||
slug: "a",
|
||||
variables: { remoteValue: 24 },
|
||||
enabled: false,
|
||||
variables: { remoteValue: 24, enabled: false },
|
||||
targeting: "false",
|
||||
},
|
||||
{
|
||||
slug: "b",
|
||||
variables: { remoteValue: 42 },
|
||||
enabled: true,
|
||||
variables: { remoteValue: 42, enabled: true },
|
||||
targeting: "true",
|
||||
},
|
||||
],
|
||||
|
@ -688,7 +677,23 @@ add_task(async function test_remote_defaults_no_bucketConfig() {
|
|||
|
||||
add_task(async function remote_defaults_variables_storage() {
|
||||
let barFeature = new ExperimentFeature("bar", {
|
||||
bar: { description: "mochitest" },
|
||||
bar: {
|
||||
description: "mochitest",
|
||||
variables: {
|
||||
storage: {
|
||||
type: "int",
|
||||
},
|
||||
object: {
|
||||
type: "json",
|
||||
},
|
||||
string: {
|
||||
type: "string",
|
||||
},
|
||||
bool: {
|
||||
type: "boolean",
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
let remoteDefaults = [
|
||||
{
|
||||
|
@ -703,8 +708,8 @@ add_task(async function remote_defaults_variables_storage() {
|
|||
object: { foo: "foo" },
|
||||
string: "string",
|
||||
bool: true,
|
||||
enabled: true,
|
||||
},
|
||||
enabled: true,
|
||||
targeting: "true",
|
||||
},
|
||||
],
|
||||
|
@ -715,6 +720,10 @@ add_task(async function remote_defaults_variables_storage() {
|
|||
await RemoteDefaultsLoader.syncRemoteDefaults("mochitest");
|
||||
await barFeature.ready();
|
||||
|
||||
Assert.ok(
|
||||
Services.prefs.getStringPref(`${SYNC_DEFAULTS_PREF_BRANCH}bar`, ""),
|
||||
"Experiment stored in prefs"
|
||||
);
|
||||
Assert.ok(
|
||||
Services.prefs.getIntPref(`${SYNC_DEFAULTS_PREF_BRANCH}bar.storage`, 0),
|
||||
"Stores variable in separate pref"
|
||||
|
|
|
@ -5,3 +5,55 @@
|
|||
|
||||
// Globals
|
||||
const { sinon } = ChromeUtils.import("resource://testing-common/Sinon.jsm");
|
||||
const { XPCOMUtils } = ChromeUtils.import(
|
||||
"resource://gre/modules/XPCOMUtils.jsm"
|
||||
);
|
||||
XPCOMUtils.defineLazyModuleGetters(this, {
|
||||
ExperimentManager: "resource://nimbus/lib/ExperimentManager.jsm",
|
||||
Ajv: "resource://testing-common/ajv-4.1.1.js",
|
||||
ExperimentTestUtils: "resource://testing-common/NimbusTestUtils.jsm",
|
||||
RemoteDefaultsLoader:
|
||||
"resource://nimbus/lib/RemoteSettingsExperimentLoader.jsm",
|
||||
ExperimentFakes: "resource://testing-common/NimbusTestUtils.jsm",
|
||||
});
|
||||
|
||||
add_task(function setup() {
|
||||
let sandbox = sinon.createSandbox();
|
||||
|
||||
/* We stub the functions that operate with enrollments and remote rollouts
|
||||
* so that any access to store something is implicitly validated against
|
||||
* the schema and no records have missing (or extra) properties while in tests
|
||||
*/
|
||||
|
||||
let origAddExperiment = ExperimentManager.store.addExperiment.bind(
|
||||
ExperimentManager.store
|
||||
);
|
||||
let origOnUpdatesReady = RemoteDefaultsLoader._onUpdatesReady.bind(
|
||||
RemoteDefaultsLoader
|
||||
);
|
||||
sandbox
|
||||
.stub(ExperimentManager.store, "addExperiment")
|
||||
.callsFake(async enrollment => {
|
||||
await ExperimentTestUtils.validateEnrollment(enrollment);
|
||||
return origAddExperiment(enrollment);
|
||||
});
|
||||
// Unlike `addExperiment` the method to store remote rollouts is syncronous
|
||||
// and our validation method would turn it async. If we had changed to `await`
|
||||
// for remote configs storage it would have changed the code logic so we are
|
||||
// going up one level to the function that receives the RS records and do
|
||||
// the validation there.
|
||||
sandbox
|
||||
.stub(RemoteDefaultsLoader, "_onUpdatesReady")
|
||||
.callsFake(async (remoteDefaults, reason) => {
|
||||
for (let remoteDefault of remoteDefaults) {
|
||||
for (let config of remoteDefault.configurations) {
|
||||
await ExperimentTestUtils.validateRollouts(config);
|
||||
}
|
||||
}
|
||||
return origOnUpdatesReady(remoteDefaults, reason);
|
||||
});
|
||||
|
||||
registerCleanupFunction(() => {
|
||||
sandbox.restore();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -3,3 +3,9 @@
|
|||
|
||||
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
|
||||
const { sinon } = ChromeUtils.import("resource://testing-common/Sinon.jsm");
|
||||
const { XPCOMUtils } = ChromeUtils.import(
|
||||
"resource://gre/modules/XPCOMUtils.jsm"
|
||||
);
|
||||
XPCOMUtils.defineLazyModuleGetters(this, {
|
||||
ExperimentFakes: "resource://testing-common/NimbusTestUtils.jsm",
|
||||
});
|
||||
|
|
|
@ -27,7 +27,7 @@ add_task(async function test_getExperiment_fromChild_slug() {
|
|||
|
||||
sandbox.stub(ExperimentAPI, "_store").get(() => ExperimentFakes.childStore());
|
||||
|
||||
manager.store.addExperiment(expected);
|
||||
await manager.store.addExperiment(expected);
|
||||
|
||||
// Wait to sync to child
|
||||
await TestUtils.waitForCondition(
|
||||
|
@ -59,7 +59,7 @@ add_task(async function test_getExperiment_fromParent_slug() {
|
|||
sandbox.stub(ExperimentAPI, "_store").get(() => manager.store);
|
||||
await ExperimentAPI.ready();
|
||||
|
||||
manager.store.addExperiment(expected);
|
||||
await manager.store.addExperiment(expected);
|
||||
|
||||
Assert.equal(
|
||||
ExperimentAPI.getExperiment({ slug: "foo" }).slug,
|
||||
|
@ -80,7 +80,7 @@ add_task(async function test_getExperimentMetaData() {
|
|||
sandbox.stub(ExperimentAPI, "_store").get(() => manager.store);
|
||||
await ExperimentAPI.ready();
|
||||
|
||||
manager.store.addExperiment(expected);
|
||||
await manager.store.addExperiment(expected);
|
||||
|
||||
let metadata = ExperimentAPI.getExperimentMetaData({ slug: expected.slug });
|
||||
|
||||
|
@ -140,7 +140,7 @@ add_task(async function test_getExperiment_feature() {
|
|||
branch: {
|
||||
slug: "treatment",
|
||||
value: { title: "hi" },
|
||||
feature: { featureId: "cfr", enabled: true },
|
||||
feature: { featureId: "cfr", enabled: true, value: null },
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -149,7 +149,7 @@ add_task(async function test_getExperiment_feature() {
|
|||
sandbox.stub(ExperimentAPI, "_store").get(() => ExperimentFakes.childStore());
|
||||
let exposureStub = sandbox.stub(ExperimentAPI, "recordExposureEvent");
|
||||
|
||||
manager.store.addExperiment(expected);
|
||||
await manager.store.addExperiment(expected);
|
||||
|
||||
// Wait to sync to child
|
||||
await TestUtils.waitForCondition(
|
||||
|
@ -268,7 +268,7 @@ add_task(async function test_addExperiment_eventEmit_add() {
|
|||
const experiment = ExperimentFakes.experiment("foo", {
|
||||
branch: {
|
||||
slug: "variant",
|
||||
feature: { featureId: "purple", enabled: true },
|
||||
feature: { featureId: "purple", enabled: true, value: null },
|
||||
},
|
||||
});
|
||||
const store = ExperimentFakes.store();
|
||||
|
@ -280,7 +280,7 @@ add_task(async function test_addExperiment_eventEmit_add() {
|
|||
ExperimentAPI.on("update", { slug: "foo" }, slugStub);
|
||||
ExperimentAPI.on("update", { featureId: "purple" }, featureStub);
|
||||
|
||||
store.addExperiment(experiment);
|
||||
await store.addExperiment(experiment);
|
||||
|
||||
Assert.equal(
|
||||
slugStub.callCount,
|
||||
|
@ -303,7 +303,7 @@ add_task(async function test_updateExperiment_eventEmit_add_and_update() {
|
|||
const experiment = ExperimentFakes.experiment("foo", {
|
||||
branch: {
|
||||
slug: "variant",
|
||||
feature: { featureId: "purple", enabled: true },
|
||||
feature: { featureId: "purple", enabled: true, value: null },
|
||||
},
|
||||
});
|
||||
const store = ExperimentFakes.store();
|
||||
|
@ -312,7 +312,7 @@ add_task(async function test_updateExperiment_eventEmit_add_and_update() {
|
|||
await store.init();
|
||||
await ExperimentAPI.ready();
|
||||
|
||||
store.addExperiment(experiment);
|
||||
await store.addExperiment(experiment);
|
||||
|
||||
ExperimentAPI.on("update", { slug: "foo" }, slugStub);
|
||||
ExperimentAPI.on("update", { featureId: "purple" }, featureStub);
|
||||
|
@ -337,7 +337,7 @@ add_task(async function test_updateExperiment_eventEmit_off() {
|
|||
const experiment = ExperimentFakes.experiment("foo", {
|
||||
branch: {
|
||||
slug: "variant",
|
||||
feature: { featureId: "purple", enabled: true },
|
||||
feature: { featureId: "purple", enabled: true, value: null },
|
||||
},
|
||||
});
|
||||
const store = ExperimentFakes.store();
|
||||
|
@ -349,7 +349,7 @@ add_task(async function test_updateExperiment_eventEmit_off() {
|
|||
ExperimentAPI.on("update", { slug: "foo" }, slugStub);
|
||||
ExperimentAPI.on("update", { featureId: "purple" }, featureStub);
|
||||
|
||||
store.addExperiment(experiment);
|
||||
await store.addExperiment(experiment);
|
||||
|
||||
ExperimentAPI.off("update:foo", slugStub);
|
||||
ExperimentAPI.off("update:purple", featureStub);
|
||||
|
@ -367,12 +367,12 @@ add_task(async function test_activateBranch() {
|
|||
const experiment = ExperimentFakes.experiment("foo", {
|
||||
branch: {
|
||||
slug: "variant",
|
||||
feature: { featureId: "green", enabled: true },
|
||||
feature: { featureId: "green", enabled: true, value: null },
|
||||
},
|
||||
});
|
||||
|
||||
await store.init();
|
||||
store.addExperiment(experiment);
|
||||
await store.addExperiment(experiment);
|
||||
|
||||
Assert.deepEqual(
|
||||
ExperimentAPI.activateBranch({ featureId: "green" }),
|
||||
|
@ -412,7 +412,7 @@ add_task(async function test_activateBranch_activationEvent() {
|
|||
});
|
||||
|
||||
await store.init();
|
||||
store.addExperiment(experiment);
|
||||
await store.addExperiment(experiment);
|
||||
// Adding stub later because `addExperiment` emits update events
|
||||
const stub = sandbox.stub(ExperimentAPI, "recordExposureEvent");
|
||||
ExperimentAPI.activateBranch({ featureId: "green" });
|
||||
|
@ -449,7 +449,7 @@ add_task(async function test_activateBranch_storeFailure() {
|
|||
});
|
||||
|
||||
await store.init();
|
||||
store.addExperiment(experiment);
|
||||
await store.addExperiment(experiment);
|
||||
// Adding stub later because `addExperiment` emits update events
|
||||
const stub = sandbox.stub(store, "emit");
|
||||
// Call activateBranch to trigger an activation event
|
||||
|
@ -476,7 +476,7 @@ add_task(async function test_activateBranch_noActivationEvent() {
|
|||
});
|
||||
|
||||
await store.init();
|
||||
store.addExperiment(experiment);
|
||||
await store.addExperiment(experiment);
|
||||
// Adding stub later because `addExperiment` emits update events
|
||||
const stub = sandbox.stub(store, "emit");
|
||||
// Call activateBranch to trigger an activation event
|
||||
|
|
|
@ -40,6 +40,7 @@ const FAKE_FEATURE_MANIFEST = {
|
|||
},
|
||||
};
|
||||
const FAKE_FEATURE_REMOTE_VALUE = {
|
||||
slug: "default-remote-value",
|
||||
variables: {
|
||||
enabled: true,
|
||||
},
|
||||
|
@ -68,7 +69,7 @@ add_task(async function test_ExperimentFeature_ready() {
|
|||
},
|
||||
});
|
||||
|
||||
manager.store.addExperiment(expected);
|
||||
await manager.store.addExperiment(expected);
|
||||
|
||||
await readyPromise;
|
||||
|
||||
|
@ -121,7 +122,7 @@ add_task(
|
|||
},
|
||||
});
|
||||
|
||||
manager.store.addExperiment(expected);
|
||||
await manager.store.addExperiment(expected);
|
||||
|
||||
setDefaultBranch(TEST_FALLBACK_PREF, `{"bar": 123}`);
|
||||
|
||||
|
@ -226,7 +227,10 @@ add_task(async function test_ExperimentFeature_test_helper_ready() {
|
|||
await ExperimentFakes.remoteDefaultsHelper({
|
||||
feature: featureInstance,
|
||||
store: manager.store,
|
||||
configuration: { variables: { remoteValue: "mochitest" }, enabled: true },
|
||||
configuration: {
|
||||
...FAKE_FEATURE_REMOTE_VALUE,
|
||||
variables: { remoteValue: "mochitest", enabled: true },
|
||||
},
|
||||
});
|
||||
|
||||
Assert.equal(featureInstance.isEnabled(), true, "enabled by remote config");
|
||||
|
@ -253,7 +257,7 @@ add_task(
|
|||
|
||||
await manager.store.ready();
|
||||
|
||||
manager.store.addExperiment(expected);
|
||||
await manager.store.addExperiment(expected);
|
||||
|
||||
const exposureSpy = sandbox.spy(ExperimentAPI, "recordExposureEvent");
|
||||
manager.store.updateRemoteConfigs("foo", {
|
||||
|
@ -305,7 +309,7 @@ add_task(async function test_ExperimentFeature_isEnabled_no_exposure() {
|
|||
|
||||
sandbox.stub(ExperimentAPI, "_store").get(() => manager.store);
|
||||
|
||||
manager.store.addExperiment(expected);
|
||||
await manager.store.addExperiment(expected);
|
||||
|
||||
const exposureSpy = sandbox.spy(ExperimentAPI, "recordExposureEvent");
|
||||
|
||||
|
@ -334,7 +338,7 @@ add_task(async function test_record_exposure_event() {
|
|||
"should not emit an exposure event when no experiment is active"
|
||||
);
|
||||
|
||||
manager.store.addExperiment(
|
||||
await manager.store.addExperiment(
|
||||
ExperimentFakes.experiment("blah", {
|
||||
branch: {
|
||||
slug: "treatment",
|
||||
|
@ -363,7 +367,7 @@ add_task(async function test_record_exposure_event_once() {
|
|||
const exposureSpy = sandbox.spy(ExperimentAPI, "recordExposureEvent");
|
||||
sandbox.stub(ExperimentAPI, "_store").get(() => manager.store);
|
||||
|
||||
manager.store.addExperiment(
|
||||
await manager.store.addExperiment(
|
||||
ExperimentFakes.experiment("blah", {
|
||||
branch: {
|
||||
slug: "treatment",
|
||||
|
@ -391,7 +395,7 @@ add_task(async function test_prevent_double_exposure_getValue() {
|
|||
const exposureSpy = sandbox.spy(ExperimentAPI, "recordExposureEvent");
|
||||
sandbox.stub(ExperimentAPI, "_store").get(() => manager.store);
|
||||
|
||||
manager.store.addExperiment(
|
||||
await manager.store.addExperiment(
|
||||
ExperimentFakes.experiment("blah", {
|
||||
branch: {
|
||||
slug: "treatment",
|
||||
|
@ -422,7 +426,7 @@ add_task(async function test_prevent_double_exposure_isEnabled() {
|
|||
const exposureSpy = sandbox.spy(ExperimentAPI, "recordExposureEvent");
|
||||
sandbox.stub(ExperimentAPI, "_store").get(() => manager.store);
|
||||
|
||||
manager.store.addExperiment(
|
||||
await manager.store.addExperiment(
|
||||
ExperimentFakes.experiment("blah", {
|
||||
branch: {
|
||||
slug: "treatment",
|
||||
|
@ -452,13 +456,15 @@ add_task(async function test_set_remote_before_ready() {
|
|||
sandbox.stub(ExperimentAPI, "_store").get(() => manager.store);
|
||||
const feature = new ExperimentFeature("foo", FAKE_FEATURE_MANIFEST);
|
||||
|
||||
Assert.throws(
|
||||
() =>
|
||||
ExperimentFakes.remoteDefaultsHelper({
|
||||
feature,
|
||||
store: manager.store,
|
||||
configuration: { variables: { test: true } },
|
||||
}),
|
||||
await Assert.rejects(
|
||||
ExperimentFakes.remoteDefaultsHelper({
|
||||
feature,
|
||||
store: manager.store,
|
||||
configuration: {
|
||||
...FAKE_FEATURE_REMOTE_VALUE,
|
||||
variables: { test: true, enabled: true },
|
||||
},
|
||||
}),
|
||||
/Store not ready/,
|
||||
"Throws if used before init finishes"
|
||||
);
|
||||
|
@ -468,7 +474,10 @@ add_task(async function test_set_remote_before_ready() {
|
|||
await ExperimentFakes.remoteDefaultsHelper({
|
||||
feature,
|
||||
store: manager.store,
|
||||
configuration: { variables: { test: true } },
|
||||
configuration: {
|
||||
...FAKE_FEATURE_REMOTE_VALUE,
|
||||
variables: { test: true, enabled: true },
|
||||
},
|
||||
});
|
||||
|
||||
Assert.ok(feature.getValue().test, "Successfully set");
|
||||
|
@ -494,12 +503,15 @@ add_task(async function test_isEnabled_backwards_compatible() {
|
|||
await ExperimentFakes.remoteDefaultsHelper({
|
||||
feature,
|
||||
store: manager.store,
|
||||
configuration: { variables: {}, enabled: false },
|
||||
configuration: {
|
||||
...FAKE_FEATURE_REMOTE_VALUE,
|
||||
variables: { enabled: false },
|
||||
},
|
||||
});
|
||||
|
||||
Assert.ok(!feature.isEnabled(), "Disabled based on remote configs");
|
||||
|
||||
manager.store.addExperiment(
|
||||
await manager.store.addExperiment(
|
||||
ExperimentFakes.experiment("blah", {
|
||||
branch: {
|
||||
slug: "treatment",
|
||||
|
|
|
@ -11,10 +11,6 @@ const { TestUtils } = ChromeUtils.import(
|
|||
"resource://testing-common/TestUtils.jsm"
|
||||
);
|
||||
|
||||
const { XPCOMUtils } = ChromeUtils.import(
|
||||
"resource://gre/modules/XPCOMUtils.jsm"
|
||||
);
|
||||
|
||||
const { cleanupStorePrefCache } = ExperimentFakes;
|
||||
|
||||
async function setupForExperimentFeature() {
|
||||
|
@ -88,7 +84,7 @@ add_task(
|
|||
},
|
||||
});
|
||||
|
||||
manager.store.addExperiment(recipe);
|
||||
await manager.store.addExperiment(recipe);
|
||||
|
||||
const featureInstance = new ExperimentFeature(
|
||||
FEATURE_ID,
|
||||
|
|
|
@ -11,10 +11,6 @@ const { TestUtils } = ChromeUtils.import(
|
|||
"resource://testing-common/TestUtils.jsm"
|
||||
);
|
||||
|
||||
const { XPCOMUtils } = ChromeUtils.import(
|
||||
"resource://gre/modules/XPCOMUtils.jsm"
|
||||
);
|
||||
|
||||
const { cleanupStorePrefCache } = ExperimentFakes;
|
||||
|
||||
async function setupForExperimentFeature() {
|
||||
|
@ -82,7 +78,7 @@ add_task(async function test_ExperimentFeature_getValue_prefsOverExperiment() {
|
|||
},
|
||||
});
|
||||
|
||||
manager.store.addExperiment(recipe);
|
||||
await manager.store.addExperiment(recipe);
|
||||
|
||||
const featureInstance = new ExperimentFeature(
|
||||
FEATURE_ID,
|
||||
|
|
|
@ -4,15 +4,9 @@ const {
|
|||
ExperimentAPI,
|
||||
_ExperimentFeature: ExperimentFeature,
|
||||
} = ChromeUtils.import("resource://nimbus/ExperimentAPI.jsm");
|
||||
const { ExperimentFakes } = ChromeUtils.import(
|
||||
"resource://testing-common/NimbusTestUtils.jsm"
|
||||
);
|
||||
const { TestUtils } = ChromeUtils.import(
|
||||
"resource://testing-common/TestUtils.jsm"
|
||||
);
|
||||
const { XPCOMUtils } = ChromeUtils.import(
|
||||
"resource://gre/modules/XPCOMUtils.jsm"
|
||||
);
|
||||
const { AppConstants } = ChromeUtils.import(
|
||||
"resource://gre/modules/AppConstants.jsm"
|
||||
);
|
||||
|
|
|
@ -5,17 +5,11 @@ const {
|
|||
NimbusFeatures,
|
||||
_ExperimentFeature: ExperimentFeature,
|
||||
} = ChromeUtils.import("resource://nimbus/ExperimentAPI.jsm");
|
||||
const { ExperimentFakes } = ChromeUtils.import(
|
||||
"resource://testing-common/NimbusTestUtils.jsm"
|
||||
);
|
||||
const { TestUtils } = ChromeUtils.import(
|
||||
"resource://testing-common/TestUtils.jsm"
|
||||
);
|
||||
|
||||
const { Ajv } = ChromeUtils.import("resource://testing-common/ajv-4.1.1.js");
|
||||
const { XPCOMUtils } = ChromeUtils.import(
|
||||
"resource://gre/modules/XPCOMUtils.jsm"
|
||||
);
|
||||
|
||||
Cu.importGlobalProperties(["fetch"]);
|
||||
|
||||
XPCOMUtils.defineLazyGetter(this, "fetchSchema", async () => {
|
||||
|
|
|
@ -1,8 +1,5 @@
|
|||
"use strict";
|
||||
|
||||
const { ExperimentFakes } = ChromeUtils.import(
|
||||
"resource://testing-common/NimbusTestUtils.jsm"
|
||||
);
|
||||
const { NormandyTestUtils } = ChromeUtils.import(
|
||||
"resource://testing-common/NormandyTestUtils.jsm"
|
||||
);
|
||||
|
@ -31,10 +28,14 @@ const { SYNC_DATA_PREF_BRANCH } = ExperimentStore;
|
|||
add_task(async function test_add_to_store() {
|
||||
const manager = ExperimentFakes.manager();
|
||||
const recipe = ExperimentFakes.recipe("foo");
|
||||
const enrollPromise = new Promise(resolve =>
|
||||
manager.store.on("update:foo", resolve)
|
||||
);
|
||||
|
||||
await manager.onStartup();
|
||||
|
||||
await manager.enroll(recipe);
|
||||
await manager.enroll(recipe, "test_add_to_store");
|
||||
await enrollPromise;
|
||||
const experiment = manager.store.get("foo");
|
||||
|
||||
Assert.ok(experiment, "should add an experiment with slug foo");
|
||||
|
@ -53,6 +54,9 @@ add_task(
|
|||
async function test_setExperimentActive_sendEnrollmentTelemetry_called() {
|
||||
const manager = ExperimentFakes.manager();
|
||||
const sandbox = sinon.createSandbox();
|
||||
const enrollPromise = new Promise(resolve =>
|
||||
manager.store.on("update:foo", resolve)
|
||||
);
|
||||
sandbox.spy(manager, "setExperimentActive");
|
||||
sandbox.spy(manager, "sendEnrollmentTelemetry");
|
||||
|
||||
|
@ -60,7 +64,11 @@ add_task(
|
|||
|
||||
await manager.onStartup();
|
||||
|
||||
await manager.enroll(ExperimentFakes.recipe("foo"));
|
||||
await manager.enroll(
|
||||
ExperimentFakes.recipe("foo"),
|
||||
"test_setExperimentActive_sendEnrollmentTelemetry_called"
|
||||
);
|
||||
await enrollPromise;
|
||||
const experiment = manager.store.get("foo");
|
||||
|
||||
Assert.equal(
|
||||
|
@ -91,10 +99,10 @@ add_task(async function test_failure_name_conflict() {
|
|||
await manager.onStartup();
|
||||
|
||||
// simulate adding a previouly enrolled experiment
|
||||
manager.store.addExperiment(ExperimentFakes.experiment("foo"));
|
||||
await manager.store.addExperiment(ExperimentFakes.experiment("foo"));
|
||||
|
||||
await Assert.rejects(
|
||||
manager.enroll(ExperimentFakes.recipe("foo")),
|
||||
manager.enroll(ExperimentFakes.recipe("foo"), "test_failure_name_conflict"),
|
||||
/An experiment with the slug "foo" already exists/,
|
||||
"should throw if a conflicting experiment exists"
|
||||
);
|
||||
|
@ -121,15 +129,15 @@ add_task(async function test_failure_group_conflict() {
|
|||
// These should not be allowed to exist simultaneously.
|
||||
const existingBranch = {
|
||||
slug: "treatment",
|
||||
feature: { featureId: "pink", enabled: true },
|
||||
feature: { featureId: "pink", enabled: true, value: {} },
|
||||
};
|
||||
const newBranch = {
|
||||
slug: "treatment",
|
||||
feature: { featureId: "pink", enabled: true },
|
||||
feature: { featureId: "pink", enabled: true, value: {} },
|
||||
};
|
||||
|
||||
// simulate adding an experiment with a conflicting group "pink"
|
||||
manager.store.addExperiment(
|
||||
await manager.store.addExperiment(
|
||||
ExperimentFakes.experiment("foo", {
|
||||
branch: existingBranch,
|
||||
})
|
||||
|
@ -139,7 +147,8 @@ add_task(async function test_failure_group_conflict() {
|
|||
sandbox.stub(manager, "chooseBranch").returns(newBranch);
|
||||
Assert.equal(
|
||||
await manager.enroll(
|
||||
ExperimentFakes.recipe("bar", { branches: [newBranch] })
|
||||
ExperimentFakes.recipe("bar", { branches: [newBranch] }),
|
||||
"test_failure_group_conflict"
|
||||
),
|
||||
null,
|
||||
"should not enroll if there is a feature conflict"
|
||||
|
@ -232,8 +241,12 @@ add_task(async function enroll_in_reference_aw_experiment() {
|
|||
recipe.bucketConfig.count = recipe.bucketConfig.total;
|
||||
|
||||
const manager = ExperimentFakes.manager();
|
||||
const enrollPromise = new Promise(resolve =>
|
||||
manager.store.on("update:reference-aw", resolve)
|
||||
);
|
||||
await manager.onStartup();
|
||||
await manager.enroll(recipe);
|
||||
await manager.enroll(recipe, "enroll_in_reference_aw_experiment");
|
||||
await enrollPromise;
|
||||
|
||||
Assert.ok(manager.store.get("reference-aw"), "Successful onboarding");
|
||||
let prefValue = Services.prefs.getStringPref(
|
||||
|
@ -251,13 +264,19 @@ add_task(async function enroll_in_reference_aw_experiment() {
|
|||
add_task(async function test_forceEnroll_cleanup() {
|
||||
const manager = ExperimentFakes.manager();
|
||||
const sandbox = sinon.createSandbox();
|
||||
const fooEnrollPromise = new Promise(resolve =>
|
||||
manager.store.on("update:foo", resolve)
|
||||
);
|
||||
const barEnrollPromise = new Promise(resolve =>
|
||||
manager.store.on("update:optin-bar", resolve)
|
||||
);
|
||||
let unenrollStub = sandbox.spy(manager, "unenroll");
|
||||
let existingRecipe = ExperimentFakes.recipe("foo", {
|
||||
branches: [
|
||||
{
|
||||
slug: "treatment",
|
||||
ratio: 1,
|
||||
feature: { featureId: "force-enrollment", enabled: true },
|
||||
feature: { featureId: "force-enrollment", enabled: true, value: {} },
|
||||
},
|
||||
],
|
||||
});
|
||||
|
@ -266,16 +285,18 @@ add_task(async function test_forceEnroll_cleanup() {
|
|||
{
|
||||
slug: "treatment",
|
||||
ratio: 1,
|
||||
feature: { featureId: "force-enrollment", enabled: true },
|
||||
feature: { featureId: "force-enrollment", enabled: true, value: {} },
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
await manager.onStartup();
|
||||
await manager.enroll(existingRecipe);
|
||||
await manager.enroll(existingRecipe, "test_forceEnroll_cleanup");
|
||||
await fooEnrollPromise;
|
||||
|
||||
let setExperimentActiveSpy = sandbox.spy(manager, "setExperimentActive");
|
||||
manager.forceEnroll(forcedRecipe, forcedRecipe.branches[0]);
|
||||
await barEnrollPromise;
|
||||
|
||||
Assert.ok(unenrollStub.called, "Unenrolled from existing experiment");
|
||||
Assert.equal(
|
||||
|
|
|
@ -6,9 +6,6 @@ const { _ExperimentManager } = ChromeUtils.import(
|
|||
const { ExperimentStore } = ChromeUtils.import(
|
||||
"resource://nimbus/lib/ExperimentStore.jsm"
|
||||
);
|
||||
const { ExperimentFakes } = ChromeUtils.import(
|
||||
"resource://testing-common/NimbusTestUtils.jsm"
|
||||
);
|
||||
const { Sampling } = ChromeUtils.import(
|
||||
"resource://gre/modules/components-utils/Sampling.jsm"
|
||||
);
|
||||
|
@ -163,8 +160,13 @@ add_task(async function test_onRecipe_isEnrollmentPaused() {
|
|||
const updatedRecipe = ExperimentFakes.recipe("foo", {
|
||||
isEnrollmentPaused: true,
|
||||
});
|
||||
await manager.enroll(fooRecipe);
|
||||
let enrollmentPromise = new Promise(resolve =>
|
||||
manager.store.on(`update:${fooRecipe.slug}`, resolve)
|
||||
);
|
||||
await manager.enroll(fooRecipe, "test");
|
||||
await enrollmentPromise;
|
||||
await manager.onRecipe(updatedRecipe, "test");
|
||||
console.log("XXX", manager.updateEnrollment.callCount);
|
||||
Assert.equal(
|
||||
manager.updateEnrollment.calledWith(updatedRecipe),
|
||||
true,
|
||||
|
@ -186,10 +188,15 @@ add_task(async function test_onFinalize_unenroll() {
|
|||
|
||||
// Add an experiment to the store without calling .onRecipe
|
||||
// This simulates an enrollment having happened in the past.
|
||||
manager.store.addExperiment(ExperimentFakes.experiment("foo"));
|
||||
let recipe0 = ExperimentFakes.experiment("foo", {
|
||||
experimentType: "unittest",
|
||||
userFacingName: "foo",
|
||||
userFacingDescription: "foo",
|
||||
lastSeen: Date.now().toLocaleString(),
|
||||
source: "test",
|
||||
});
|
||||
await manager.store.addExperiment(recipe0);
|
||||
|
||||
// Simulate adding some other recipes
|
||||
await manager.onStartup();
|
||||
const recipe1 = ExperimentFakes.recipe("bar");
|
||||
// Unique features to prevent overlap
|
||||
recipe1.branches[0].feature.featureId = "red";
|
||||
|
|
|
@ -1,8 +1,5 @@
|
|||
"use strict";
|
||||
|
||||
const { ExperimentFakes } = ChromeUtils.import(
|
||||
"resource://testing-common/NimbusTestUtils.jsm"
|
||||
);
|
||||
const { NormandyTestUtils } = ChromeUtils.import(
|
||||
"resource://testing-common/NormandyTestUtils.jsm"
|
||||
);
|
||||
|
@ -31,7 +28,7 @@ add_task(async function test_set_inactive() {
|
|||
const manager = ExperimentFakes.manager();
|
||||
|
||||
await manager.onStartup();
|
||||
manager.store.addExperiment(ExperimentFakes.experiment("foo"));
|
||||
await manager.store.addExperiment(ExperimentFakes.experiment("foo"));
|
||||
|
||||
manager.unenroll("foo", "some-reason");
|
||||
|
||||
|
@ -49,7 +46,7 @@ add_task(async function test_unenroll_opt_out() {
|
|||
const experiment = ExperimentFakes.experiment("foo");
|
||||
|
||||
await manager.onStartup();
|
||||
manager.store.addExperiment(experiment);
|
||||
await manager.store.addExperiment(experiment);
|
||||
|
||||
Services.prefs.setBoolPref(STUDIES_OPT_OUT_PREF, false);
|
||||
|
||||
|
@ -83,7 +80,7 @@ add_task(async function test_setExperimentInactive_called() {
|
|||
const experiment = ExperimentFakes.experiment("foo");
|
||||
|
||||
await manager.onStartup();
|
||||
manager.store.addExperiment(experiment);
|
||||
await manager.store.addExperiment(experiment);
|
||||
|
||||
manager.unenroll("foo", "some-reason");
|
||||
|
||||
|
@ -99,7 +96,7 @@ add_task(async function test_send_unenroll_event() {
|
|||
const experiment = ExperimentFakes.experiment("foo");
|
||||
|
||||
await manager.onStartup();
|
||||
manager.store.addExperiment(experiment);
|
||||
await manager.store.addExperiment(experiment);
|
||||
|
||||
manager.unenroll("foo", "some-reason");
|
||||
|
||||
|
@ -126,7 +123,7 @@ add_task(async function test_undefined_reason() {
|
|||
const experiment = ExperimentFakes.experiment("foo");
|
||||
|
||||
await manager.onStartup();
|
||||
manager.store.addExperiment(experiment);
|
||||
await manager.store.addExperiment(experiment);
|
||||
|
||||
manager.unenroll("foo");
|
||||
|
||||
|
|
|
@ -1,8 +1,5 @@
|
|||
"use strict";
|
||||
|
||||
const { ExperimentFakes } = ChromeUtils.import(
|
||||
"resource://testing-common/NimbusTestUtils.jsm"
|
||||
);
|
||||
const { ExperimentStore } = ChromeUtils.import(
|
||||
"resource://nimbus/lib/ExperimentStore.jsm"
|
||||
);
|
||||
|
|
|
@ -4,9 +4,6 @@ const { FeatureManifest } = ChromeUtils.import(
|
|||
"resource://nimbus/FeatureManifest.js"
|
||||
);
|
||||
const { Ajv } = ChromeUtils.import("resource://testing-common/ajv-4.1.1.js");
|
||||
const { XPCOMUtils } = ChromeUtils.import(
|
||||
"resource://gre/modules/XPCOMUtils.jsm"
|
||||
);
|
||||
Cu.importGlobalProperties(["fetch"]);
|
||||
|
||||
XPCOMUtils.defineLazyGetter(this, "fetchSchema", async () => {
|
||||
|
|
|
@ -7,9 +7,6 @@ const { FileTestUtils } = ChromeUtils.import(
|
|||
const { TestUtils } = ChromeUtils.import(
|
||||
"resource://testing-common/TestUtils.jsm"
|
||||
);
|
||||
const { ExperimentFakes } = ChromeUtils.import(
|
||||
"resource://testing-common/NimbusTestUtils.jsm"
|
||||
);
|
||||
|
||||
const PATH = FileTestUtils.getTempFile("shared-data-map").path;
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче