Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
131 changes: 106 additions & 25 deletions src/__tests__/context.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2055,9 +2055,8 @@ describe("Context", () => {
});

describe("rules evaluation", () => {
const buildRulesResponse = (overrides = {}, responseOverrides = {}) => ({
const buildRulesResponse = (overrides = {}) => ({
...getContextResponse,
...responseOverrides,
experiments: getContextResponse.experiments.map((x) => {
if (x.name === "exp_test_abc") {
return { ...x, ...overrides };
Expand Down Expand Up @@ -2093,12 +2092,12 @@ describe("Context", () => {
name: "Production Only",
type: "assign",
conditions: { and: [{ eq: [{ var: "country" }, { value: "US" }] }] },
environments: [10],
environments: ["production"],
variant: 1,
},
],
}),
}, { environment_id: 10 });
});

const rulesStrictContextResponse = buildRulesResponse({
audienceStrict: true,
Expand Down Expand Up @@ -2132,30 +2131,25 @@ describe("Context", () => {
expect(context.treatment("exp_test_abc")).toEqual(expectedVariants["exp_test_abc"]);
});

it("should skip rule when environment id does not match", () => {
const stagingResponse = {
...envScopedRulesContextResponse,
environment_id: 20,
};
const context = new Context(sdk, contextOptions, contextParams, stagingResponse);
it("should skip rule when environment name does not match", () => {
client.getEnvironment.mockReturnValueOnce("staging");
const context = new Context(sdk, contextOptions, contextParams, envScopedRulesContextResponse);
context.attribute("country", "US");
// Rule scoped to env 10, context has env 20 — should get normal assignment (2)
// Rule scoped to "production", client has "staging" — should get normal assignment (2)
expect(context.treatment("exp_test_abc")).toEqual(expectedVariants["exp_test_abc"]);
});

it("should skip environment-scoped rules when API response has no environment_id", () => {
const noEnvIdResponse = { ...envScopedRulesContextResponse };
delete noEnvIdResponse.environment_id;
const context = new Context(sdk, contextOptions, contextParams, noEnvIdResponse);
it("should skip environment-scoped rules when getEnvironment returns undefined", () => {
client.getEnvironment.mockReturnValueOnce(undefined);
const context = new Context(sdk, contextOptions, contextParams, envScopedRulesContextResponse);
context.attribute("country", "US");
// Rule requires env 10, but no environment_id in response — should get normal assignment
expect(context.treatment("exp_test_abc")).toEqual(expectedVariants["exp_test_abc"]);
});

it("should match rule when environment id matches", () => {
it("should match rule when environment name matches", () => {
const context = new Context(sdk, contextOptions, contextParams, envScopedRulesContextResponse);
context.attribute("country", "US");
// Rule scoped to env 10, context has env 10 — should get rule variant (1)
// Rule scoped to "production", client has "production" — should get rule variant (1)
expect(context.treatment("exp_test_abc")).toEqual(1);
});

Expand Down Expand Up @@ -2218,7 +2212,7 @@ describe("Context", () => {
});
});

it("should set correct flags when rule matches with audienceMismatch", (done) => {
it("should not evaluate audience when rule matches (audienceMismatch stays false)", (done) => {
const context = new Context(sdk, contextOptions, contextParams, rulesStrictContextResponse);
context.attribute("country", "US");
expect(context.treatment("exp_test_abc")).toEqual(1);
Expand All @@ -2235,7 +2229,7 @@ describe("Context", () => {
overridden: false,
fullOn: false,
custom: false,
audienceMismatch: true,
audienceMismatch: false,
ruleOverride: true,
});
done();
Expand Down Expand Up @@ -2333,6 +2327,25 @@ describe("Context", () => {
expect(context.treatment("exp_test_abc")).toEqual(0);
});

it("should set audienceMismatch true when no rule matches and audience mismatches in strict mode", (done) => {
const context = new Context(sdk, contextOptions, contextParams, rulesStrictContextResponse);
context.attribute("country", "GB");
expect(context.treatment("exp_test_abc")).toEqual(0);

publisher.publish.mockReturnValue(Promise.resolve());

context.publish().then(() => {
const publishCall = publisher.publish.mock.calls[0][0];
const exposure = publishCall.exposures.find((e) => e.name === "exp_test_abc");
expect(exposure).toMatchObject({
variant: 0,
ruleOverride: false,
audienceMismatch: true,
});
done();
});
});

it("should return correct variableValue when rule forces a different variant", () => {
const context = new Context(sdk, contextOptions, contextParams, rulesContextResponse);
context.attribute("country", "US");
Expand Down Expand Up @@ -2407,7 +2420,7 @@ describe("Context", () => {
expect(context.treatment("exp_test_abc")).toEqual(expectedVariants["exp_test_abc"]);
});

it("should match rule scoped to multiple environment ids", () => {
it("should match rule scoped to multiple environment names", () => {
const multiEnvResponse = buildRulesResponse({
audience: JSON.stringify({
filter: [{ value: true }],
Expand All @@ -2418,12 +2431,12 @@ describe("Context", () => {
name: "Prod and Staging",
type: "assign",
conditions: { and: [{ eq: [{ var: "country" }, { value: "US" }] }] },
environments: [10, 20],
environments: ["production", "staging"],
variant: 1,
},
],
}),
}, { environment_id: 20 });
});
const context = new Context(sdk, contextOptions, contextParams, multiEnvResponse);
context.attribute("country", "US");
expect(context.treatment("exp_test_abc")).toEqual(1);
Expand Down Expand Up @@ -2559,6 +2572,70 @@ describe("Context", () => {
expect(context.treatment("exp_test_abc")).toEqual(expectedVariants["exp_test_abc"]);
});

it("should match rule on application attribute when includeSystemAttributes is true", () => {
const appRuleResponse = buildRulesResponse({
assignmentRules: JSON.stringify({
rules: [
{
name: "Website Only",
type: "assign",
conditions: { and: [{ eq: [{ var: "application" }, { value: "website" }] }] },
environments: [],
variant: 1,
},
],
}),
});
const optionsWithSystemAttrs = {
publishDelay: -1,
refreshPeriod: 0,
includeSystemAttributes: true,
};
const context = new Context(sdk, optionsWithSystemAttrs, contextParams, appRuleResponse);
expect(context.treatment("exp_test_abc")).toEqual(1);
});

it("should not match rule on application attribute when includeSystemAttributes is not set", () => {
const appRuleResponse = buildRulesResponse({
assignmentRules: JSON.stringify({
rules: [
{
name: "Website Only",
type: "assign",
conditions: { and: [{ eq: [{ var: "application" }, { value: "website" }] }] },
environments: [],
variant: 1,
},
],
}),
});
const context = new Context(sdk, contextOptions, contextParams, appRuleResponse);
expect(context.treatment("exp_test_abc")).toEqual(expectedVariants["exp_test_abc"]);
});

it("should match rule on environment attribute when includeSystemAttributes is true", () => {
const envRuleResponse = buildRulesResponse({
assignmentRules: JSON.stringify({
rules: [
{
name: "Production Only",
type: "assign",
conditions: { and: [{ eq: [{ var: "environment" }, { value: "production" }] }] },
environments: [],
variant: 1,
},
],
}),
});
const optionsWithSystemAttrs = {
publishDelay: -1,
refreshPeriod: 0,
includeSystemAttributes: true,
};
const context = new Context(sdk, optionsWithSystemAttrs, contextParams, envRuleResponse);
expect(context.treatment("exp_test_abc")).toEqual(1);
});

it("should not invalidate cache when out-of-range rule variant changes to a different out-of-range value", () => {
const oobRulesResponse = buildRulesResponse({
audience: JSON.stringify({
Expand Down Expand Up @@ -4564,6 +4641,10 @@ describe("Context", () => {
});

describe("includeSystemAttributes", () => {
afterEach(() => {
client.getApplication.mockReturnValue({ name: "website", version: 0 });
});

it("should not include system attributes by default", (done) => {
const defaultOptions = {
publishDelay: -1,
Expand Down Expand Up @@ -4658,7 +4739,7 @@ describe("Context", () => {
});

it("should include app_version when application version is set", (done) => {
client.getApplication.mockReturnValueOnce({ name: "website", version: 3 });
client.getApplication.mockReturnValue({ name: "website", version: 3 });

const optionsWithSystemAttrs = {
publishDelay: -1,
Expand Down Expand Up @@ -4707,7 +4788,7 @@ describe("Context", () => {
});

it("should include app_version when application version is a semver string", (done) => {
client.getApplication.mockReturnValueOnce({ name: "website", version: "1.2.3" });
client.getApplication.mockReturnValue({ name: "website", version: "1.2.3" });

const optionsWithSystemAttrs = {
publishDelay: -1,
Expand Down
Loading
Loading