gecko-dev/toolkit/components/nimbus/test/unit/test_ExperimentAPI_NimbusFeatures.js

242 lines
6.3 KiB
JavaScript

"use strict";
const { ExperimentAPI, NimbusFeatures, 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 () => {
const response = await fetch(
"resource://testing-common/ExperimentFeatureRemote.schema.json"
);
const schema = await response.json();
if (!schema) {
throw new Error("Failed to load ExperimentFeatureRemote schema");
}
return schema.definitions.RemoteFeatureConfigurations;
});
const REMOTE_CONFIGURATION = Object.freeze({
id: "aboutwelcome",
configurations: [
{
slug: "non-matching-configuration",
description: "This configuration does not match because of targeting.",
variables: { skipFocus: false, remoteValue: false, enabled: false },
targeting: "false",
},
{
slug: "matching-configuration",
description: "This configuration will match targeting.",
variables: { skipFocus: true, remoteValue: true, enabled: true },
targeting: "true",
},
],
});
async function setupForExperimentFeature() {
const sandbox = sinon.createSandbox();
const manager = ExperimentFakes.manager();
await manager.onStartup();
sandbox.stub(ExperimentAPI, "_store").get(() => manager.store);
return { sandbox, manager };
}
add_task(async function validSchema() {
const ajv = new Ajv({ allErrors: true });
const validate = ajv.compile(await fetchSchema);
Assert.ok(
validate(REMOTE_CONFIGURATION),
JSON.stringify(validate.errors, null, 2)
);
});
add_task(async function readyCallAfterStore_with_remote_value() {
let { sandbox, manager } = await setupForExperimentFeature();
let feature = new ExperimentFeature("aboutwelcome");
Assert.equal(
feature.getValue().remoteValue,
undefined,
"Feature is not defined"
);
manager.store.updateRemoteConfigs(
"aboutwelcome",
REMOTE_CONFIGURATION.configurations[1]
);
// Should resolve because the store will return a dummy remote value
await feature.ready();
Assert.ok(feature.getValue().skipFocus, "Loads value from store");
manager.store._deleteForTests("aboutwelcome");
sandbox.restore();
});
add_task(async function has_sync_value_before_ready() {
let { manager } = await setupForExperimentFeature();
let feature = new ExperimentFeature("aboutwelcome");
Assert.equal(
feature.getValue().remoteValue,
undefined,
"Feature is not defined"
);
Services.prefs.setStringPref(
"nimbus.syncdefaultsstore.aboutwelcome",
JSON.stringify(REMOTE_CONFIGURATION.configurations[0])
);
Assert.equal(
feature.getValue().remoteValue,
REMOTE_CONFIGURATION.configurations[0].variables.remoteValue,
"Sync load from pref"
);
manager.store._deleteForTests("aboutwelcome");
});
add_task(async function update_remote_defaults_onUpdate() {
let { sandbox, manager } = await setupForExperimentFeature();
let feature = new ExperimentFeature("aboutwelcome");
let stub = sandbox.stub();
feature.onUpdate(stub);
Assert.equal(
feature.getValue().remoteValue,
undefined,
"Feature is not defined"
);
manager.store.updateRemoteConfigs(
"aboutwelcome",
REMOTE_CONFIGURATION.configurations[1]
);
Assert.ok(stub.called, "update event called");
Assert.equal(stub.callCount, 1, "Called once for remote configs");
Assert.equal(
stub.firstCall.args[1],
"remote-defaults-update",
"Correct reason"
);
manager.store._deleteForTests("aboutwelcome");
sandbox.restore();
});
add_task(async function update_remote_defaults_readyPromise() {
let { sandbox, manager } = await setupForExperimentFeature();
let feature = new ExperimentFeature("aboutwelcome");
let stub = sandbox.stub();
let promise = feature.ready();
feature.onUpdate(stub);
Assert.equal(
feature.getValue().remoteValue,
undefined,
"Feature is not defined"
);
manager.store.updateRemoteConfigs(
"aboutwelcome",
REMOTE_CONFIGURATION.configurations[1]
);
await promise;
manager.store._deleteForTests("aboutwelcome");
sandbox.restore();
});
add_task(async function update_remote_defaults_enabled() {
let { sandbox, manager } = await setupForExperimentFeature();
let feature = new ExperimentFeature("aboutwelcome");
let promise = feature.ready();
Assert.equal(
feature.isEnabled(),
true,
"Feature is enabled by manifest.variables.enabled"
);
manager.store.updateRemoteConfigs(
"aboutwelcome",
REMOTE_CONFIGURATION.configurations[0]
);
await promise;
Assert.ok(
!feature.isEnabled(),
"Feature is disabled by remote configuration"
);
manager.store._deleteForTests("aboutwelcome");
sandbox.restore();
});
// If the branch data returned from the store is not modified
// this test should not throw
add_task(async function test_getValue_no_mutation() {
let { sandbox, manager } = await setupForExperimentFeature();
sandbox.stub(manager.store, "getExperimentForFeature").returns(
Cu.cloneInto(
{
branch: {
feature: { value: { mochitest: true } },
},
},
{},
{ deepFreeze: true }
)
);
let feature = new ExperimentFeature("aboutwelcome");
Assert.ok(feature.getValue().mochitest, "Got back the expected feature");
sandbox.restore();
});
add_task(async function remote_isEarlyStartup_config() {
let { manager } = await setupForExperimentFeature();
let feature = new ExperimentFeature("password-autocomplete");
manager.store.updateRemoteConfigs("password-autocomplete", {
slug: "remote-config-isEarlyStartup",
description: "This feature normally is not marked isEarlyStartup",
variables: { remote: true },
isEarlyStartup: true,
});
await feature.ready();
Assert.ok(
Services.prefs.prefHasUserValue(
"nimbus.syncdefaultsstore.password-autocomplete"
),
"Configuration is marked early startup"
);
Services.prefs.clearUserPref(
"nimbus.syncdefaultsstore.password-autocomplete"
);
});