flagsmith-nodejs 8.0.0 → 8.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/.release-please-manifest.json +1 -1
- package/CHANGELOG.md +19 -0
- package/build/cjs/flagsmith-engine/evaluation/evaluationContext/mappers.js +12 -8
- package/build/cjs/sdk/index.js +2 -2
- package/build/esm/flagsmith-engine/evaluation/evaluationContext/mappers.js +12 -8
- package/build/esm/sdk/index.js +3 -3
- package/flagsmith-engine/evaluation/evaluationContext/mappers.ts +14 -10
- package/package.json +1 -1
- package/sdk/index.ts +9 -3
- package/tests/engine/unit/engine.test.ts +92 -2
- package/tests/sdk/flagsmith-identity-flags.test.ts +70 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{".":"8.0.
|
|
1
|
+
{".":"8.0.2"}
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,24 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [8.0.2](https://github.com/Flagsmith/flagsmith-nodejs-client/compare/v8.0.1...v8.0.2) (2026-03-19)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
* Missing variants in feature engine context ([#257](https://github.com/Flagsmith/flagsmith-nodejs-client/issues/257)) ([7e191b8](https://github.com/Flagsmith/flagsmith-nodejs-client/commit/7e191b8fff2634e58ff4ade92d4729b0b51ed6dc))
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Other
|
|
12
|
+
|
|
13
|
+
* **deps:** bump minimatch and npm ([#255](https://github.com/Flagsmith/flagsmith-nodejs-client/issues/255)) ([c18c3b3](https://github.com/Flagsmith/flagsmith-nodejs-client/commit/c18c3b357243df72708fdc3e0184589b25efa8a1))
|
|
14
|
+
|
|
15
|
+
## [8.0.1](https://github.com/Flagsmith/flagsmith-nodejs-client/compare/v8.0.0...v8.0.1) (2026-03-16)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
### Bug Fixes
|
|
19
|
+
|
|
20
|
+
* unwrap TraitConfig values in local evaluation before segment matching ([#252](https://github.com/Flagsmith/flagsmith-nodejs-client/issues/252)) ([4e37994](https://github.com/Flagsmith/flagsmith-nodejs-client/commit/4e37994829bb741665a26aa38d543bebe51231d8))
|
|
21
|
+
|
|
3
22
|
## [8.0.0](https://github.com/Flagsmith/flagsmith-nodejs-client/compare/v7.0.3...v8.0.0) (2026-02-25)
|
|
4
23
|
|
|
5
24
|
|
|
@@ -27,19 +27,12 @@ function mapEnvironmentModelToEvaluationContext(environment) {
|
|
|
27
27
|
};
|
|
28
28
|
const features = {};
|
|
29
29
|
for (const fs of environment.featureStates) {
|
|
30
|
-
const variants = fs.multivariateFeatureStateValues?.length > 0
|
|
31
|
-
? fs.multivariateFeatureStateValues.map(mv => ({
|
|
32
|
-
value: mv.multivariateFeatureOption.value,
|
|
33
|
-
weight: mv.percentageAllocation,
|
|
34
|
-
priority: mv.id ?? (0, util_js_1.uuidToBigInt)(mv.mvFsValueUuid)
|
|
35
|
-
}))
|
|
36
|
-
: undefined;
|
|
37
30
|
features[fs.feature.name] = {
|
|
38
31
|
key: fs.djangoID?.toString() || fs.featurestateUUID,
|
|
39
32
|
name: fs.feature.name,
|
|
40
33
|
enabled: fs.enabled,
|
|
41
34
|
value: fs.getValue(),
|
|
42
|
-
variants,
|
|
35
|
+
variants: mapFeatureStateVariants(fs),
|
|
43
36
|
priority: fs.featureSegment?.priority,
|
|
44
37
|
metadata: {
|
|
45
38
|
id: fs.feature.id
|
|
@@ -58,6 +51,7 @@ function mapEnvironmentModelToEvaluationContext(environment) {
|
|
|
58
51
|
name: fs.feature.name,
|
|
59
52
|
enabled: fs.enabled,
|
|
60
53
|
value: fs.getValue(),
|
|
54
|
+
variants: mapFeatureStateVariants(fs),
|
|
61
55
|
priority: fs.featureSegment?.priority,
|
|
62
56
|
metadata: {
|
|
63
57
|
id: fs.feature.id
|
|
@@ -95,6 +89,15 @@ function mapIdentityModelToIdentityContext(identity, overrideTraits) {
|
|
|
95
89
|
};
|
|
96
90
|
return identityContext;
|
|
97
91
|
}
|
|
92
|
+
function mapFeatureStateVariants(fs) {
|
|
93
|
+
return fs.multivariateFeatureStateValues?.length > 0
|
|
94
|
+
? fs.multivariateFeatureStateValues.map(mv => ({
|
|
95
|
+
value: mv.multivariateFeatureOption.value,
|
|
96
|
+
weight: mv.percentageAllocation,
|
|
97
|
+
priority: mv.id ?? (0, util_js_1.uuidToBigInt)(mv.mvFsValueUuid)
|
|
98
|
+
}))
|
|
99
|
+
: undefined;
|
|
100
|
+
}
|
|
98
101
|
function mapSegmentRuleModelToRule(rule) {
|
|
99
102
|
return {
|
|
100
103
|
type: rule.type,
|
|
@@ -118,6 +121,7 @@ function mapIdentityOverridesToSegments(identityOverrides) {
|
|
|
118
121
|
name: fs.feature.name,
|
|
119
122
|
enabled: fs.enabled,
|
|
120
123
|
value: fs.getValue(),
|
|
124
|
+
variants: mapFeatureStateVariants(fs),
|
|
121
125
|
priority: -Infinity,
|
|
122
126
|
metadata: {
|
|
123
127
|
id: fs.feature.id
|
package/build/cjs/sdk/index.js
CHANGED
|
@@ -226,7 +226,7 @@ class Flagsmith {
|
|
|
226
226
|
const environment = await this.getEnvironment();
|
|
227
227
|
const identityModel = this.getIdentityModel(environment, identifier, Object.keys(traits || {}).map(key => ({
|
|
228
228
|
key,
|
|
229
|
-
value: traits?.[key]
|
|
229
|
+
value: (0, utils_js_1.isTraitConfig)(traits?.[key]) ? traits[key].value : traits?.[key]
|
|
230
230
|
})));
|
|
231
231
|
const context = (0, mappers_js_1.getEvaluationContext)(environment, identityModel);
|
|
232
232
|
if (!context) {
|
|
@@ -379,7 +379,7 @@ class Flagsmith {
|
|
|
379
379
|
const environment = await this.getEnvironment();
|
|
380
380
|
const identityModel = this.getIdentityModel(environment, identifier, Object.keys(traits).map(key => ({
|
|
381
381
|
key,
|
|
382
|
-
value: traits[key]
|
|
382
|
+
value: (0, utils_js_1.isTraitConfig)(traits[key]) ? traits[key].value : traits[key]
|
|
383
383
|
})));
|
|
384
384
|
const context = (0, mappers_js_1.getEvaluationContext)(environment, identityModel);
|
|
385
385
|
if (!context) {
|
|
@@ -23,19 +23,12 @@ function mapEnvironmentModelToEvaluationContext(environment) {
|
|
|
23
23
|
};
|
|
24
24
|
const features = {};
|
|
25
25
|
for (const fs of environment.featureStates) {
|
|
26
|
-
const variants = fs.multivariateFeatureStateValues?.length > 0
|
|
27
|
-
? fs.multivariateFeatureStateValues.map(mv => ({
|
|
28
|
-
value: mv.multivariateFeatureOption.value,
|
|
29
|
-
weight: mv.percentageAllocation,
|
|
30
|
-
priority: mv.id ?? uuidToBigInt(mv.mvFsValueUuid)
|
|
31
|
-
}))
|
|
32
|
-
: undefined;
|
|
33
26
|
features[fs.feature.name] = {
|
|
34
27
|
key: fs.djangoID?.toString() || fs.featurestateUUID,
|
|
35
28
|
name: fs.feature.name,
|
|
36
29
|
enabled: fs.enabled,
|
|
37
30
|
value: fs.getValue(),
|
|
38
|
-
variants,
|
|
31
|
+
variants: mapFeatureStateVariants(fs),
|
|
39
32
|
priority: fs.featureSegment?.priority,
|
|
40
33
|
metadata: {
|
|
41
34
|
id: fs.feature.id
|
|
@@ -54,6 +47,7 @@ function mapEnvironmentModelToEvaluationContext(environment) {
|
|
|
54
47
|
name: fs.feature.name,
|
|
55
48
|
enabled: fs.enabled,
|
|
56
49
|
value: fs.getValue(),
|
|
50
|
+
variants: mapFeatureStateVariants(fs),
|
|
57
51
|
priority: fs.featureSegment?.priority,
|
|
58
52
|
metadata: {
|
|
59
53
|
id: fs.feature.id
|
|
@@ -91,6 +85,15 @@ function mapIdentityModelToIdentityContext(identity, overrideTraits) {
|
|
|
91
85
|
};
|
|
92
86
|
return identityContext;
|
|
93
87
|
}
|
|
88
|
+
function mapFeatureStateVariants(fs) {
|
|
89
|
+
return fs.multivariateFeatureStateValues?.length > 0
|
|
90
|
+
? fs.multivariateFeatureStateValues.map(mv => ({
|
|
91
|
+
value: mv.multivariateFeatureOption.value,
|
|
92
|
+
weight: mv.percentageAllocation,
|
|
93
|
+
priority: mv.id ?? uuidToBigInt(mv.mvFsValueUuid)
|
|
94
|
+
}))
|
|
95
|
+
: undefined;
|
|
96
|
+
}
|
|
94
97
|
function mapSegmentRuleModelToRule(rule) {
|
|
95
98
|
return {
|
|
96
99
|
type: rule.type,
|
|
@@ -114,6 +117,7 @@ function mapIdentityOverridesToSegments(identityOverrides) {
|
|
|
114
117
|
name: fs.feature.name,
|
|
115
118
|
enabled: fs.enabled,
|
|
116
119
|
value: fs.getValue(),
|
|
120
|
+
variants: mapFeatureStateVariants(fs),
|
|
117
121
|
priority: -Infinity,
|
|
118
122
|
metadata: {
|
|
119
123
|
id: fs.feature.id
|
package/build/esm/sdk/index.js
CHANGED
|
@@ -3,7 +3,7 @@ import { ANALYTICS_ENDPOINT, AnalyticsProcessor } from './analytics.js';
|
|
|
3
3
|
import { FlagsmithAPIError, FlagsmithClientError } from './errors.js';
|
|
4
4
|
import { Flags } from './models.js';
|
|
5
5
|
import { EnvironmentDataPollingManager } from './polling_manager.js';
|
|
6
|
-
import { Deferred, generateIdentitiesData, getUserAgent, retryFetch } from './utils.js';
|
|
6
|
+
import { Deferred, generateIdentitiesData, getUserAgent, isTraitConfig, retryFetch } from './utils.js';
|
|
7
7
|
import { SegmentModel, IdentityModel, TraitModel, getEvaluationResult } from '../flagsmith-engine/index.js';
|
|
8
8
|
import { pino } from 'pino';
|
|
9
9
|
import { getEvaluationContext } from '../flagsmith-engine/evaluation/evaluationContext/mappers.js';
|
|
@@ -216,7 +216,7 @@ export class Flagsmith {
|
|
|
216
216
|
const environment = await this.getEnvironment();
|
|
217
217
|
const identityModel = this.getIdentityModel(environment, identifier, Object.keys(traits || {}).map(key => ({
|
|
218
218
|
key,
|
|
219
|
-
value: traits?.[key]
|
|
219
|
+
value: isTraitConfig(traits?.[key]) ? traits[key].value : traits?.[key]
|
|
220
220
|
})));
|
|
221
221
|
const context = getEvaluationContext(environment, identityModel);
|
|
222
222
|
if (!context) {
|
|
@@ -369,7 +369,7 @@ export class Flagsmith {
|
|
|
369
369
|
const environment = await this.getEnvironment();
|
|
370
370
|
const identityModel = this.getIdentityModel(environment, identifier, Object.keys(traits).map(key => ({
|
|
371
371
|
key,
|
|
372
|
-
value: traits[key]
|
|
372
|
+
value: isTraitConfig(traits[key]) ? traits[key].value : traits[key]
|
|
373
373
|
})));
|
|
374
374
|
const context = getEvaluationContext(environment, identityModel);
|
|
375
375
|
if (!context) {
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
import { EnvironmentModel } from '../../environments/models.js';
|
|
13
13
|
import { IdentityModel } from '../../identities/models.js';
|
|
14
14
|
import { TraitModel } from '../../identities/traits/models.js';
|
|
15
|
+
import { FeatureStateModel } from '../../features/models.js';
|
|
15
16
|
import { IDENTITY_OVERRIDE_SEGMENT_NAME } from '../../segments/constants.js';
|
|
16
17
|
import { createHash } from 'node:crypto';
|
|
17
18
|
import { uuidToBigInt } from '../../features/util.js';
|
|
@@ -48,21 +49,12 @@ function mapEnvironmentModelToEvaluationContext(
|
|
|
48
49
|
|
|
49
50
|
const features: FeaturesWithMetadata<SDKFeatureMetadata> = {};
|
|
50
51
|
for (const fs of environment.featureStates) {
|
|
51
|
-
const variants =
|
|
52
|
-
fs.multivariateFeatureStateValues?.length > 0
|
|
53
|
-
? fs.multivariateFeatureStateValues.map(mv => ({
|
|
54
|
-
value: mv.multivariateFeatureOption.value,
|
|
55
|
-
weight: mv.percentageAllocation,
|
|
56
|
-
priority: mv.id ?? uuidToBigInt(mv.mvFsValueUuid)
|
|
57
|
-
}))
|
|
58
|
-
: undefined;
|
|
59
|
-
|
|
60
52
|
features[fs.feature.name] = {
|
|
61
53
|
key: fs.djangoID?.toString() || fs.featurestateUUID,
|
|
62
54
|
name: fs.feature.name,
|
|
63
55
|
enabled: fs.enabled,
|
|
64
56
|
value: fs.getValue(),
|
|
65
|
-
variants,
|
|
57
|
+
variants: mapFeatureStateVariants(fs),
|
|
66
58
|
priority: fs.featureSegment?.priority,
|
|
67
59
|
metadata: {
|
|
68
60
|
id: fs.feature.id
|
|
@@ -83,6 +75,7 @@ function mapEnvironmentModelToEvaluationContext(
|
|
|
83
75
|
name: fs.feature.name,
|
|
84
76
|
enabled: fs.enabled,
|
|
85
77
|
value: fs.getValue(),
|
|
78
|
+
variants: mapFeatureStateVariants(fs),
|
|
86
79
|
priority: fs.featureSegment?.priority,
|
|
87
80
|
metadata: {
|
|
88
81
|
id: fs.feature.id
|
|
@@ -130,6 +123,16 @@ function mapIdentityModelToIdentityContext(
|
|
|
130
123
|
return identityContext;
|
|
131
124
|
}
|
|
132
125
|
|
|
126
|
+
function mapFeatureStateVariants(fs: FeatureStateModel) {
|
|
127
|
+
return fs.multivariateFeatureStateValues?.length > 0
|
|
128
|
+
? fs.multivariateFeatureStateValues.map(mv => ({
|
|
129
|
+
value: mv.multivariateFeatureOption.value,
|
|
130
|
+
weight: mv.percentageAllocation,
|
|
131
|
+
priority: mv.id ?? uuidToBigInt(mv.mvFsValueUuid)
|
|
132
|
+
}))
|
|
133
|
+
: undefined;
|
|
134
|
+
}
|
|
135
|
+
|
|
133
136
|
function mapSegmentRuleModelToRule(rule: any): any {
|
|
134
137
|
return {
|
|
135
138
|
type: rule.type,
|
|
@@ -160,6 +163,7 @@ function mapIdentityOverridesToSegments(
|
|
|
160
163
|
name: fs.feature.name,
|
|
161
164
|
enabled: fs.enabled,
|
|
162
165
|
value: fs.getValue(),
|
|
166
|
+
variants: mapFeatureStateVariants(fs),
|
|
163
167
|
priority: -Infinity,
|
|
164
168
|
metadata: {
|
|
165
169
|
id: fs.feature.id
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "flagsmith-nodejs",
|
|
3
|
-
"version": "8.0.
|
|
3
|
+
"version": "8.0.2",
|
|
4
4
|
"description": "Flagsmith lets you manage features flags and remote config across web, mobile and server side applications. Deliver true Continuous Integration. Get builds out faster. Control who has access to new features.",
|
|
5
5
|
"main": "./build/cjs/index.js",
|
|
6
6
|
"type": "module",
|
package/sdk/index.ts
CHANGED
|
@@ -8,7 +8,13 @@ import { FlagsmithAPIError, FlagsmithClientError } from './errors.js';
|
|
|
8
8
|
|
|
9
9
|
import { DefaultFlag, Flags } from './models.js';
|
|
10
10
|
import { EnvironmentDataPollingManager } from './polling_manager.js';
|
|
11
|
-
import {
|
|
11
|
+
import {
|
|
12
|
+
Deferred,
|
|
13
|
+
generateIdentitiesData,
|
|
14
|
+
getUserAgent,
|
|
15
|
+
isTraitConfig,
|
|
16
|
+
retryFetch
|
|
17
|
+
} from './utils.js';
|
|
12
18
|
import {
|
|
13
19
|
SegmentModel,
|
|
14
20
|
EnvironmentModel,
|
|
@@ -275,7 +281,7 @@ export class Flagsmith {
|
|
|
275
281
|
identifier,
|
|
276
282
|
Object.keys(traits || {}).map(key => ({
|
|
277
283
|
key,
|
|
278
|
-
value: traits?.[key]
|
|
284
|
+
value: isTraitConfig(traits?.[key]) ? traits![key].value : traits?.[key]
|
|
279
285
|
}))
|
|
280
286
|
);
|
|
281
287
|
|
|
@@ -474,7 +480,7 @@ export class Flagsmith {
|
|
|
474
480
|
identifier,
|
|
475
481
|
Object.keys(traits).map(key => ({
|
|
476
482
|
key,
|
|
477
|
-
value: traits[key]
|
|
483
|
+
value: isTraitConfig(traits[key]) ? traits[key].value : traits[key]
|
|
478
484
|
}))
|
|
479
485
|
);
|
|
480
486
|
|
|
@@ -7,7 +7,13 @@ import {
|
|
|
7
7
|
shouldApplyOverride
|
|
8
8
|
} from '../../../flagsmith-engine/index.js';
|
|
9
9
|
import { CONSTANTS } from '../../../flagsmith-engine/features/constants.js';
|
|
10
|
-
import {
|
|
10
|
+
import {
|
|
11
|
+
FeatureModel,
|
|
12
|
+
FeatureSegment,
|
|
13
|
+
FeatureStateModel,
|
|
14
|
+
MultivariateFeatureOptionModel,
|
|
15
|
+
MultivariateFeatureStateValueModel
|
|
16
|
+
} from '../../../flagsmith-engine/features/models.js';
|
|
11
17
|
import { TraitModel } from '../../../flagsmith-engine/identities/traits/models.js';
|
|
12
18
|
import {
|
|
13
19
|
environment,
|
|
@@ -15,12 +21,15 @@ import {
|
|
|
15
21
|
feature1,
|
|
16
22
|
identity,
|
|
17
23
|
identityInSegment,
|
|
24
|
+
segment,
|
|
18
25
|
segmentConditionProperty,
|
|
19
|
-
segmentConditionStringValue
|
|
26
|
+
segmentConditionStringValue,
|
|
27
|
+
traitMatchingSegment
|
|
20
28
|
} from './utils.js';
|
|
21
29
|
import { getEvaluationContext } from '../../../flagsmith-engine/evaluation/evaluationContext/mappers.js';
|
|
22
30
|
import { TARGETING_REASONS } from '../../../flagsmith-engine/features/types.js';
|
|
23
31
|
import { EvaluationContext } from '../../../flagsmith-engine/evaluation/evaluationContext/evaluationContext.types.js';
|
|
32
|
+
import { EvaluationContextWithMetadata } from '../../../flagsmith-engine/evaluation/models.js';
|
|
24
33
|
import { IDENTITY_OVERRIDE_SEGMENT_NAME } from '../../../flagsmith-engine/segments/constants.js';
|
|
25
34
|
|
|
26
35
|
test('test_get_evaluation_result_without_any_override', () => {
|
|
@@ -356,3 +365,84 @@ test('evaluateFeatures with multivariate evaluation', () => {
|
|
|
356
365
|
const flags = evaluateFeatures(context as any, {});
|
|
357
366
|
expect(flags['Multivariate Feature'].value).toBe('variant_b');
|
|
358
367
|
});
|
|
368
|
+
|
|
369
|
+
test('local evaluation returns correct multivariate value for segment override with 100% weight', () => {
|
|
370
|
+
// Given
|
|
371
|
+
// a feature with two multivariate variants where the segment override
|
|
372
|
+
// assigns 100% weight to the second variant
|
|
373
|
+
const env = environment();
|
|
374
|
+
const seg = segment();
|
|
375
|
+
|
|
376
|
+
const mvFeature = new FeatureModel(10, 'mv_feature', CONSTANTS.STANDARD);
|
|
377
|
+
|
|
378
|
+
const controlOption = new MultivariateFeatureOptionModel('control', 1);
|
|
379
|
+
const variantOption = new MultivariateFeatureOptionModel('variant_b', 2);
|
|
380
|
+
|
|
381
|
+
const envFs = new FeatureStateModel(mvFeature, true, 10, 'default');
|
|
382
|
+
envFs.multivariateFeatureStateValues = [
|
|
383
|
+
new MultivariateFeatureStateValueModel(controlOption, 0, 1),
|
|
384
|
+
new MultivariateFeatureStateValueModel(variantOption, 100, 2)
|
|
385
|
+
];
|
|
386
|
+
env.featureStates.push(envFs);
|
|
387
|
+
|
|
388
|
+
const overrideFs = new FeatureStateModel(mvFeature, true, 11, 'default');
|
|
389
|
+
overrideFs.featureSegment = new FeatureSegment(0);
|
|
390
|
+
overrideFs.multivariateFeatureStateValues = [
|
|
391
|
+
new MultivariateFeatureStateValueModel(controlOption, 0, 1),
|
|
392
|
+
new MultivariateFeatureStateValueModel(variantOption, 100, 2)
|
|
393
|
+
];
|
|
394
|
+
seg.featureStates.push(overrideFs);
|
|
395
|
+
env.project.segments = [seg];
|
|
396
|
+
|
|
397
|
+
// When
|
|
398
|
+
// evaluating flags for an identity that matches the segment
|
|
399
|
+
const context = getEvaluationContext(env, identityInSegment(), [traitMatchingSegment()]);
|
|
400
|
+
const result = getEvaluationResult(context as EvaluationContextWithMetadata);
|
|
401
|
+
const flag = result.flags['mv_feature'];
|
|
402
|
+
|
|
403
|
+
// Then
|
|
404
|
+
// the flag value should be the 100%-weighted variant, not the base default
|
|
405
|
+
expect(flag).toBeDefined();
|
|
406
|
+
expect(flag.value).toBe('variant_b');
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
test('getEvaluationContext maps multivariate variants onto segment override feature states', () => {
|
|
410
|
+
// Given
|
|
411
|
+
// a segment override feature state with multivariate values
|
|
412
|
+
const env = environment();
|
|
413
|
+
const seg = segment();
|
|
414
|
+
|
|
415
|
+
const mvFeature = new FeatureModel(10, 'mv_feature', CONSTANTS.STANDARD);
|
|
416
|
+
env.featureStates.push(new FeatureStateModel(mvFeature, true, 10, 'default'));
|
|
417
|
+
|
|
418
|
+
const overrideFs = new FeatureStateModel(mvFeature, true, 11, 'default');
|
|
419
|
+
overrideFs.featureSegment = new FeatureSegment(0);
|
|
420
|
+
overrideFs.multivariateFeatureStateValues = [
|
|
421
|
+
new MultivariateFeatureStateValueModel(
|
|
422
|
+
new MultivariateFeatureOptionModel('variant_value', 1),
|
|
423
|
+
100,
|
|
424
|
+
1
|
|
425
|
+
)
|
|
426
|
+
];
|
|
427
|
+
seg.featureStates.push(overrideFs);
|
|
428
|
+
env.project.segments = [seg];
|
|
429
|
+
|
|
430
|
+
// When
|
|
431
|
+
// mapping the environment model to an evaluation context
|
|
432
|
+
const context = getEvaluationContext(env, identityInSegment(), [traitMatchingSegment()]);
|
|
433
|
+
|
|
434
|
+
// Then
|
|
435
|
+
// the segment override should include the variants array
|
|
436
|
+
const segmentOverrides = Object.values(context.segments || {});
|
|
437
|
+
const segWithOverrides = segmentOverrides.find(
|
|
438
|
+
s => s.overrides && s.overrides.some((o: any) => o.name === 'mv_feature')
|
|
439
|
+
);
|
|
440
|
+
expect(segWithOverrides).toBeDefined();
|
|
441
|
+
|
|
442
|
+
const mvOverride = segWithOverrides!.overrides!.find((o: any) => o.name === 'mv_feature');
|
|
443
|
+
expect(mvOverride).toBeDefined();
|
|
444
|
+
expect((mvOverride as any).variants).toBeDefined();
|
|
445
|
+
expect((mvOverride as any).variants).toHaveLength(1);
|
|
446
|
+
expect((mvOverride as any).variants[0].value).toBe('variant_value');
|
|
447
|
+
expect((mvOverride as any).variants[0].weight).toBe(100);
|
|
448
|
+
});
|
|
@@ -209,6 +209,76 @@ test('test_identity_with_transient_traits', async () => {
|
|
|
209
209
|
expect(identityFlags[0].featureName).toBe('some_feature');
|
|
210
210
|
});
|
|
211
211
|
|
|
212
|
+
test('getIdentityFlags local evaluation with plain traits matches segment', async () => {
|
|
213
|
+
const identifier = 'identifier';
|
|
214
|
+
// Plain trait format: age=30 should match segment rule "age LESS_THAN 40"
|
|
215
|
+
const traits = { age: 30 };
|
|
216
|
+
|
|
217
|
+
const flg = flagsmith({
|
|
218
|
+
environmentKey: 'ser.key',
|
|
219
|
+
enableLocalEvaluation: true
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
const flags = await flg.getIdentityFlags(identifier, traits);
|
|
223
|
+
|
|
224
|
+
// Should get segment override value, not the default
|
|
225
|
+
expect(flags.getFeatureValue('some_feature')).toBe('segment_override');
|
|
226
|
+
expect(flags.isFeatureEnabled('some_feature')).toBe(false);
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
test('getIdentityFlags local evaluation with TraitConfig format matches segment', async () => {
|
|
230
|
+
const identifier = 'identifier';
|
|
231
|
+
// TraitConfig format: same trait value wrapped with transient metadata
|
|
232
|
+
const traits = { age: { value: 30, transient: true } };
|
|
233
|
+
|
|
234
|
+
const flg = flagsmith({
|
|
235
|
+
environmentKey: 'ser.key',
|
|
236
|
+
enableLocalEvaluation: true
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
const flags = await flg.getIdentityFlags(identifier, traits);
|
|
240
|
+
|
|
241
|
+
// Should get segment override value — same result as plain trait format
|
|
242
|
+
expect(flags.getFeatureValue('some_feature')).toBe('segment_override');
|
|
243
|
+
expect(flags.isFeatureEnabled('some_feature')).toBe(false);
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
test('getIdentityFlags local evaluation with mixed trait formats matches segment', async () => {
|
|
247
|
+
const identifier = 'identifier';
|
|
248
|
+
// Mix of plain and TraitConfig formats
|
|
249
|
+
const traits = {
|
|
250
|
+
age: { value: 30, transient: true },
|
|
251
|
+
some_other_trait: 'plain_value'
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
const flg = flagsmith({
|
|
255
|
+
environmentKey: 'ser.key',
|
|
256
|
+
enableLocalEvaluation: true
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
const flags = await flg.getIdentityFlags(identifier, traits);
|
|
260
|
+
|
|
261
|
+
// Should get segment override value
|
|
262
|
+
expect(flags.getFeatureValue('some_feature')).toBe('segment_override');
|
|
263
|
+
expect(flags.isFeatureEnabled('some_feature')).toBe(false);
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
test('getIdentitySegments with TraitConfig format matches segment', async () => {
|
|
267
|
+
const identifier = 'identifier';
|
|
268
|
+
// TraitConfig format should work for getIdentitySegments too
|
|
269
|
+
const traits = { age: { value: 30, transient: true } };
|
|
270
|
+
|
|
271
|
+
const flg = flagsmith({
|
|
272
|
+
environmentKey: 'ser.key',
|
|
273
|
+
enableLocalEvaluation: true
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
const segments = await flg.getIdentitySegments(identifier, traits);
|
|
277
|
+
|
|
278
|
+
expect(segments).toHaveLength(1);
|
|
279
|
+
expect(segments[0].name).toBe('regular_segment');
|
|
280
|
+
});
|
|
281
|
+
|
|
212
282
|
test('getIdentityFlags fails if API call failed and no default flag handler was provided', async () => {
|
|
213
283
|
const flg = flagsmith({
|
|
214
284
|
fetch: badFetch
|