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.
Files changed (127) hide show
  1. package/.github/workflows/pull_request.yaml +33 -0
  2. package/.gitmodules +3 -0
  3. package/.husky/pre-commit +6 -0
  4. package/.idea/flagsmith-nodejs-client.iml +12 -0
  5. package/.idea/modules.xml +8 -0
  6. package/.idea/vcs.xml +6 -0
  7. package/.prettierignore +1 -0
  8. package/.prettierrc.js +9 -0
  9. package/CONTRIBUTING.md +5 -4
  10. package/{LICENCE.md → LICENCE} +5 -6
  11. package/README.md +1 -1
  12. package/build/flagsmith-engine/environments/integrations/models.d.ts +4 -0
  13. package/build/flagsmith-engine/environments/integrations/models.js +8 -0
  14. package/build/flagsmith-engine/environments/models.d.ts +25 -0
  15. package/build/flagsmith-engine/environments/models.js +40 -0
  16. package/build/flagsmith-engine/environments/util.d.ts +3 -0
  17. package/build/flagsmith-engine/environments/util.js +19 -0
  18. package/build/flagsmith-engine/features/constants.d.ts +4 -0
  19. package/build/flagsmith-engine/features/constants.js +7 -0
  20. package/build/flagsmith-engine/features/models.d.ts +32 -0
  21. package/build/flagsmith-engine/features/models.js +85 -0
  22. package/build/flagsmith-engine/features/util.d.ts +3 -0
  23. package/build/flagsmith-engine/features/util.js +20 -0
  24. package/build/flagsmith-engine/identities/models.d.ts +15 -0
  25. package/build/flagsmith-engine/identities/models.js +47 -0
  26. package/build/flagsmith-engine/identities/traits/models.d.ts +5 -0
  27. package/build/flagsmith-engine/identities/traits/models.js +12 -0
  28. package/build/flagsmith-engine/identities/util.d.ts +4 -0
  29. package/build/flagsmith-engine/identities/util.js +22 -0
  30. package/build/flagsmith-engine/index.d.ts +8 -0
  31. package/build/flagsmith-engine/index.js +60 -0
  32. package/build/flagsmith-engine/organisations/models.d.ts +9 -0
  33. package/build/flagsmith-engine/organisations/models.js +21 -0
  34. package/build/flagsmith-engine/organisations/util.d.ts +2 -0
  35. package/build/flagsmith-engine/organisations/util.js +8 -0
  36. package/build/flagsmith-engine/projects/models.d.ts +10 -0
  37. package/build/flagsmith-engine/projects/models.js +18 -0
  38. package/build/flagsmith-engine/projects/util.d.ts +2 -0
  39. package/build/flagsmith-engine/projects/util.js +15 -0
  40. package/build/flagsmith-engine/segments/constants.d.ts +26 -0
  41. package/build/flagsmith-engine/segments/constants.js +31 -0
  42. package/build/flagsmith-engine/segments/evaluators.d.ts +6 -0
  43. package/build/flagsmith-engine/segments/evaluators.js +29 -0
  44. package/build/flagsmith-engine/segments/models.d.ts +31 -0
  45. package/build/flagsmith-engine/segments/models.js +83 -0
  46. package/build/flagsmith-engine/segments/util.d.ts +4 -0
  47. package/build/flagsmith-engine/segments/util.js +23 -0
  48. package/build/flagsmith-engine/utils/collections.d.ts +4 -0
  49. package/build/flagsmith-engine/utils/collections.js +16 -0
  50. package/build/flagsmith-engine/utils/hashing/index.d.ts +1 -0
  51. package/build/flagsmith-engine/utils/hashing/index.js +56 -0
  52. package/build/flagsmith-engine/utils/index.d.ts +1 -0
  53. package/build/flagsmith-engine/utils/index.js +14 -0
  54. package/build/index.d.ts +2 -0
  55. package/build/index.js +7 -0
  56. package/build/sdk/analytics.d.ts +28 -0
  57. package/build/sdk/analytics.js +81 -0
  58. package/build/sdk/errors.d.ts +4 -0
  59. package/build/sdk/errors.js +9 -0
  60. package/build/sdk/index.d.ts +99 -0
  61. package/build/sdk/index.js +221 -0
  62. package/build/sdk/models.d.ts +55 -0
  63. package/build/sdk/models.js +102 -0
  64. package/build/sdk/polling_manager.d.ts +9 -0
  65. package/build/sdk/polling_manager.js +31 -0
  66. package/build/sdk/utils.d.ts +12 -0
  67. package/build/sdk/utils.js +45 -0
  68. package/example/README.md +8 -14
  69. package/example/package-lock.json +12 -12
  70. package/example/package.json +4 -4
  71. package/example/server/api/index.js +19 -22
  72. package/example/server/index.js +4 -9
  73. package/flagsmith-engine/environments/integrations/models.ts +4 -0
  74. package/flagsmith-engine/environments/models.ts +50 -0
  75. package/flagsmith-engine/environments/util.ts +29 -0
  76. package/flagsmith-engine/features/constants.ts +4 -0
  77. package/flagsmith-engine/features/models.ts +105 -0
  78. package/flagsmith-engine/features/util.ts +38 -0
  79. package/flagsmith-engine/identities/models.ts +60 -0
  80. package/flagsmith-engine/identities/traits/models.ts +9 -0
  81. package/flagsmith-engine/identities/util.ts +30 -0
  82. package/flagsmith-engine/index.ts +92 -0
  83. package/flagsmith-engine/organisations/models.ts +25 -0
  84. package/flagsmith-engine/organisations/util.ts +11 -0
  85. package/flagsmith-engine/projects/models.ts +23 -0
  86. package/flagsmith-engine/projects/util.ts +18 -0
  87. package/flagsmith-engine/segments/constants.ts +31 -0
  88. package/flagsmith-engine/segments/evaluators.ts +72 -0
  89. package/flagsmith-engine/segments/models.ts +103 -0
  90. package/flagsmith-engine/segments/util.ts +29 -0
  91. package/flagsmith-engine/utils/collections.ts +14 -0
  92. package/flagsmith-engine/utils/hashing/index.ts +57 -0
  93. package/flagsmith-engine/utils/index.ts +10 -0
  94. package/index.ts +3 -0
  95. package/jest.config.js +5 -0
  96. package/package.json +24 -3
  97. package/sdk/analytics.ts +88 -0
  98. package/sdk/errors.ts +2 -0
  99. package/sdk/index.ts +282 -0
  100. package/sdk/models.ts +143 -0
  101. package/sdk/polling_manager.ts +31 -0
  102. package/sdk/utils.ts +45 -0
  103. package/tests/engine/e2e/engine.test.ts +51 -0
  104. package/tests/engine/engine-tests/engine-test-data/data/environment_n9fbf9h3v4fFgH3U3ngWhb.json +12393 -0
  105. package/tests/engine/engine-tests/engine-test-data/readme.md +30 -0
  106. package/tests/engine/unit/egine.test.ts +96 -0
  107. package/tests/engine/unit/environments/builder.test.ts +148 -0
  108. package/tests/engine/unit/environments/models.test.ts +49 -0
  109. package/tests/engine/unit/features/models.test.ts +72 -0
  110. package/tests/engine/unit/identities/identities_builders.test.ts +85 -0
  111. package/tests/engine/unit/identities/identities_models.test.ts +105 -0
  112. package/tests/engine/unit/organization/models.test.ts +12 -0
  113. package/tests/engine/unit/segments/segments_model.test.ts +101 -0
  114. package/tests/engine/unit/segments/util.ts +151 -0
  115. package/tests/engine/unit/utils.ts +114 -0
  116. package/tests/index.js +0 -0
  117. package/tests/sdk/analytics.test.ts +43 -0
  118. package/tests/sdk/data/environment.json +33 -0
  119. package/tests/sdk/data/flags.json +20 -0
  120. package/tests/sdk/data/identities.json +29 -0
  121. package/tests/sdk/flagsmith.test.ts +184 -0
  122. package/tests/sdk/polling.test.ts +34 -0
  123. package/tests/sdk/utils.ts +39 -0
  124. package/tsconfig.json +19 -0
  125. package/flagsmith-core.js +0 -238
  126. package/index.d.ts +0 -78
  127. 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
+ });