flagsmith-nodejs 6.1.0 → 7.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/conventional-commit.yml +29 -0
- package/.github/workflows/publish.yml +20 -17
- package/.github/workflows/pull_request.yaml +36 -33
- package/.github/workflows/release-please.yml +18 -0
- package/.gitmodules +1 -0
- package/.husky/pre-commit +1 -0
- package/.nvmrc +1 -0
- package/.prettierrc.cjs +9 -1
- package/.release-please-manifest.json +1 -0
- package/CHANGELOG.md +592 -0
- package/CODEOWNERS +1 -0
- package/README.md +0 -2
- package/build/cjs/flagsmith-engine/environments/models.d.ts +2 -1
- package/build/cjs/flagsmith-engine/environments/models.js +3 -1
- package/build/cjs/flagsmith-engine/environments/util.js +1 -1
- package/build/cjs/flagsmith-engine/evaluation/evaluationContext/evaluationContext.types.d.ts +230 -0
- package/build/cjs/flagsmith-engine/evaluation/evaluationContext/evaluationContext.types.js +8 -0
- package/build/cjs/flagsmith-engine/evaluation/evaluationContext/mappers.d.ts +5 -0
- package/build/cjs/flagsmith-engine/evaluation/evaluationContext/mappers.js +156 -0
- package/build/cjs/flagsmith-engine/evaluation/evaluationContext/types.d.ts +216 -0
- package/build/cjs/flagsmith-engine/evaluation/evaluationContext/types.js +8 -0
- package/build/cjs/flagsmith-engine/evaluation/evaluationResult/evaluationResult.types.d.ts +68 -0
- package/build/cjs/flagsmith-engine/evaluation/evaluationResult/evaluationResult.types.js +8 -0
- package/build/cjs/flagsmith-engine/evaluation/models.d.ts +50 -0
- package/build/cjs/flagsmith-engine/evaluation/models.js +26 -0
- package/build/cjs/flagsmith-engine/features/models.js +1 -1
- package/build/cjs/flagsmith-engine/features/types.d.ts +5 -0
- package/build/cjs/flagsmith-engine/features/types.js +9 -0
- package/build/cjs/flagsmith-engine/features/util.d.ts +1 -0
- package/build/cjs/flagsmith-engine/features/util.js +5 -1
- package/build/cjs/flagsmith-engine/index.d.ts +61 -9
- package/build/cjs/flagsmith-engine/index.js +176 -56
- package/build/cjs/flagsmith-engine/segments/constants.d.ts +1 -0
- package/build/cjs/flagsmith-engine/segments/constants.js +2 -1
- package/build/cjs/flagsmith-engine/segments/evaluators.d.ts +41 -7
- package/build/cjs/flagsmith-engine/segments/evaluators.js +136 -24
- package/build/cjs/flagsmith-engine/segments/models.d.ts +9 -4
- package/build/cjs/flagsmith-engine/segments/models.js +115 -13
- package/build/cjs/flagsmith-engine/utils/hashing/index.d.ts +1 -1
- package/build/cjs/flagsmith-engine/utils/hashing/index.js +4 -4
- package/build/cjs/sdk/analytics.js +3 -1
- package/build/cjs/sdk/index.d.ts +1 -3
- package/build/cjs/sdk/index.js +63 -24
- package/build/cjs/sdk/models.d.ts +8 -1
- package/build/cjs/sdk/models.js +29 -1
- package/build/cjs/sdk/utils.d.ts +1 -0
- package/build/cjs/sdk/utils.js +14 -1
- package/build/esm/flagsmith-engine/environments/models.d.ts +2 -1
- package/build/esm/flagsmith-engine/environments/models.js +3 -1
- package/build/esm/flagsmith-engine/environments/util.js +1 -1
- package/build/esm/flagsmith-engine/evaluation/evaluationContext/evaluationContext.types.d.ts +230 -0
- package/build/esm/flagsmith-engine/evaluation/evaluationContext/evaluationContext.types.js +7 -0
- package/build/esm/flagsmith-engine/evaluation/evaluationContext/mappers.d.ts +5 -0
- package/build/esm/flagsmith-engine/evaluation/evaluationContext/mappers.js +152 -0
- package/build/esm/flagsmith-engine/evaluation/evaluationContext/types.d.ts +216 -0
- package/build/esm/flagsmith-engine/evaluation/evaluationContext/types.js +7 -0
- package/build/esm/flagsmith-engine/evaluation/evaluationResult/evaluationResult.types.d.ts +68 -0
- package/build/esm/flagsmith-engine/evaluation/evaluationResult/evaluationResult.types.js +7 -0
- package/build/esm/flagsmith-engine/evaluation/models.d.ts +50 -0
- package/build/esm/flagsmith-engine/evaluation/models.js +9 -0
- package/build/esm/flagsmith-engine/features/models.js +2 -2
- package/build/esm/flagsmith-engine/features/types.d.ts +5 -0
- package/build/esm/flagsmith-engine/features/types.js +6 -0
- package/build/esm/flagsmith-engine/features/util.d.ts +1 -0
- package/build/esm/flagsmith-engine/features/util.js +3 -0
- package/build/esm/flagsmith-engine/index.d.ts +61 -9
- package/build/esm/flagsmith-engine/index.js +161 -43
- package/build/esm/flagsmith-engine/segments/constants.d.ts +1 -0
- package/build/esm/flagsmith-engine/segments/constants.js +1 -0
- package/build/esm/flagsmith-engine/segments/evaluators.d.ts +41 -7
- package/build/esm/flagsmith-engine/segments/evaluators.js +137 -25
- package/build/esm/flagsmith-engine/segments/models.d.ts +9 -4
- package/build/esm/flagsmith-engine/segments/models.js +115 -13
- package/build/esm/flagsmith-engine/utils/hashing/index.d.ts +1 -1
- package/build/esm/flagsmith-engine/utils/hashing/index.js +2 -2
- package/build/esm/sdk/analytics.js +3 -1
- package/build/esm/sdk/index.d.ts +1 -3
- package/build/esm/sdk/index.js +63 -24
- package/build/esm/sdk/models.d.ts +8 -1
- package/build/esm/sdk/models.js +29 -1
- package/build/esm/sdk/utils.d.ts +1 -0
- package/build/esm/sdk/utils.js +12 -0
- package/flagsmith-engine/environments/models.ts +3 -1
- package/flagsmith-engine/environments/util.ts +2 -1
- package/flagsmith-engine/evaluation/evaluationContext/evaluationContext.types.ts +247 -0
- package/flagsmith-engine/evaluation/evaluationContext/mappers.ts +204 -0
- package/flagsmith-engine/evaluation/evaluationContext/types.ts +233 -0
- package/flagsmith-engine/evaluation/evaluationResult/evaluationResult.types.ts +71 -0
- package/flagsmith-engine/evaluation/models.ts +96 -0
- package/flagsmith-engine/features/models.ts +3 -2
- package/flagsmith-engine/features/types.ts +5 -0
- package/flagsmith-engine/features/util.ts +4 -0
- package/flagsmith-engine/index.ts +229 -72
- package/flagsmith-engine/segments/constants.ts +1 -0
- package/flagsmith-engine/segments/evaluators.ts +178 -62
- package/flagsmith-engine/segments/models.ts +171 -23
- package/flagsmith-engine/utils/hashing/index.ts +2 -2
- package/package.json +13 -2
- package/release-please-config.json +62 -0
- package/sdk/analytics.ts +3 -1
- package/sdk/index.ts +89 -30
- package/sdk/models.ts +44 -2
- package/sdk/utils.ts +13 -0
- package/tests/engine/e2e/engine.test.ts +43 -38
- package/tests/engine/unit/engine.test.ts +306 -60
- package/tests/engine/unit/mappers.test.ts +353 -0
- package/tests/engine/unit/segments/segment_evaluators.test.ts +391 -49
- package/tests/engine/unit/segments/segments_model.test.ts +85 -0
- package/tests/engine/unit/utils/utils.test.ts +7 -7
- package/tests/engine/unit/utils.ts +1 -1
- package/tests/sdk/analytics.test.ts +6 -1
- package/tests/sdk/data/environment.json +1 -0
- package/tests/sdk/flagsmith-environment-flags.test.ts +28 -0
- package/tests/sdk/flagsmith-identity-flags.test.ts +11 -2
- package/tests/sdk/flagsmith.test.ts +190 -3
- package/tests/sdk/offline-handlers.test.ts +3 -1
- package/vitest.config.esm.ts +34 -0
|
@@ -0,0 +1,353 @@
|
|
|
1
|
+
import { test, expect, describe } from 'vitest';
|
|
2
|
+
import { getEvaluationContext } from '../../../flagsmith-engine/evaluation/evaluationContext/mappers.js';
|
|
3
|
+
import { buildEnvironmentModel } from '../../../flagsmith-engine/environments/util.js';
|
|
4
|
+
import { EnvironmentModel } from '../../../flagsmith-engine/environments/models.js';
|
|
5
|
+
import { IdentityModel } from '../../../flagsmith-engine/identities/models.js';
|
|
6
|
+
import { TraitModel } from '../../../flagsmith-engine/identities/traits/models.js';
|
|
7
|
+
import { FeatureModel, FeatureStateModel } from '../../../flagsmith-engine/features/models.js';
|
|
8
|
+
import {
|
|
9
|
+
MultivariateFeatureOptionModel,
|
|
10
|
+
MultivariateFeatureStateValueModel
|
|
11
|
+
} from '../../../flagsmith-engine/features/models.js';
|
|
12
|
+
import { CONSTANTS } from '../../../flagsmith-engine/features/constants.js';
|
|
13
|
+
import { readFileSync } from 'fs';
|
|
14
|
+
import { IDENTITY_OVERRIDE_SEGMENT_NAME } from '../../../flagsmith-engine/segments/constants.js';
|
|
15
|
+
import { SegmentSource } from '../../../flagsmith-engine/evaluation/models.js';
|
|
16
|
+
|
|
17
|
+
const DATA_DIR = __dirname + '/../../sdk/data/';
|
|
18
|
+
|
|
19
|
+
describe('getEvaluationContext', () => {
|
|
20
|
+
const environmentJSON = JSON.parse(readFileSync(DATA_DIR + 'environment.json', 'utf-8'));
|
|
21
|
+
const testEnvironment = buildEnvironmentModel(environmentJSON);
|
|
22
|
+
|
|
23
|
+
test('produces evaluation context from environment document', () => {
|
|
24
|
+
// When
|
|
25
|
+
const context = getEvaluationContext(testEnvironment);
|
|
26
|
+
|
|
27
|
+
// Then - verify environment
|
|
28
|
+
expect(context).toBeDefined();
|
|
29
|
+
expect(context.environment?.key).toBe('B62qaMZNwfiqT76p38ggrQ');
|
|
30
|
+
expect(context.environment?.name).toBe('Test environment');
|
|
31
|
+
expect(context.identity).toBeUndefined();
|
|
32
|
+
|
|
33
|
+
// Verify segments
|
|
34
|
+
expect(context.segments).toBeDefined();
|
|
35
|
+
expect(context.segments).toHaveProperty('1');
|
|
36
|
+
|
|
37
|
+
const segment = context.segments!['1'];
|
|
38
|
+
expect(segment.key).toBe('1');
|
|
39
|
+
expect(segment.name).toBe('regular_segment');
|
|
40
|
+
expect(segment.rules.length).toBe(1);
|
|
41
|
+
expect(segment.overrides).toBeDefined();
|
|
42
|
+
expect(Array.isArray(segment.overrides)).toBe(true);
|
|
43
|
+
expect(segment.metadata?.source).toBe(SegmentSource.API);
|
|
44
|
+
expect(segment.metadata?.id).toBe(1);
|
|
45
|
+
|
|
46
|
+
// Verify segment rules
|
|
47
|
+
expect(segment.rules[0].type).toBe('ALL');
|
|
48
|
+
expect(segment.rules[0].conditions).toEqual([]);
|
|
49
|
+
expect(segment.rules[0].rules?.length).toBe(1);
|
|
50
|
+
|
|
51
|
+
const nestedRule = segment.rules[0].rules?.[0]!;
|
|
52
|
+
expect(nestedRule.type).toBe('ANY');
|
|
53
|
+
expect(nestedRule.conditions?.length).toBe(1);
|
|
54
|
+
expect(nestedRule.rules?.length).toEqual(0);
|
|
55
|
+
|
|
56
|
+
const condition = nestedRule.conditions?.[0]!;
|
|
57
|
+
expect(condition.property).toBe('age');
|
|
58
|
+
expect(condition.operator).toBe('LESS_THAN');
|
|
59
|
+
expect(condition.value).toBe('40');
|
|
60
|
+
|
|
61
|
+
// Verify identity override segment
|
|
62
|
+
const identityOverrideSegment = Object.values(context.segments!).find(
|
|
63
|
+
s => s.name === IDENTITY_OVERRIDE_SEGMENT_NAME
|
|
64
|
+
);
|
|
65
|
+
expect(identityOverrideSegment).toBeDefined();
|
|
66
|
+
expect(identityOverrideSegment!.name).toBe(IDENTITY_OVERRIDE_SEGMENT_NAME);
|
|
67
|
+
expect(identityOverrideSegment!.rules.length).toBe(1);
|
|
68
|
+
expect(identityOverrideSegment!.overrides?.length).toBe(1);
|
|
69
|
+
|
|
70
|
+
const overrideRule = identityOverrideSegment!.rules?.[0]!;
|
|
71
|
+
expect(overrideRule.type).toBe('ALL');
|
|
72
|
+
expect(overrideRule.conditions?.length).toBe(1);
|
|
73
|
+
|
|
74
|
+
const overrideCondition = overrideRule.conditions?.[0]!;
|
|
75
|
+
expect(overrideCondition.property).toBe('$.identity.identifier');
|
|
76
|
+
expect(overrideCondition.operator).toBe('IN');
|
|
77
|
+
expect(overrideCondition.value).toContain('overridden-id');
|
|
78
|
+
|
|
79
|
+
const override = identityOverrideSegment!.overrides?.[0]!;
|
|
80
|
+
expect(override.name).toBe('some_feature');
|
|
81
|
+
expect(override.enabled).toBe(false);
|
|
82
|
+
expect(override.value).toBe('some-overridden-value');
|
|
83
|
+
expect(override.priority).toBe(-Infinity);
|
|
84
|
+
expect(override.metadata?.id).toBe(1);
|
|
85
|
+
|
|
86
|
+
// Verify features
|
|
87
|
+
expect(context.features).toBeDefined();
|
|
88
|
+
expect(context.features).toHaveProperty('some_feature');
|
|
89
|
+
|
|
90
|
+
const someFeature = context.features!['some_feature'];
|
|
91
|
+
expect(someFeature.name).toBe('some_feature');
|
|
92
|
+
expect(someFeature.enabled).toBe(true);
|
|
93
|
+
expect(someFeature.value).toBe('some-value');
|
|
94
|
+
expect(someFeature.priority).toBeUndefined();
|
|
95
|
+
expect(someFeature.metadata?.id).toBe(1);
|
|
96
|
+
|
|
97
|
+
// Verify multivariate feature
|
|
98
|
+
expect(context.features).toHaveProperty('mv_feature');
|
|
99
|
+
const mvFeature = context.features!['mv_feature'];
|
|
100
|
+
expect(mvFeature.name).toBe('mv_feature');
|
|
101
|
+
expect(mvFeature.enabled).toBe(false);
|
|
102
|
+
expect(mvFeature.value).toBe('foo');
|
|
103
|
+
expect(mvFeature.priority).toBeUndefined();
|
|
104
|
+
expect(mvFeature.variants?.length).toBe(1);
|
|
105
|
+
|
|
106
|
+
const variant = mvFeature.variants![0];
|
|
107
|
+
expect(variant.value).toBe('bar');
|
|
108
|
+
expect(variant.weight).toBe(100);
|
|
109
|
+
expect(variant.priority).toBe(1);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
test('maps multivariate features with multiple variants correctly', () => {
|
|
113
|
+
// Given
|
|
114
|
+
const mvOption1 = new MultivariateFeatureOptionModel('variant_a', 100);
|
|
115
|
+
const mvOption2 = new MultivariateFeatureOptionModel('variant_b', 200);
|
|
116
|
+
const mvOption3 = new MultivariateFeatureOptionModel('variant_c', 150);
|
|
117
|
+
|
|
118
|
+
const mvValue1 = new MultivariateFeatureStateValueModel(
|
|
119
|
+
mvOption1,
|
|
120
|
+
30,
|
|
121
|
+
100,
|
|
122
|
+
'00000000-0000-0000-0000-000000000001'
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
const mvValue2 = new MultivariateFeatureStateValueModel(
|
|
126
|
+
mvOption2,
|
|
127
|
+
50,
|
|
128
|
+
200,
|
|
129
|
+
'00000000-0000-0000-0000-000000000002'
|
|
130
|
+
);
|
|
131
|
+
|
|
132
|
+
const mvValue3 = new MultivariateFeatureStateValueModel(
|
|
133
|
+
mvOption3,
|
|
134
|
+
20,
|
|
135
|
+
150,
|
|
136
|
+
'00000000-0000-0000-0000-000000000003'
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
const feature = new FeatureModel(999, 'multi_variant_feature', CONSTANTS.MULTIVARIATE);
|
|
140
|
+
const featureState = new FeatureStateModel(feature, true, 999);
|
|
141
|
+
featureState.setValue('control');
|
|
142
|
+
featureState.multivariateFeatureStateValues = [mvValue1, mvValue2, mvValue3];
|
|
143
|
+
|
|
144
|
+
const envWithMv = new EnvironmentModel(1, 'test_key', testEnvironment.project, 'Test Env');
|
|
145
|
+
envWithMv.featureStates = [featureState];
|
|
146
|
+
|
|
147
|
+
// When
|
|
148
|
+
const context = getEvaluationContext(envWithMv);
|
|
149
|
+
|
|
150
|
+
// Then
|
|
151
|
+
const featureContext = context.features!['multi_variant_feature'];
|
|
152
|
+
expect(featureContext.variants?.length).toBe(3);
|
|
153
|
+
|
|
154
|
+
expect(featureContext.variants![0].value).toBe('variant_a');
|
|
155
|
+
expect(featureContext.variants![0].weight).toBe(30);
|
|
156
|
+
expect(featureContext.variants![0].priority).toBe(100);
|
|
157
|
+
|
|
158
|
+
expect(featureContext.variants![1].value).toBe('variant_b');
|
|
159
|
+
expect(featureContext.variants![1].weight).toBe(50);
|
|
160
|
+
expect(featureContext.variants![1].priority).toBe(200);
|
|
161
|
+
|
|
162
|
+
expect(featureContext.variants![2].value).toBe('variant_c');
|
|
163
|
+
expect(featureContext.variants![2].weight).toBe(20);
|
|
164
|
+
expect(featureContext.variants![2].priority).toBe(150);
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
test('handles multivariate features without IDs using UUID', () => {
|
|
168
|
+
// Given
|
|
169
|
+
const mvOption1 = new MultivariateFeatureOptionModel('option_x', undefined);
|
|
170
|
+
const mvOption2 = new MultivariateFeatureOptionModel('option_y', undefined);
|
|
171
|
+
|
|
172
|
+
const mvValue1 = new MultivariateFeatureStateValueModel(
|
|
173
|
+
mvOption1,
|
|
174
|
+
60,
|
|
175
|
+
undefined as any,
|
|
176
|
+
'aaaaaaaa-bbbb-cccc-dddd-000000000001'
|
|
177
|
+
);
|
|
178
|
+
|
|
179
|
+
const mvValue2 = new MultivariateFeatureStateValueModel(
|
|
180
|
+
mvOption2,
|
|
181
|
+
40,
|
|
182
|
+
undefined as any,
|
|
183
|
+
'aaaaaaaa-bbbb-cccc-dddd-000000000002'
|
|
184
|
+
);
|
|
185
|
+
|
|
186
|
+
const feature = new FeatureModel(888, 'uuid_variant_feature', CONSTANTS.MULTIVARIATE);
|
|
187
|
+
const featureState = new FeatureStateModel(feature, true, 888);
|
|
188
|
+
featureState.setValue('default');
|
|
189
|
+
featureState.multivariateFeatureStateValues = [mvValue1, mvValue2];
|
|
190
|
+
|
|
191
|
+
const envWithUuid = new EnvironmentModel(
|
|
192
|
+
1,
|
|
193
|
+
'test_key',
|
|
194
|
+
testEnvironment.project,
|
|
195
|
+
'Test Env'
|
|
196
|
+
);
|
|
197
|
+
envWithUuid.featureStates = [featureState];
|
|
198
|
+
|
|
199
|
+
// When
|
|
200
|
+
const context = getEvaluationContext(envWithUuid);
|
|
201
|
+
|
|
202
|
+
// Then
|
|
203
|
+
const featureContext = context.features!['uuid_variant_feature'];
|
|
204
|
+
expect(featureContext.variants?.length).toBe(2);
|
|
205
|
+
|
|
206
|
+
// When using UUID-based priorities, they become bigints
|
|
207
|
+
expect(
|
|
208
|
+
typeof featureContext.variants![0].priority === 'number' ||
|
|
209
|
+
typeof featureContext.variants![0].priority === 'bigint'
|
|
210
|
+
).toBe(true);
|
|
211
|
+
expect(
|
|
212
|
+
typeof featureContext.variants![1].priority === 'number' ||
|
|
213
|
+
typeof featureContext.variants![1].priority === 'bigint'
|
|
214
|
+
).toBe(true);
|
|
215
|
+
expect(featureContext.variants![0].priority).not.toBe(featureContext.variants![1].priority);
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
test('handles environment with no features', () => {
|
|
219
|
+
// Given - create a copy with no features
|
|
220
|
+
const emptyEnvJSON = { ...environmentJSON, feature_states: [] };
|
|
221
|
+
const emptyEnv = buildEnvironmentModel(emptyEnvJSON);
|
|
222
|
+
|
|
223
|
+
// When
|
|
224
|
+
const context = getEvaluationContext(emptyEnv);
|
|
225
|
+
|
|
226
|
+
// Then
|
|
227
|
+
expect(context.features).toEqual({});
|
|
228
|
+
expect(context.environment?.key).toBe('B62qaMZNwfiqT76p38ggrQ');
|
|
229
|
+
expect(context.environment?.name).toBe('Test environment');
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
test('produces evaluation context with identity', () => {
|
|
233
|
+
// Given
|
|
234
|
+
const identity = new IdentityModel(
|
|
235
|
+
'2024-01-01T00:00:00Z',
|
|
236
|
+
[new TraitModel('email', 'test@example.com'), new TraitModel('age', 25)],
|
|
237
|
+
[],
|
|
238
|
+
'B62qaMZNwfiqT76p38ggrQ',
|
|
239
|
+
'test_user'
|
|
240
|
+
);
|
|
241
|
+
|
|
242
|
+
// When
|
|
243
|
+
const context = getEvaluationContext(testEnvironment, identity);
|
|
244
|
+
|
|
245
|
+
// Then
|
|
246
|
+
expect(context.identity).toBeDefined();
|
|
247
|
+
expect(context.identity?.identifier).toBe('test_user');
|
|
248
|
+
expect(context.identity?.traits).toEqual({
|
|
249
|
+
email: 'test@example.com',
|
|
250
|
+
age: 25
|
|
251
|
+
});
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
test('produces evaluation context with override traits', () => {
|
|
255
|
+
// Given
|
|
256
|
+
const identity = new IdentityModel(
|
|
257
|
+
'2024-01-01T00:00:00Z',
|
|
258
|
+
[new TraitModel('email', 'original@example.com')],
|
|
259
|
+
[],
|
|
260
|
+
'B62qaMZNwfiqT76p38ggrQ',
|
|
261
|
+
'test_user',
|
|
262
|
+
undefined,
|
|
263
|
+
456
|
|
264
|
+
);
|
|
265
|
+
|
|
266
|
+
const overrideTraits = [
|
|
267
|
+
new TraitModel('email', 'override@example.com'),
|
|
268
|
+
new TraitModel('premium', true)
|
|
269
|
+
];
|
|
270
|
+
|
|
271
|
+
// When
|
|
272
|
+
const context = getEvaluationContext(testEnvironment, identity, overrideTraits);
|
|
273
|
+
|
|
274
|
+
// Then
|
|
275
|
+
expect(context.identity?.traits).toEqual({
|
|
276
|
+
email: 'override@example.com',
|
|
277
|
+
premium: true
|
|
278
|
+
});
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
test('produces evaluation context without identity when isEnvironmentEvaluation is true', () => {
|
|
282
|
+
// Given
|
|
283
|
+
const identity = new IdentityModel(
|
|
284
|
+
'2024-01-01T00:00:00Z',
|
|
285
|
+
[new TraitModel('test', 'value')],
|
|
286
|
+
[],
|
|
287
|
+
'B62qaMZNwfiqT76p38ggrQ',
|
|
288
|
+
'test_user',
|
|
289
|
+
undefined,
|
|
290
|
+
789
|
|
291
|
+
);
|
|
292
|
+
|
|
293
|
+
// When
|
|
294
|
+
const context = getEvaluationContext(testEnvironment, identity, undefined, true);
|
|
295
|
+
|
|
296
|
+
// Then
|
|
297
|
+
expect(context.identity).toBeUndefined();
|
|
298
|
+
expect(context.environment).toBeDefined();
|
|
299
|
+
expect(context.features).toBeDefined();
|
|
300
|
+
expect(context.segments).toBeDefined();
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
test('handles identity without django_id', () => {
|
|
304
|
+
// Given
|
|
305
|
+
const identity = new IdentityModel(
|
|
306
|
+
'2024-01-01T00:00:00Z',
|
|
307
|
+
[new TraitModel('name', 'John')],
|
|
308
|
+
[],
|
|
309
|
+
'B62qaMZNwfiqT76p38ggrQ',
|
|
310
|
+
'john_doe',
|
|
311
|
+
undefined,
|
|
312
|
+
undefined
|
|
313
|
+
);
|
|
314
|
+
|
|
315
|
+
// When
|
|
316
|
+
const context = getEvaluationContext(testEnvironment, identity);
|
|
317
|
+
|
|
318
|
+
// Then
|
|
319
|
+
expect(context.identity?.identifier).toBe('john_doe');
|
|
320
|
+
expect(context.identity?.key).toBeUndefined();
|
|
321
|
+
expect(context.identity?.traits).toEqual({ name: 'John' });
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
test('maps segment override priorities correctly', () => {
|
|
325
|
+
// When - using fixture which has segment with priority
|
|
326
|
+
const context = getEvaluationContext(testEnvironment);
|
|
327
|
+
|
|
328
|
+
// Then - verify regular_segment has a feature override
|
|
329
|
+
const segment = context.segments!['1'];
|
|
330
|
+
expect(segment.overrides?.length).toBeGreaterThan(0);
|
|
331
|
+
|
|
332
|
+
// The segment override from the fixture has no explicit priority, should be undefined
|
|
333
|
+
const segmentOverride = segment.overrides?.[0]!;
|
|
334
|
+
expect(segmentOverride.name).toBe('some_feature');
|
|
335
|
+
expect(segmentOverride.priority).toBeUndefined();
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
test('handles multiple identity overrides with same features', () => {
|
|
339
|
+
// Given - the fixture already has identity override with 'overridden-id'
|
|
340
|
+
// Verify it's mapped correctly
|
|
341
|
+
const context = getEvaluationContext(testEnvironment);
|
|
342
|
+
|
|
343
|
+
// Then
|
|
344
|
+
const overrideSegments = Object.values(context.segments!).filter(
|
|
345
|
+
s => s.name === IDENTITY_OVERRIDE_SEGMENT_NAME
|
|
346
|
+
);
|
|
347
|
+
|
|
348
|
+
// The fixture has one identity override
|
|
349
|
+
expect(overrideSegments.length).toBe(1);
|
|
350
|
+
expect(overrideSegments[0].rules?.[0].conditions?.[0].value).toContain('overridden-id');
|
|
351
|
+
expect(overrideSegments[0].overrides?.length).toBe(1);
|
|
352
|
+
});
|
|
353
|
+
});
|