Bug 1670293 - RSExperimentLoader should expose evaluateJexl for testing r=andreio

Differential Revision: https://phabricator.services.mozilla.com/D104401
This commit is contained in:
Kate Hudson 2021-02-09 21:25:30 +00:00
Родитель 0bdf21e675
Коммит f4213fbbfe
3 изменённых файлов: 133 добавлений и 20 удалений

Просмотреть файл

@ -117,32 +117,48 @@ class _RemoteSettingsExperimentLoader {
this._initialized = false;
}
async evaluateJexl(jexlString, customContext) {
if (customContext && !customContext.experiment) {
throw new Error(
"Expected an .experiment property in second param of this function"
);
}
const context = TargetingContext.combineContexts(
customContext,
this.manager.createTargetingContext(),
ASRouterTargeting.Environment
);
log.debug("Testing targeting expression:", jexlString);
const targetingContext = new TargetingContext(context);
let result = false;
try {
result = await targetingContext.evalWithDefault(jexlString);
} catch (e) {
log.debug("Targeting failed because of an error");
Cu.reportError(e);
}
return result;
}
/**
* Checks targeting of a recipe if it is defined
* @param {Recipe} recipe
* @param {{[key: string]: any}} customContext A custom filter context
* @returns {Promise<boolean>} Should we process the recipe?
*/
async checkTargeting(recipe, customContext = {}) {
const context = TargetingContext.combineContexts(
{ experiment: recipe },
customContext,
ASRouterTargeting.Environment
);
const { targeting } = recipe;
if (!targeting) {
async checkTargeting(recipe) {
if (!recipe.targeting) {
log.debug("No targeting for recipe, so it matches automatically");
return true;
}
log.debug("Testing targeting expression:", targeting);
const targetingContext = new TargetingContext(context);
let result = false;
try {
result = await targetingContext.evalWithDefault(targeting);
} catch (e) {
log.debug("Targeting failed because of an error");
Cu.reportError(e);
}
const result = await this.evaluateJexl(recipe.targeting, {
experiment: recipe,
});
return Boolean(result);
}
@ -172,10 +188,8 @@ class _RemoteSettingsExperimentLoader {
let matches = 0;
if (recipes && !loadingError) {
const context = this.manager.createTargetingContext();
for (const r of recipes) {
if (await this.checkTargeting(r, context)) {
if (await this.checkTargeting(r)) {
matches++;
log.debug(`${r.id} matched`);
await this.manager.onRecipe(r, "rs-loader");

Просмотреть файл

@ -1,4 +1,5 @@
[DEFAULT]
[browser_remotesettings_experiment_enroll.js]
[browser_experiment_evaluate_jexl.js]
tags = remote-settings

Просмотреть файл

@ -0,0 +1,98 @@
"use strict";
const { RemoteSettingsExperimentLoader } = ChromeUtils.import(
"resource://messaging-system/lib/RemoteSettingsExperimentLoader.jsm"
);
const { ExperimentManager } = ChromeUtils.import(
"resource://messaging-system/experiments/ExperimentManager.jsm"
);
const { ExperimentFakes } = ChromeUtils.import(
"resource://testing-common/MSTestUtils.jsm"
);
add_task(async function setup() {
await SpecialPowers.pushPrefEnv({
set: [
["messaging-system.log", "all"],
["app.shield.optoutstudies.enabled", true],
],
});
registerCleanupFunction(async () => {
await SpecialPowers.popPrefEnv();
});
});
const FAKE_CONTEXT = {
experiment: ExperimentFakes.recipe("fake-test-experiment"),
};
add_task(async function test_throws_if_no_experiment_in_context() {
await Assert.rejects(
RemoteSettingsExperimentLoader.evaluateJexl("true", { customThing: 1 }),
/Expected an .experiment property/,
"should throw if experiment is not passed to the custom context"
);
});
add_task(async function test_evaluate_jexl() {
Assert.deepEqual(
await RemoteSettingsExperimentLoader.evaluateJexl(
`["hello"]`,
FAKE_CONTEXT
),
["hello"],
"should return the evaluated result of a jexl expression"
);
});
add_task(async function test_evaluate_custom_context() {
const result = await RemoteSettingsExperimentLoader.evaluateJexl(
"experiment.slug",
FAKE_CONTEXT
);
Assert.equal(
result,
"fake-test-experiment",
"should have the custom .experiment context"
);
});
add_task(async function test_evaluate_active_experiments() {
const result = await RemoteSettingsExperimentLoader.evaluateJexl(
"isFirstStartup",
FAKE_CONTEXT
);
Assert.equal(
typeof result,
"boolean",
"should have a .isFirstStartup property from ExperimentManager "
);
});
add_task(async function test_evaluate_active_experiments() {
// Add an experiment to active experiments
const slug = "foo" + Date.now();
ExperimentManager.store.addExperiment(ExperimentFakes.experiment(slug));
registerCleanupFunction(() => {
ExperimentManager.store._deleteForTests(slug);
});
Assert.equal(
await RemoteSettingsExperimentLoader.evaluateJexl(
`"${slug}" in activeExperiments`,
FAKE_CONTEXT
),
true,
"should find an active experiment"
);
Assert.equal(
await RemoteSettingsExperimentLoader.evaluateJexl(
`"does-not-exist-fake" in activeExperiments`,
FAKE_CONTEXT
),
false,
"should not find an experiment that doesn't exist"
);
});