flagsmith-nodejs 1.0.9 → 2.0.0-beta.1
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/pull_request.yaml +33 -0
- package/.gitmodules +3 -0
- package/.husky/pre-commit +6 -0
- package/.idea/flagsmith-nodejs-client.iml +12 -0
- package/.idea/modules.xml +8 -0
- package/.idea/vcs.xml +6 -0
- package/.prettierignore +1 -0
- package/.prettierrc.js +9 -0
- package/CONTRIBUTING.md +5 -4
- package/{LICENCE.md → LICENCE} +5 -6
- package/README.md +1 -1
- package/build/flagsmith-engine/environments/integrations/models.d.ts +4 -0
- package/build/flagsmith-engine/environments/integrations/models.js +8 -0
- package/build/flagsmith-engine/environments/models.d.ts +25 -0
- package/build/flagsmith-engine/environments/models.js +40 -0
- package/build/flagsmith-engine/environments/util.d.ts +3 -0
- package/build/flagsmith-engine/environments/util.js +19 -0
- package/build/flagsmith-engine/features/constants.d.ts +4 -0
- package/build/flagsmith-engine/features/constants.js +7 -0
- package/build/flagsmith-engine/features/models.d.ts +32 -0
- package/build/flagsmith-engine/features/models.js +85 -0
- package/build/flagsmith-engine/features/util.d.ts +3 -0
- package/build/flagsmith-engine/features/util.js +20 -0
- package/build/flagsmith-engine/identities/models.d.ts +15 -0
- package/build/flagsmith-engine/identities/models.js +47 -0
- package/build/flagsmith-engine/identities/traits/models.d.ts +5 -0
- package/build/flagsmith-engine/identities/traits/models.js +12 -0
- package/build/flagsmith-engine/identities/util.d.ts +4 -0
- package/build/flagsmith-engine/identities/util.js +22 -0
- package/build/flagsmith-engine/index.d.ts +8 -0
- package/build/flagsmith-engine/index.js +60 -0
- package/build/flagsmith-engine/organisations/models.d.ts +9 -0
- package/build/flagsmith-engine/organisations/models.js +21 -0
- package/build/flagsmith-engine/organisations/util.d.ts +2 -0
- package/build/flagsmith-engine/organisations/util.js +8 -0
- package/build/flagsmith-engine/projects/models.d.ts +10 -0
- package/build/flagsmith-engine/projects/models.js +18 -0
- package/build/flagsmith-engine/projects/util.d.ts +2 -0
- package/build/flagsmith-engine/projects/util.js +15 -0
- package/build/flagsmith-engine/segments/constants.d.ts +26 -0
- package/build/flagsmith-engine/segments/constants.js +31 -0
- package/build/flagsmith-engine/segments/evaluators.d.ts +6 -0
- package/build/flagsmith-engine/segments/evaluators.js +29 -0
- package/build/flagsmith-engine/segments/models.d.ts +31 -0
- package/build/flagsmith-engine/segments/models.js +83 -0
- package/build/flagsmith-engine/segments/util.d.ts +4 -0
- package/build/flagsmith-engine/segments/util.js +23 -0
- package/build/flagsmith-engine/utils/collections.d.ts +4 -0
- package/build/flagsmith-engine/utils/collections.js +16 -0
- package/build/flagsmith-engine/utils/hashing/index.d.ts +1 -0
- package/build/flagsmith-engine/utils/hashing/index.js +56 -0
- package/build/flagsmith-engine/utils/index.d.ts +1 -0
- package/build/flagsmith-engine/utils/index.js +14 -0
- package/build/index.d.ts +2 -0
- package/build/index.js +7 -0
- package/build/sdk/analytics.d.ts +28 -0
- package/build/sdk/analytics.js +81 -0
- package/build/sdk/errors.d.ts +4 -0
- package/build/sdk/errors.js +9 -0
- package/build/sdk/index.d.ts +99 -0
- package/build/sdk/index.js +221 -0
- package/build/sdk/models.d.ts +55 -0
- package/build/sdk/models.js +102 -0
- package/build/sdk/polling_manager.d.ts +9 -0
- package/build/sdk/polling_manager.js +31 -0
- package/build/sdk/utils.d.ts +12 -0
- package/build/sdk/utils.js +45 -0
- package/example/README.md +8 -14
- package/example/package-lock.json +12 -12
- package/example/package.json +4 -4
- package/example/server/api/index.js +19 -22
- package/example/server/index.js +4 -9
- package/flagsmith-engine/environments/integrations/models.ts +4 -0
- package/flagsmith-engine/environments/models.ts +50 -0
- package/flagsmith-engine/environments/util.ts +29 -0
- package/flagsmith-engine/features/constants.ts +4 -0
- package/flagsmith-engine/features/models.ts +105 -0
- package/flagsmith-engine/features/util.ts +38 -0
- package/flagsmith-engine/identities/models.ts +60 -0
- package/flagsmith-engine/identities/traits/models.ts +9 -0
- package/flagsmith-engine/identities/util.ts +30 -0
- package/flagsmith-engine/index.ts +92 -0
- package/flagsmith-engine/organisations/models.ts +25 -0
- package/flagsmith-engine/organisations/util.ts +11 -0
- package/flagsmith-engine/projects/models.ts +23 -0
- package/flagsmith-engine/projects/util.ts +18 -0
- package/flagsmith-engine/segments/constants.ts +31 -0
- package/flagsmith-engine/segments/evaluators.ts +72 -0
- package/flagsmith-engine/segments/models.ts +103 -0
- package/flagsmith-engine/segments/util.ts +29 -0
- package/flagsmith-engine/utils/collections.ts +14 -0
- package/flagsmith-engine/utils/hashing/index.ts +57 -0
- package/flagsmith-engine/utils/index.ts +10 -0
- package/index.ts +3 -0
- package/jest.config.js +5 -0
- package/package.json +24 -3
- package/sdk/analytics.ts +88 -0
- package/sdk/errors.ts +2 -0
- package/sdk/index.ts +282 -0
- package/sdk/models.ts +143 -0
- package/sdk/polling_manager.ts +31 -0
- package/sdk/utils.ts +45 -0
- package/tests/engine/e2e/engine.test.ts +51 -0
- package/tests/engine/engine-tests/engine-test-data/data/environment_n9fbf9h3v4fFgH3U3ngWhb.json +12393 -0
- package/tests/engine/engine-tests/engine-test-data/readme.md +30 -0
- package/tests/engine/unit/egine.test.ts +96 -0
- package/tests/engine/unit/environments/builder.test.ts +148 -0
- package/tests/engine/unit/environments/models.test.ts +49 -0
- package/tests/engine/unit/features/models.test.ts +72 -0
- package/tests/engine/unit/identities/identities_builders.test.ts +85 -0
- package/tests/engine/unit/identities/identities_models.test.ts +105 -0
- package/tests/engine/unit/organization/models.test.ts +12 -0
- package/tests/engine/unit/segments/segments_model.test.ts +101 -0
- package/tests/engine/unit/segments/util.ts +151 -0
- package/tests/engine/unit/utils.ts +114 -0
- package/tests/index.js +0 -0
- package/tests/sdk/analytics.test.ts +43 -0
- package/tests/sdk/data/environment.json +33 -0
- package/tests/sdk/data/flags.json +20 -0
- package/tests/sdk/data/identities.json +29 -0
- package/tests/sdk/flagsmith.test.ts +184 -0
- package/tests/sdk/polling.test.ts +34 -0
- package/tests/sdk/utils.ts +39 -0
- package/tsconfig.json +19 -0
- package/flagsmith-core.js +0 -238
- package/index.d.ts +0 -78
- package/index.js +0 -5
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ALL_RULE,
|
|
3
|
+
ANY_RULE,
|
|
4
|
+
CONDITION_OPERATORS,
|
|
5
|
+
NONE_RULE
|
|
6
|
+
} from '../../../../flagsmith-engine/segments/constants';
|
|
7
|
+
import {
|
|
8
|
+
all,
|
|
9
|
+
any,
|
|
10
|
+
SegmentConditionModel,
|
|
11
|
+
SegmentRuleModel
|
|
12
|
+
} from '../../../../flagsmith-engine/segments/models';
|
|
13
|
+
|
|
14
|
+
const conditionMatchCases: [string, string | number | boolean, string, boolean][] = [
|
|
15
|
+
[CONDITION_OPERATORS.EQUAL, 'bar', 'bar', true],
|
|
16
|
+
[CONDITION_OPERATORS.EQUAL, 'bar', 'baz', false],
|
|
17
|
+
[CONDITION_OPERATORS.EQUAL, 1, '1', true],
|
|
18
|
+
[CONDITION_OPERATORS.EQUAL, 1, '2', false],
|
|
19
|
+
[CONDITION_OPERATORS.EQUAL, true, 'true', true],
|
|
20
|
+
[CONDITION_OPERATORS.EQUAL, false, 'false', true],
|
|
21
|
+
[CONDITION_OPERATORS.EQUAL, false, 'true', false],
|
|
22
|
+
[CONDITION_OPERATORS.EQUAL, true, 'false', false],
|
|
23
|
+
[CONDITION_OPERATORS.EQUAL, 1.23, '1.23', true],
|
|
24
|
+
[CONDITION_OPERATORS.EQUAL, 1.23, '4.56', false],
|
|
25
|
+
[CONDITION_OPERATORS.GREATER_THAN, 2, '1', true],
|
|
26
|
+
[CONDITION_OPERATORS.GREATER_THAN, 1, '1', false],
|
|
27
|
+
[CONDITION_OPERATORS.GREATER_THAN, 0, '1', false],
|
|
28
|
+
[CONDITION_OPERATORS.GREATER_THAN, 2.1, '2.0', true],
|
|
29
|
+
[CONDITION_OPERATORS.GREATER_THAN, 2.1, '2.1', false],
|
|
30
|
+
[CONDITION_OPERATORS.GREATER_THAN, 2.0, '2.1', false],
|
|
31
|
+
[CONDITION_OPERATORS.GREATER_THAN_INCLUSIVE, 2, '1', true],
|
|
32
|
+
[CONDITION_OPERATORS.GREATER_THAN_INCLUSIVE, 1, '1', true],
|
|
33
|
+
[CONDITION_OPERATORS.GREATER_THAN_INCLUSIVE, 0, '1', false],
|
|
34
|
+
[CONDITION_OPERATORS.GREATER_THAN_INCLUSIVE, 2.1, '2.0', true],
|
|
35
|
+
[CONDITION_OPERATORS.GREATER_THAN_INCLUSIVE, 2.1, '2.1', true],
|
|
36
|
+
[CONDITION_OPERATORS.GREATER_THAN_INCLUSIVE, 2.0, '2.1', false],
|
|
37
|
+
[CONDITION_OPERATORS.LESS_THAN, 1, '2', true],
|
|
38
|
+
[CONDITION_OPERATORS.LESS_THAN, 1, '1', false],
|
|
39
|
+
[CONDITION_OPERATORS.LESS_THAN, 1, '0', false],
|
|
40
|
+
[CONDITION_OPERATORS.LESS_THAN, 2.0, '2.1', true],
|
|
41
|
+
[CONDITION_OPERATORS.LESS_THAN, 2.1, '2.1', false],
|
|
42
|
+
[CONDITION_OPERATORS.LESS_THAN, 2.1, '2.0', false],
|
|
43
|
+
[CONDITION_OPERATORS.LESS_THAN_INCLUSIVE, 1, '2', true],
|
|
44
|
+
[CONDITION_OPERATORS.LESS_THAN_INCLUSIVE, 1, '1', true],
|
|
45
|
+
[CONDITION_OPERATORS.LESS_THAN_INCLUSIVE, 1, '0', false],
|
|
46
|
+
[CONDITION_OPERATORS.LESS_THAN_INCLUSIVE, 2.0, '2.1', true],
|
|
47
|
+
[CONDITION_OPERATORS.LESS_THAN_INCLUSIVE, 2.1, '2.1', true],
|
|
48
|
+
[CONDITION_OPERATORS.LESS_THAN_INCLUSIVE, 2.1, '2.0', false],
|
|
49
|
+
[CONDITION_OPERATORS.NOT_EQUAL, 'bar', 'baz', true],
|
|
50
|
+
[CONDITION_OPERATORS.NOT_EQUAL, 'bar', 'bar', false],
|
|
51
|
+
[CONDITION_OPERATORS.NOT_EQUAL, 1, '2', true],
|
|
52
|
+
[CONDITION_OPERATORS.NOT_EQUAL, 1, '1', false],
|
|
53
|
+
[CONDITION_OPERATORS.NOT_EQUAL, true, 'false', true],
|
|
54
|
+
[CONDITION_OPERATORS.NOT_EQUAL, false, 'true', true],
|
|
55
|
+
[CONDITION_OPERATORS.NOT_EQUAL, false, 'false', false],
|
|
56
|
+
[CONDITION_OPERATORS.NOT_EQUAL, true, 'true', false],
|
|
57
|
+
[CONDITION_OPERATORS.CONTAINS, 'bar', 'b', true],
|
|
58
|
+
[CONDITION_OPERATORS.CONTAINS, 'bar', 'bar', true],
|
|
59
|
+
[CONDITION_OPERATORS.CONTAINS, 'bar', 'baz', false],
|
|
60
|
+
[CONDITION_OPERATORS.NOT_CONTAINS, 'bar', 'b', false],
|
|
61
|
+
[CONDITION_OPERATORS.NOT_CONTAINS, 'bar', 'bar', false],
|
|
62
|
+
[CONDITION_OPERATORS.NOT_CONTAINS, 'bar', 'baz', true],
|
|
63
|
+
[CONDITION_OPERATORS.REGEX, 'foo', '[a-z]+', true],
|
|
64
|
+
[CONDITION_OPERATORS.REGEX, 'FOO', '[a-z]+', false]
|
|
65
|
+
];
|
|
66
|
+
|
|
67
|
+
test('test_segment_condition_matches_trait_value', () => {
|
|
68
|
+
for (const testCase of conditionMatchCases) {
|
|
69
|
+
expect(
|
|
70
|
+
new SegmentConditionModel(testCase[0], testCase[2], 'foo').matchesTraitValue(
|
|
71
|
+
testCase[1]
|
|
72
|
+
)
|
|
73
|
+
).toBe(testCase[3]);
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
test('test_segment_rule_none', () => {
|
|
78
|
+
const testCases: [boolean[], boolean][] = [
|
|
79
|
+
[[], true],
|
|
80
|
+
[[false], true],
|
|
81
|
+
[[false, false], true],
|
|
82
|
+
[[false, true], false],
|
|
83
|
+
[[true, true], false]
|
|
84
|
+
];
|
|
85
|
+
|
|
86
|
+
for (const testCase of testCases) {
|
|
87
|
+
expect(SegmentRuleModel.none(testCase[0])).toBe(testCase[1]);
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
test('test_segment_rule_matching_function', () => {
|
|
92
|
+
const testCases: [string, CallableFunction][] = [
|
|
93
|
+
[ALL_RULE, all],
|
|
94
|
+
[ANY_RULE, any],
|
|
95
|
+
[NONE_RULE, SegmentRuleModel.none]
|
|
96
|
+
];
|
|
97
|
+
|
|
98
|
+
for (const testCase of testCases) {
|
|
99
|
+
expect(new SegmentRuleModel(testCase[0]).matchingFunction()).toBe(testCase[1]);
|
|
100
|
+
}
|
|
101
|
+
});
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { ALL_RULE, ANY_RULE, EQUAL } from '../../../../flagsmith-engine/segments/constants';
|
|
2
|
+
import { SegmentModel } from '../../../../flagsmith-engine/segments/models';
|
|
3
|
+
import { buildSegmentModel } from '../../../../flagsmith-engine/segments/util';
|
|
4
|
+
|
|
5
|
+
export const traitKey1 = 'email';
|
|
6
|
+
export const traitValue1 = 'user@example.com';
|
|
7
|
+
|
|
8
|
+
export const traitKey2 = 'num_purchase';
|
|
9
|
+
export const traitValue2 = '12';
|
|
10
|
+
|
|
11
|
+
export const traitKey_3 = 'date_joined';
|
|
12
|
+
export const traitValue3 = '2021-01-01';
|
|
13
|
+
|
|
14
|
+
export const emptySegment = new SegmentModel(1, 'empty_segment');
|
|
15
|
+
|
|
16
|
+
export const segmentSingleCondition = buildSegmentModel({
|
|
17
|
+
id: 2,
|
|
18
|
+
name: 'segment_one_condition',
|
|
19
|
+
rules: [
|
|
20
|
+
{
|
|
21
|
+
type: ALL_RULE,
|
|
22
|
+
conditions: [
|
|
23
|
+
{
|
|
24
|
+
operator: EQUAL,
|
|
25
|
+
property_: traitKey1,
|
|
26
|
+
value: traitValue1
|
|
27
|
+
}
|
|
28
|
+
]
|
|
29
|
+
}
|
|
30
|
+
]
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
export const segmentMultipleConditionsAll = buildSegmentModel({
|
|
34
|
+
id: 3,
|
|
35
|
+
name: 'segment_multiple_conditions_all',
|
|
36
|
+
rules: [
|
|
37
|
+
{
|
|
38
|
+
type: ALL_RULE,
|
|
39
|
+
conditions: [
|
|
40
|
+
{
|
|
41
|
+
operator: EQUAL,
|
|
42
|
+
property_: traitKey1,
|
|
43
|
+
value: traitValue1
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
operator: EQUAL,
|
|
47
|
+
property_: traitKey2,
|
|
48
|
+
value: traitValue2
|
|
49
|
+
}
|
|
50
|
+
]
|
|
51
|
+
}
|
|
52
|
+
]
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
export const segmentMultipleConditionsAny = buildSegmentModel({
|
|
56
|
+
id: 4,
|
|
57
|
+
name: 'segment_multiple_conditions_any',
|
|
58
|
+
rules: [
|
|
59
|
+
{
|
|
60
|
+
type: ANY_RULE,
|
|
61
|
+
conditions: [
|
|
62
|
+
{
|
|
63
|
+
operator: EQUAL,
|
|
64
|
+
property_: traitKey1,
|
|
65
|
+
value: traitValue1
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
operator: EQUAL,
|
|
69
|
+
property_: traitKey2,
|
|
70
|
+
value: traitValue2
|
|
71
|
+
}
|
|
72
|
+
]
|
|
73
|
+
}
|
|
74
|
+
]
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
export const segmentNestedRules = buildSegmentModel({
|
|
78
|
+
id: 5,
|
|
79
|
+
name: 'segment_nested_rules_all',
|
|
80
|
+
rules: [
|
|
81
|
+
{
|
|
82
|
+
type: ALL_RULE,
|
|
83
|
+
rules: [
|
|
84
|
+
{
|
|
85
|
+
type: ALL_RULE,
|
|
86
|
+
conditions: [
|
|
87
|
+
{
|
|
88
|
+
operator: EQUAL,
|
|
89
|
+
property_: traitKey1,
|
|
90
|
+
value: traitValue1
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
operator: EQUAL,
|
|
94
|
+
property_: traitKey2,
|
|
95
|
+
value: traitValue2
|
|
96
|
+
}
|
|
97
|
+
]
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
type: ALL_RULE,
|
|
101
|
+
conditions: [
|
|
102
|
+
{
|
|
103
|
+
operator: EQUAL,
|
|
104
|
+
property_: traitKey_3,
|
|
105
|
+
value: traitValue3
|
|
106
|
+
}
|
|
107
|
+
]
|
|
108
|
+
}
|
|
109
|
+
]
|
|
110
|
+
}
|
|
111
|
+
]
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
export const segmentConditionsAndNestedRules = buildSegmentModel({
|
|
115
|
+
id: 6,
|
|
116
|
+
name: 'segment_multiple_conditions_all_and_nested_rules',
|
|
117
|
+
rules: [
|
|
118
|
+
{
|
|
119
|
+
type: ALL_RULE,
|
|
120
|
+
conditions: [
|
|
121
|
+
{
|
|
122
|
+
operator: EQUAL,
|
|
123
|
+
property_: traitKey1,
|
|
124
|
+
value: traitValue1
|
|
125
|
+
}
|
|
126
|
+
],
|
|
127
|
+
rules: [
|
|
128
|
+
{
|
|
129
|
+
type: ALL_RULE,
|
|
130
|
+
conditions: [
|
|
131
|
+
{
|
|
132
|
+
operator: EQUAL,
|
|
133
|
+
property_: traitKey2,
|
|
134
|
+
value: traitValue2
|
|
135
|
+
}
|
|
136
|
+
]
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
type: ALL_RULE,
|
|
140
|
+
conditions: [
|
|
141
|
+
{
|
|
142
|
+
operator: EQUAL,
|
|
143
|
+
property_: traitKey_3,
|
|
144
|
+
value: traitValue3
|
|
145
|
+
}
|
|
146
|
+
]
|
|
147
|
+
}
|
|
148
|
+
]
|
|
149
|
+
}
|
|
150
|
+
]
|
|
151
|
+
});
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { EnvironmentModel } from '../../../flagsmith-engine/environments/models';
|
|
2
|
+
import { CONSTANTS } from '../../../flagsmith-engine/features/constants';
|
|
3
|
+
import { FeatureModel, FeatureStateModel } from '../../../flagsmith-engine/features/models';
|
|
4
|
+
import { IdentityModel } from '../../../flagsmith-engine/identities/models';
|
|
5
|
+
import { TraitModel } from '../../../flagsmith-engine/identities/traits/models';
|
|
6
|
+
import { OrganisationModel } from '../../../flagsmith-engine/organisations/models';
|
|
7
|
+
import { ProjectModel } from '../../../flagsmith-engine/projects/models';
|
|
8
|
+
import { ALL_RULE, EQUAL } from '../../../flagsmith-engine/segments/constants';
|
|
9
|
+
import {
|
|
10
|
+
SegmentConditionModel,
|
|
11
|
+
SegmentModel,
|
|
12
|
+
SegmentRuleModel
|
|
13
|
+
} from '../../../flagsmith-engine/segments/models';
|
|
14
|
+
|
|
15
|
+
export const segmentConditionProperty = 'foo';
|
|
16
|
+
export const segmentConditionStringValue = 'bar';
|
|
17
|
+
|
|
18
|
+
export function segmentCondition() {
|
|
19
|
+
return new SegmentConditionModel(EQUAL, segmentConditionStringValue, segmentConditionProperty);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function traitMatchingSegment() {
|
|
23
|
+
return new TraitModel(segmentCondition().property_ as string, segmentCondition().value);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function organisation() {
|
|
27
|
+
return new OrganisationModel(1, 'test Org', true, false, true);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function segmentRule() {
|
|
31
|
+
const rule = new SegmentRuleModel(ALL_RULE);
|
|
32
|
+
rule.conditions = [segmentCondition()];
|
|
33
|
+
return rule;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function segment() {
|
|
37
|
+
const segment = new SegmentModel(1, 'test name');
|
|
38
|
+
segment.rules = [segmentRule()];
|
|
39
|
+
return segment;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function project() {
|
|
43
|
+
const project = new ProjectModel(1, 'test project', false, organisation());
|
|
44
|
+
project.segments = [segment()];
|
|
45
|
+
return project;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function feature1() {
|
|
49
|
+
return new FeatureModel(1, 'feature_1', CONSTANTS.STANDARD);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function feature2() {
|
|
53
|
+
return new FeatureModel(2, 'feature_2', CONSTANTS.STANDARD);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function environment() {
|
|
57
|
+
const env = new EnvironmentModel(1, 'api-key', project());
|
|
58
|
+
|
|
59
|
+
env.featureStates = [
|
|
60
|
+
new FeatureStateModel(feature1(), true, 1),
|
|
61
|
+
new FeatureStateModel(feature2(), false, 2)
|
|
62
|
+
];
|
|
63
|
+
|
|
64
|
+
return env;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function identity() {
|
|
68
|
+
return new IdentityModel(Date.now().toString(), [], [], environment().apiKey, 'identity_1');
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function identityInSegment() {
|
|
72
|
+
const identity = new IdentityModel(
|
|
73
|
+
Date.now().toString(),
|
|
74
|
+
[],
|
|
75
|
+
[],
|
|
76
|
+
environment().apiKey,
|
|
77
|
+
'identity_2'
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
identity.identityTraits = [traitMatchingSegment()];
|
|
81
|
+
|
|
82
|
+
return identity;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function getEnvironmentFeatureStateForFeatureByName(
|
|
86
|
+
environment: EnvironmentModel,
|
|
87
|
+
feature_name: string
|
|
88
|
+
): FeatureStateModel | undefined {
|
|
89
|
+
const features = environment.featureStates.filter(fs => fs.feature.name === feature_name);
|
|
90
|
+
return features.length > 0 ? features[0] : undefined;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export function getEnvironmentFeatureStateForFeature(
|
|
94
|
+
environment: EnvironmentModel,
|
|
95
|
+
feature: FeatureModel
|
|
96
|
+
): FeatureStateModel | undefined {
|
|
97
|
+
const f = environment.featureStates.find(f => f.feature === feature);
|
|
98
|
+
return f;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function segmentOverrideFs() {
|
|
102
|
+
const fs = new FeatureStateModel(feature1(), false, 4);
|
|
103
|
+
fs.setValue('segment_override');
|
|
104
|
+
return fs;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export function environmentWithSegmentOverride(): EnvironmentModel {
|
|
108
|
+
const env = environment();
|
|
109
|
+
const segm = segment();
|
|
110
|
+
|
|
111
|
+
segm.featureStates.push(segmentOverrideFs());
|
|
112
|
+
env.project.segments.push(segm);
|
|
113
|
+
return env;
|
|
114
|
+
}
|
package/tests/index.js
ADDED
|
File without changes
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
jest.mock('node-fetch');
|
|
2
|
+
import fetch from 'node-fetch';
|
|
3
|
+
import { analyticsProcessor } from './utils';
|
|
4
|
+
|
|
5
|
+
afterEach(() => {
|
|
6
|
+
jest.clearAllMocks();
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
test('test_analytics_processor_track_feature_updates_analytics_data', () => {
|
|
10
|
+
const aP = analyticsProcessor();
|
|
11
|
+
aP.trackFeature(1);
|
|
12
|
+
expect(aP.analyticsData[1]).toBe(1);
|
|
13
|
+
|
|
14
|
+
aP.trackFeature(1);
|
|
15
|
+
expect(aP.analyticsData[1]).toBe(2);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
test('test_analytics_processor_flush_clears_analytics_data', async () => {
|
|
19
|
+
const aP = analyticsProcessor();
|
|
20
|
+
aP.trackFeature(1);
|
|
21
|
+
await aP.flush();
|
|
22
|
+
expect(aP.analyticsData).toStrictEqual({});
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
test('test_analytics_processor_flush_post_request_data_match_ananlytics_data', async () => {
|
|
26
|
+
const aP = analyticsProcessor();
|
|
27
|
+
aP.trackFeature(1);
|
|
28
|
+
aP.trackFeature(2);
|
|
29
|
+
await aP.flush();
|
|
30
|
+
expect(fetch).toHaveBeenCalledTimes(1);
|
|
31
|
+
expect(fetch).toHaveBeenCalledWith('http://testUrlanalytics/flags/', {
|
|
32
|
+
body: '{"1":1,"2":1}',
|
|
33
|
+
headers: { 'Content-Type': 'application/json', 'X-Environment-Key': 'test-key' },
|
|
34
|
+
method: 'POST',
|
|
35
|
+
timeout: 3
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
test('test_analytics_processor_flush_early_exit_if_analytics_data_is_empty', async () => {
|
|
40
|
+
const aP = analyticsProcessor();
|
|
41
|
+
await aP.flush();
|
|
42
|
+
expect(fetch).not.toHaveBeenCalled();
|
|
43
|
+
});
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"api_key": "B62qaMZNwfiqT76p38ggrQ",
|
|
3
|
+
"project": {
|
|
4
|
+
"name": "Test project",
|
|
5
|
+
"organisation": {
|
|
6
|
+
"feature_analytics": false,
|
|
7
|
+
"name": "Test Org",
|
|
8
|
+
"id": 1,
|
|
9
|
+
"persist_trait_data": true,
|
|
10
|
+
"stop_serving_flags": false
|
|
11
|
+
},
|
|
12
|
+
"id": 1,
|
|
13
|
+
"hide_disabled_flags": false,
|
|
14
|
+
"segments": []
|
|
15
|
+
},
|
|
16
|
+
"segment_overrides": [],
|
|
17
|
+
"id": 1,
|
|
18
|
+
"feature_states": [
|
|
19
|
+
{
|
|
20
|
+
"multivariate_feature_state_values": [],
|
|
21
|
+
"feature_state_value": "some-value",
|
|
22
|
+
"id": 1,
|
|
23
|
+
"featurestate_uuid": "40eb539d-3713-4720-bbd4-829dbef10d51",
|
|
24
|
+
"feature": {
|
|
25
|
+
"name": "some_feature",
|
|
26
|
+
"type": "STANDARD",
|
|
27
|
+
"id": 1
|
|
28
|
+
},
|
|
29
|
+
"segment_id": null,
|
|
30
|
+
"enabled": true
|
|
31
|
+
}
|
|
32
|
+
]
|
|
33
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"id": 1,
|
|
4
|
+
"feature": {
|
|
5
|
+
"id": 1,
|
|
6
|
+
"name": "some_feature",
|
|
7
|
+
"created_date": "2019-08-27T14:53:45.698555Z",
|
|
8
|
+
"initial_value": null,
|
|
9
|
+
"description": null,
|
|
10
|
+
"default_enabled": false,
|
|
11
|
+
"type": "STANDARD",
|
|
12
|
+
"project": 1
|
|
13
|
+
},
|
|
14
|
+
"feature_state_value": "some-value",
|
|
15
|
+
"enabled": true,
|
|
16
|
+
"environment": 1,
|
|
17
|
+
"identity": null,
|
|
18
|
+
"feature_segment": null
|
|
19
|
+
}
|
|
20
|
+
]
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"traits": [
|
|
3
|
+
{
|
|
4
|
+
"id": 1,
|
|
5
|
+
"trait_key": "some_trait",
|
|
6
|
+
"trait_value": "some_value"
|
|
7
|
+
}
|
|
8
|
+
],
|
|
9
|
+
"flags": [
|
|
10
|
+
{
|
|
11
|
+
"id": 1,
|
|
12
|
+
"feature": {
|
|
13
|
+
"id": 1,
|
|
14
|
+
"name": "some_feature",
|
|
15
|
+
"created_date": "2019-08-27T14:53:45.698555Z",
|
|
16
|
+
"initial_value": null,
|
|
17
|
+
"description": null,
|
|
18
|
+
"default_enabled": false,
|
|
19
|
+
"type": "STANDARD",
|
|
20
|
+
"project": 1
|
|
21
|
+
},
|
|
22
|
+
"feature_state_value": "some-value",
|
|
23
|
+
"enabled": true,
|
|
24
|
+
"environment": 1,
|
|
25
|
+
"identity": null,
|
|
26
|
+
"feature_segment": null
|
|
27
|
+
}
|
|
28
|
+
]
|
|
29
|
+
}
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import { Flagsmith } from '../../sdk';
|
|
2
|
+
import { EnvironmentDataPollingManager } from '../../sdk/polling_manager';
|
|
3
|
+
import fetch, { Headers } from 'node-fetch';
|
|
4
|
+
import { environmentJSON, environmentModel, flagsJSON, flagsmith, identitiesJSON } from './utils';
|
|
5
|
+
import { DefaultFlag } from '../../sdk/models';
|
|
6
|
+
|
|
7
|
+
jest.mock('node-fetch');
|
|
8
|
+
jest.mock('../../sdk/polling_manager');
|
|
9
|
+
const { Response } = jest.requireActual('node-fetch');
|
|
10
|
+
|
|
11
|
+
beforeEach(() => {
|
|
12
|
+
// @ts-ignore
|
|
13
|
+
jest.clearAllMocks();
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
test('test_flagsmith_starts_polling_manager_on_init_if_enabled', () => {
|
|
17
|
+
new Flagsmith({
|
|
18
|
+
environmentKey: 'key',
|
|
19
|
+
enableLocalEvaluation: true
|
|
20
|
+
});
|
|
21
|
+
expect(EnvironmentDataPollingManager).toBeCalled();
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
test('test_update_environment_sets_environment', async () => {
|
|
25
|
+
// @ts-ignore
|
|
26
|
+
fetch.mockReturnValue(Promise.resolve(new Response(environmentJSON())));
|
|
27
|
+
const flg = flagsmith();
|
|
28
|
+
await flg.updateEnvironment();
|
|
29
|
+
expect(flg.environment).toBeDefined();
|
|
30
|
+
|
|
31
|
+
// @ts-ignore
|
|
32
|
+
flg.environment.featureStates[0].featurestateUUID = undefined;
|
|
33
|
+
// @ts-ignore
|
|
34
|
+
const model = environmentModel(JSON.parse(environmentJSON()));
|
|
35
|
+
// @ts-ignore
|
|
36
|
+
model.featureStates[0].featurestateUUID = undefined;
|
|
37
|
+
expect(flg.environment).toStrictEqual(model);
|
|
38
|
+
});
|
|
39
|
+
test('test_get_environment_flags_calls_api_when_no_local_environment', async () => {
|
|
40
|
+
// @ts-ignore
|
|
41
|
+
fetch.mockReturnValue(Promise.resolve(new Response(flagsJSON())));
|
|
42
|
+
|
|
43
|
+
const flg = flagsmith();
|
|
44
|
+
const allFlags = await (await flg.getEnvironmentFlags()).allFlags();
|
|
45
|
+
|
|
46
|
+
expect(fetch).toBeCalledTimes(1);
|
|
47
|
+
expect(allFlags[0].enabled).toBe(true);
|
|
48
|
+
expect(allFlags[0].value).toBe('some-value');
|
|
49
|
+
expect(allFlags[0].featureName).toBe('some_feature');
|
|
50
|
+
});
|
|
51
|
+
test('test_get_environment_flags_uses_local_environment_when_available', async () => {
|
|
52
|
+
// @ts-ignore
|
|
53
|
+
fetch.mockReturnValue(Promise.resolve(new Response(flagsJSON())));
|
|
54
|
+
|
|
55
|
+
const flg = flagsmith();
|
|
56
|
+
const model = environmentModel(JSON.parse(environmentJSON()));
|
|
57
|
+
flg.environment = model;
|
|
58
|
+
|
|
59
|
+
const allFlags = await (await flg.getEnvironmentFlags()).allFlags();
|
|
60
|
+
expect(fetch).toBeCalledTimes(0);
|
|
61
|
+
expect(allFlags[0].enabled).toBe(model.featureStates[0].enabled);
|
|
62
|
+
expect(allFlags[0].value).toBe(model.featureStates[0].getValue());
|
|
63
|
+
expect(allFlags[0].featureName).toBe(model.featureStates[0].feature.name);
|
|
64
|
+
});
|
|
65
|
+
test('test_get_identity_flags_calls_api_when_no_local_environment_no_traits', async () => {
|
|
66
|
+
// @ts-ignore
|
|
67
|
+
fetch.mockReturnValue(Promise.resolve(new Response(identitiesJSON())));
|
|
68
|
+
const identifier = 'identifier';
|
|
69
|
+
|
|
70
|
+
const flg = flagsmith();
|
|
71
|
+
|
|
72
|
+
const identityFlags = (await flg.getIdentityFlags(identifier)).allFlags();
|
|
73
|
+
|
|
74
|
+
expect(identityFlags[0].enabled).toBe(true);
|
|
75
|
+
expect(identityFlags[0].value).toBe('some-value');
|
|
76
|
+
expect(identityFlags[0].featureName).toBe('some_feature');
|
|
77
|
+
});
|
|
78
|
+
test('test_get_identity_flags_calls_api_when_no_local_environment_with_traits', async () => {
|
|
79
|
+
// @ts-ignore
|
|
80
|
+
fetch.mockReturnValue(Promise.resolve(new Response(identitiesJSON())));
|
|
81
|
+
const identifier = 'identifier';
|
|
82
|
+
const traits = { some_trait: 'some_value' };
|
|
83
|
+
const flg = flagsmith();
|
|
84
|
+
|
|
85
|
+
const identityFlags = (await flg.getIdentityFlags(identifier, traits)).allFlags();
|
|
86
|
+
|
|
87
|
+
expect(identityFlags[0].enabled).toBe(true);
|
|
88
|
+
expect(identityFlags[0].value).toBe('some-value');
|
|
89
|
+
expect(identityFlags[0].featureName).toBe('some_feature');
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
test('test_default_flag_is_used_when_no_environment_flags_returned', async () => {
|
|
93
|
+
// @ts-ignore
|
|
94
|
+
fetch.mockReturnValue(Promise.resolve(new Response(JSON.stringify([]))));
|
|
95
|
+
|
|
96
|
+
const defaultFlag = new DefaultFlag('some-default-value', true);
|
|
97
|
+
|
|
98
|
+
const defaultFlagHandler = (featureName: string) => defaultFlag;
|
|
99
|
+
|
|
100
|
+
const flg = new Flagsmith({
|
|
101
|
+
environmentKey: 'key',
|
|
102
|
+
defaultFlagHandler: defaultFlagHandler
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
const flags = await flg.getEnvironmentFlags();
|
|
106
|
+
const flag = flags.getFlag('some_feature');
|
|
107
|
+
expect(flag.isDefault).toBe(true);
|
|
108
|
+
expect(flag.enabled).toBe(defaultFlag.enabled);
|
|
109
|
+
expect(flag.value).toBe(defaultFlag.value);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
test('test_non_200_response_raises_flagsmith_api_error', async () => {
|
|
113
|
+
const errorResponse403 = new Response('403 Forbidden', {
|
|
114
|
+
status: 403
|
|
115
|
+
});
|
|
116
|
+
// @ts-ignore
|
|
117
|
+
fetch.mockReturnValue(Promise.resolve(errorResponse403));
|
|
118
|
+
|
|
119
|
+
const flg = new Flagsmith({
|
|
120
|
+
environmentKey: 'some'
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
await expect(flg.getEnvironmentFlags()).rejects.toThrow();
|
|
124
|
+
});
|
|
125
|
+
test('test_default_flag_is_not_used_when_environment_flags_returned', async () => {
|
|
126
|
+
// @ts-ignore
|
|
127
|
+
fetch.mockReturnValue(Promise.resolve(new Response(flagsJSON())));
|
|
128
|
+
|
|
129
|
+
const defaultFlag = new DefaultFlag('some-default-value', true);
|
|
130
|
+
|
|
131
|
+
const defaultFlagHandler = (featureName: string) => defaultFlag;
|
|
132
|
+
|
|
133
|
+
const flg = new Flagsmith({
|
|
134
|
+
environmentKey: 'key',
|
|
135
|
+
defaultFlagHandler: defaultFlagHandler
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
const flags = await flg.getEnvironmentFlags();
|
|
139
|
+
const flag = flags.getFlag('some_feature');
|
|
140
|
+
|
|
141
|
+
expect(flag.isDefault).toBe(false);
|
|
142
|
+
expect(flag.value).not.toBe(defaultFlag.value);
|
|
143
|
+
expect(flag.value).toBe('some-value');
|
|
144
|
+
});
|
|
145
|
+
test('test_default_flag_is_not_used_when_identity_flags_returned', async () => {
|
|
146
|
+
// @ts-ignore
|
|
147
|
+
fetch.mockReturnValue(Promise.resolve(new Response(identitiesJSON())));
|
|
148
|
+
|
|
149
|
+
const defaultFlag = new DefaultFlag('some-default-value', true);
|
|
150
|
+
|
|
151
|
+
const defaultFlagHandler = (featureName: string) => defaultFlag;
|
|
152
|
+
|
|
153
|
+
const flg = new Flagsmith({
|
|
154
|
+
environmentKey: 'key',
|
|
155
|
+
defaultFlagHandler: defaultFlagHandler
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
const flags = await flg.getIdentityFlags('identifier');
|
|
159
|
+
const flag = flags.getFlag('some_feature');
|
|
160
|
+
|
|
161
|
+
expect(flag.isDefault).toBe(false);
|
|
162
|
+
expect(flag.value).not.toBe(defaultFlag.value);
|
|
163
|
+
expect(flag.value).toBe('some-value');
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
test('test_default_flag_is_used_when_no_identity_flags_returned', async () => {
|
|
167
|
+
// @ts-ignore
|
|
168
|
+
fetch.mockReturnValue(Promise.resolve(new Response(JSON.stringify({ flags: [], traits: [] }))));
|
|
169
|
+
|
|
170
|
+
const defaultFlag = new DefaultFlag('some-default-value', true);
|
|
171
|
+
const defaultFlagHandler = (featureName: string) => defaultFlag;
|
|
172
|
+
|
|
173
|
+
const flg = new Flagsmith({
|
|
174
|
+
environmentKey: 'key',
|
|
175
|
+
defaultFlagHandler: defaultFlagHandler
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
const flags = await flg.getIdentityFlags('identifier');
|
|
179
|
+
const flag = flags.getFlag('some_feature');
|
|
180
|
+
|
|
181
|
+
expect(flag.isDefault).toBe(true);
|
|
182
|
+
expect(flag.value).toBe(defaultFlag.value);
|
|
183
|
+
expect(flag.enabled).toBe(defaultFlag.enabled);
|
|
184
|
+
});
|