flagsmith-nodejs 2.0.0-beta.6 → 2.0.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 (94) hide show
  1. package/.github/workflows/pull_request.yaml +37 -29
  2. package/README.md +7 -0
  3. package/example/server/api/index.js +1 -1
  4. package/flagsmith-engine/features/models.ts +28 -6
  5. package/flagsmith-engine/features/util.ts +9 -0
  6. package/flagsmith-engine/identities/models.ts +1 -1
  7. package/flagsmith-engine/index.ts +7 -5
  8. package/flagsmith-engine/organisations/models.ts +1 -1
  9. package/flagsmith-engine/segments/models.ts +21 -4
  10. package/flagsmith-engine/segments/util.ts +8 -0
  11. package/flagsmith-engine/utils/collections.ts +1 -12
  12. package/flagsmith-engine/utils/hashing/index.ts +5 -2
  13. package/flagsmith-engine/utils/index.ts +6 -2
  14. package/package.json +4 -2
  15. package/sdk/analytics.ts +0 -2
  16. package/sdk/index.ts +84 -28
  17. package/sdk/polling_manager.ts +0 -1
  18. package/sdk/types.ts +8 -0
  19. package/sdk/utils.ts +4 -5
  20. package/tests/engine/unit/{egine.test.ts → engine.test.ts} +20 -0
  21. package/tests/engine/unit/features/models.test.ts +8 -4
  22. package/tests/engine/unit/identities/identities_builders.test.ts +13 -0
  23. package/tests/engine/unit/identities/identities_models.test.ts +3 -14
  24. package/tests/engine/unit/organization/models.test.ts +1 -1
  25. package/tests/engine/unit/segments/segments_model.test.ts +22 -1
  26. package/tests/engine/unit/utils.ts +1 -1
  27. package/tests/sdk/analytics.test.ts +11 -0
  28. package/tests/sdk/flagsmith-cache.test.ts +150 -0
  29. package/tests/sdk/flagsmith-environment-flags.test.ts +197 -0
  30. package/tests/sdk/flagsmith-identity-flags.test.ts +140 -0
  31. package/tests/sdk/flagsmith.test.ts +100 -85
  32. package/tests/sdk/polling.test.ts +25 -0
  33. package/tests/sdk/utils.ts +21 -2
  34. package/tsconfig.json +1 -1
  35. package/build/flagsmith-engine/environments/integrations/models.d.ts +0 -4
  36. package/build/flagsmith-engine/environments/integrations/models.js +0 -11
  37. package/build/flagsmith-engine/environments/models.d.ts +0 -25
  38. package/build/flagsmith-engine/environments/models.js +0 -29
  39. package/build/flagsmith-engine/environments/util.d.ts +0 -3
  40. package/build/flagsmith-engine/environments/util.js +0 -21
  41. package/build/flagsmith-engine/features/constants.d.ts +0 -4
  42. package/build/flagsmith-engine/features/constants.js +0 -7
  43. package/build/flagsmith-engine/features/models.d.ts +0 -32
  44. package/build/flagsmith-engine/features/models.js +0 -102
  45. package/build/flagsmith-engine/features/util.d.ts +0 -3
  46. package/build/flagsmith-engine/features/util.js +0 -20
  47. package/build/flagsmith-engine/identities/models.d.ts +0 -15
  48. package/build/flagsmith-engine/identities/models.js +0 -112
  49. package/build/flagsmith-engine/identities/traits/models.d.ts +0 -5
  50. package/build/flagsmith-engine/identities/traits/models.js +0 -11
  51. package/build/flagsmith-engine/identities/util.d.ts +0 -4
  52. package/build/flagsmith-engine/identities/util.js +0 -46
  53. package/build/flagsmith-engine/index.d.ts +0 -8
  54. package/build/flagsmith-engine/index.js +0 -113
  55. package/build/flagsmith-engine/organisations/models.d.ts +0 -9
  56. package/build/flagsmith-engine/organisations/models.js +0 -21
  57. package/build/flagsmith-engine/organisations/util.d.ts +0 -2
  58. package/build/flagsmith-engine/organisations/util.js +0 -8
  59. package/build/flagsmith-engine/projects/models.d.ts +0 -10
  60. package/build/flagsmith-engine/projects/models.js +0 -14
  61. package/build/flagsmith-engine/projects/util.d.ts +0 -2
  62. package/build/flagsmith-engine/projects/util.js +0 -15
  63. package/build/flagsmith-engine/segments/constants.d.ts +0 -26
  64. package/build/flagsmith-engine/segments/constants.js +0 -31
  65. package/build/flagsmith-engine/segments/evaluators.d.ts +0 -6
  66. package/build/flagsmith-engine/segments/evaluators.js +0 -37
  67. package/build/flagsmith-engine/segments/models.d.ts +0 -31
  68. package/build/flagsmith-engine/segments/models.js +0 -92
  69. package/build/flagsmith-engine/segments/util.d.ts +0 -4
  70. package/build/flagsmith-engine/segments/util.js +0 -25
  71. package/build/flagsmith-engine/utils/collections.d.ts +0 -4
  72. package/build/flagsmith-engine/utils/collections.js +0 -97
  73. package/build/flagsmith-engine/utils/errors.d.ts +0 -2
  74. package/build/flagsmith-engine/utils/errors.js +0 -26
  75. package/build/flagsmith-engine/utils/hashing/index.d.ts +0 -9
  76. package/build/flagsmith-engine/utils/hashing/index.js +0 -57
  77. package/build/flagsmith-engine/utils/index.d.ts +0 -1
  78. package/build/flagsmith-engine/utils/index.js +0 -14
  79. package/build/index.d.ts +0 -1
  80. package/build/index.js +0 -11
  81. package/build/sdk/analytics.d.ts +0 -28
  82. package/build/sdk/analytics.js +0 -103
  83. package/build/sdk/errors.d.ts +0 -4
  84. package/build/sdk/errors.js +0 -34
  85. package/build/sdk/index.d.ts +0 -118
  86. package/build/sdk/index.js +0 -386
  87. package/build/sdk/models.d.ts +0 -55
  88. package/build/sdk/models.js +0 -149
  89. package/build/sdk/polling_manager.d.ts +0 -9
  90. package/build/sdk/polling_manager.js +0 -73
  91. package/build/sdk/utils.d.ts +0 -12
  92. package/build/sdk/utils.js +0 -94
  93. package/tests/engine/engine-tests/engine-test-data/data/environment_n9fbf9h3v4fFgH3U3ngWhb.json +0 -12393
  94. package/tests/engine/engine-tests/engine-test-data/readme.md +0 -30
package/sdk/utils.ts CHANGED
@@ -2,8 +2,8 @@ import fetch, { Response } from 'node-fetch';
2
2
  // @ts-ignore
3
3
  if (typeof fetch.default !== 'undefined') fetch = fetch.default;
4
4
 
5
- export function generateIdentitiesData(identifier: string, traits?: { [key: string]: any }) {
6
- const traitsGenerated = Object.entries(traits || {}).map(trait => ({
5
+ export function generateIdentitiesData(identifier: string, traits: { [key: string]: any }) {
6
+ const traitsGenerated = Object.entries(traits).map(trait => ({
7
7
  trait_key: trait[0],
8
8
  trait_value: trait[1]
9
9
  }));
@@ -18,9 +18,8 @@ export const delay = (ms: number) =>
18
18
 
19
19
  export const retryFetch = (
20
20
  url: string,
21
- fetchOptions = {},
21
+ fetchOptions: any,
22
22
  retries = 3,
23
- retryDelay = 1000,
24
23
  timeout: number
25
24
  ): Promise<Response> => {
26
25
  return new Promise((resolve, reject) => {
@@ -32,7 +31,7 @@ export const retryFetch = (
32
31
  .then(res => resolve(res))
33
32
  .catch(async err => {
34
33
  if (n > 0) {
35
- await delay(retryDelay);
34
+ await delay(1000);
36
35
  wrapper(--n);
37
36
  } else {
38
37
  reject(err);
@@ -25,6 +25,12 @@ test('test_identity_get_feature_state_without_any_override', () => {
25
25
  expect(feature_state.feature).toStrictEqual(feature1());
26
26
  });
27
27
 
28
+ test('test_identity_get_feature_state_without_any_override_no_fs', () => {
29
+ expect(() => {
30
+ getIdentityFeatureState(environment(), identity(), 'nonExistentName');
31
+ }).toThrowError('Feature State Not Found');
32
+ });
33
+
28
34
  test('test_identity_get_all_feature_states_no_segments', () => {
29
35
  const env = environment();
30
36
  const ident = identity();
@@ -61,6 +67,20 @@ test('test_identity_get_all_feature_states_with_traits', () => {
61
67
  expect(featureStates[0].getValue()).toBe('segment_override');
62
68
  });
63
69
 
70
+ test('test_identity_get_all_feature_states_with_traits_hideDisabledFlags', () => {
71
+ const trait_models = new TraitModel(segmentConditionProperty, segmentConditionStringValue);
72
+
73
+ const env = environmentWithSegmentOverride();
74
+ env.project.hideDisabledFlags = true;
75
+
76
+ const featureStates = getIdentityFeatureStates(
77
+ env,
78
+ identityInSegment(),
79
+ [trait_models]
80
+ );
81
+ expect(featureStates.length).toBe(0);
82
+ });
83
+
64
84
  test('test_environment_get_all_feature_states', () => {
65
85
  const env = environment();
66
86
  const featureStates = getEnvironmentFeatureStates(env);
@@ -7,6 +7,12 @@ import {
7
7
  } from '../../../../flagsmith-engine/features/models';
8
8
  import { feature1 } from '../utils';
9
9
 
10
+ test('test_compare_feature_model', () => {
11
+ const fm1 = new FeatureModel(1, 'a', 'test');
12
+ const fm2 = new FeatureModel(1, 'a', 'test');
13
+ expect(fm1.eq(fm2)).toBe(true);
14
+ });
15
+
10
16
  test('test_initializing_feature_state_creates_default_feature_state_uuid', () => {
11
17
  const featureState = new FeatureStateModel(feature1(), true, 1);
12
18
  expect(featureState.featurestateUUID).toBeDefined();
@@ -60,13 +66,11 @@ test('test_feature_state_get_value_mv_values', () => {
60
66
  const mvFeatureState = new FeatureStateModel(myFeature, true, 1);
61
67
  mvFeatureState.multivariateFeatureStateValues = [
62
68
  mvFeatureStateValue1,
63
- mvFeatureStateValue2
69
+ mvFeatureStateValue2,
64
70
  ];
65
71
 
66
72
  mvFeatureState.setValue(mvFeatureControlValue);
67
73
 
68
- // TODO
69
- // expect(mvFeatureState.getValue(3)).toBe(testCase[1]);
70
- expect(1).toBe(1);
74
+ expect(mvFeatureState.getValue("test")).toBe(mvFeatureValue2);
71
75
  }
72
76
  });
@@ -42,6 +42,19 @@ test('test_build_identity_model_from_dictionary_uses_identity_feature_list_for_i
42
42
  expect(identityModel.identityFeatures?.length).toBe(1);
43
43
  });
44
44
 
45
+ test('test_build_identity_model_from_dictionary_uses_identity_feature_list_for_identity_features', () => {
46
+ const identity_dict = {
47
+ id: 1,
48
+ identifier: 'test-identity',
49
+ environment_api_key: 'api-key',
50
+ created_date: '2021-08-22T06:25:23.406995Z',
51
+ };
52
+
53
+ const identityModel = buildIdentityModel(identity_dict);
54
+
55
+ expect(identityModel.identityFeatures?.length).toBe(0);
56
+ });
57
+
45
58
  test('test_build_build_identity_model_from_dict_creates_identity_uuid', () => {
46
59
  const identity_model = buildIdentityModel({
47
60
  identifier: 'test_user',
@@ -54,7 +54,7 @@ test('test_update_traits_remove_traits_with_none_value', () => {
54
54
  const trait_key = ident.identityTraits[0].traitKey;
55
55
  const trait_to_remove = new TraitModel(trait_key, undefined);
56
56
 
57
- ident.update_traits([trait_to_remove]);
57
+ ident.updateTraits([trait_to_remove]);
58
58
 
59
59
  expect(ident.identityTraits.length).toBe(0);
60
60
  });
@@ -66,7 +66,7 @@ test('test_update_identity_traits_updates_trait_value', () => {
66
66
  const traitValue = 'updated_trait_value';
67
67
  const traitToUpdate = new TraitModel(traitKey, traitValue);
68
68
 
69
- identity.update_traits([traitToUpdate]);
69
+ identity.updateTraits([traitToUpdate]);
70
70
 
71
71
  expect(identity.identityTraits.length).toBe(1);
72
72
  expect(identity.identityTraits[0]).toBe(traitToUpdate);
@@ -77,7 +77,7 @@ test('test_update_traits_adds_new_traits', () => {
77
77
 
78
78
  const newTrait = new TraitModel('new_key', 'foobar');
79
79
 
80
- identity.update_traits([newTrait]);
80
+ identity.updateTraits([newTrait]);
81
81
 
82
82
  expect(identity.identityTraits.length).toBe(2);
83
83
  expect(identity.identityTraits).toContain(newTrait);
@@ -92,14 +92,3 @@ test('test_append_feature_state', () => {
92
92
 
93
93
  expect(ident.identityFeatures).toContain(fs1);
94
94
  });
95
-
96
- test('test_appending_feature_states_raises_duplicate_feature_state_if_fs_for_the_feature_already_exists', () => {
97
- const ident = identityInSegment();
98
-
99
- const fs1 = new FeatureStateModel(feature1(), false, 1);
100
- const fs2 = new FeatureStateModel(feature1(), true, 1);
101
- ident.identityFeatures.push(fs1);
102
- expect(() => {
103
- ident.identityFeatures.push(fs2);
104
- }).toThrowError();
105
- });
@@ -8,5 +8,5 @@ test('Test builder', () => {
8
8
  stop_serving_flags: false,
9
9
  id: 13
10
10
  });
11
- expect(model.unique_slug).toBe('13-Flagsmith');
11
+ expect(model.uniqueSlug).toBe('13-Flagsmith');
12
12
  });
@@ -61,7 +61,28 @@ const conditionMatchCases: [string, string | number | boolean, string, boolean][
61
61
  [CONDITION_OPERATORS.NOT_CONTAINS, 'bar', 'bar', false],
62
62
  [CONDITION_OPERATORS.NOT_CONTAINS, 'bar', 'baz', true],
63
63
  [CONDITION_OPERATORS.REGEX, 'foo', '[a-z]+', true],
64
- [CONDITION_OPERATORS.REGEX, 'FOO', '[a-z]+', false]
64
+ [CONDITION_OPERATORS.REGEX, 'FOO', '[a-z]+', false],
65
+ [CONDITION_OPERATORS.EQUAL, "1.0.0", "1.0.0:semver", true],
66
+ [CONDITION_OPERATORS.EQUAL, "1.0.0", "1.0.0:semver", true],
67
+ [CONDITION_OPERATORS.EQUAL, "1.0.0", "1.0.1:semver", false],
68
+ [CONDITION_OPERATORS.NOT_EQUAL, "1.0.0", "1.0.0:semver", false],
69
+ [CONDITION_OPERATORS.NOT_EQUAL, "1.0.0", "1.0.1:semver", true],
70
+ [CONDITION_OPERATORS.GREATER_THAN, "1.0.1", "1.0.0:semver", true],
71
+ [CONDITION_OPERATORS.GREATER_THAN, "1.0.0", "1.0.0-beta:semver", true],
72
+ [CONDITION_OPERATORS.GREATER_THAN, "1.0.1", "1.2.0:semver", false],
73
+ [CONDITION_OPERATORS.GREATER_THAN, "1.0.1", "1.0.1:semver", false],
74
+ [CONDITION_OPERATORS.GREATER_THAN, "1.2.4", "1.2.3-pre.2+build.4:semver", true],
75
+ [CONDITION_OPERATORS.LESS_THAN, "1.0.0", "1.0.1:semver", true],
76
+ [CONDITION_OPERATORS.LESS_THAN, "1.0.0", "1.0.0:semver", false],
77
+ [CONDITION_OPERATORS.LESS_THAN, "1.0.1", "1.0.0:semver", false],
78
+ [CONDITION_OPERATORS.LESS_THAN, "1.0.0-rc.2", "1.0.0-rc.3:semver", true],
79
+ [CONDITION_OPERATORS.GREATER_THAN_INCLUSIVE, "1.0.1", "1.0.0:semver", true],
80
+ [CONDITION_OPERATORS.GREATER_THAN_INCLUSIVE, "1.0.1", "1.2.0:semver", false],
81
+ [CONDITION_OPERATORS.GREATER_THAN_INCLUSIVE, "1.0.1", "1.0.1:semver", true],
82
+ [CONDITION_OPERATORS.LESS_THAN_INCLUSIVE, "1.0.0", "1.0.1:semver", true],
83
+ [CONDITION_OPERATORS.LESS_THAN_INCLUSIVE, "1.0.0", "1.0.0:semver", true],
84
+ [CONDITION_OPERATORS.LESS_THAN_INCLUSIVE, "1.0.1", "1.0.0:semver", false],
85
+ ['BAD_OP', 'a', 'a', false]
65
86
  ];
66
87
 
67
88
  test('test_segment_condition_matches_trait_value', () => {
@@ -87,7 +87,7 @@ export function getEnvironmentFeatureStateForFeatureByName(
87
87
  feature_name: string
88
88
  ): FeatureStateModel | undefined {
89
89
  const features = environment.featureStates.filter(fs => fs.feature.name === feature_name);
90
- return features.length > 0 ? features[0] : undefined;
90
+ return features[0];
91
91
  }
92
92
 
93
93
  export function getEnvironmentFeatureStateForFeature(
@@ -36,6 +36,17 @@ test('test_analytics_processor_flush_post_request_data_match_ananlytics_data', a
36
36
  });
37
37
  });
38
38
 
39
+ jest.useFakeTimers()
40
+ test('test_analytics_processor_flush_post_request_data_match_ananlytics_data_test', async () => {
41
+ const aP = analyticsProcessor();
42
+ aP.trackFeature(1);
43
+ setTimeout(() => {
44
+ aP.trackFeature(2);
45
+ expect(fetch).toHaveBeenCalledTimes(1);
46
+ }, 15000);
47
+ jest.runOnlyPendingTimers();
48
+ });
49
+
39
50
  test('test_analytics_processor_flush_early_exit_if_analytics_data_is_empty', async () => {
40
51
  const aP = analyticsProcessor();
41
52
  await aP.flush();
@@ -0,0 +1,150 @@
1
+ import fetch, { Headers } from 'node-fetch';
2
+ import { environmentJSON, environmentModel, flagsJSON, flagsmith, identitiesJSON, TestCache } from './utils';
3
+
4
+ jest.mock('node-fetch');
5
+ jest.mock('../../sdk/polling_manager');
6
+
7
+ const { Response } = jest.requireActual('node-fetch');
8
+
9
+ beforeEach(() => {
10
+ // @ts-ignore
11
+ jest.clearAllMocks();
12
+ });
13
+
14
+ test('test_wrong_cache_interface_throws_an_error', async () => {
15
+ const cache = {
16
+ set: () => { },
17
+ get: () => { },
18
+ };
19
+
20
+ expect(() => { const flg = flagsmith({ cache }); }).toThrow();
21
+ });
22
+
23
+ test('test_empty_cache_not_read_but_populated', async () => {
24
+ // @ts-ignore
25
+ fetch.mockReturnValue(Promise.resolve(new Response(flagsJSON())));
26
+
27
+ const cache = new TestCache();
28
+ const set = jest.spyOn(cache, 'set');
29
+
30
+ const flg = flagsmith({ cache });
31
+ const allFlags = await (await flg.getEnvironmentFlags()).allFlags();
32
+
33
+ expect(set).toBeCalled();
34
+ expect(await cache.has('flags')).toBe(true);
35
+
36
+ expect(fetch).toBeCalledTimes(1);
37
+ expect(allFlags[0].enabled).toBe(true);
38
+ expect(allFlags[0].value).toBe('some-value');
39
+ expect(allFlags[0].featureName).toBe('some_feature');
40
+ });
41
+
42
+ test('test_api_not_called_when_cache_present', async () => {
43
+ // @ts-ignore
44
+ fetch.mockReturnValue(Promise.resolve(new Response(flagsJSON())));
45
+
46
+ const cache = new TestCache();
47
+ const set = jest.spyOn(cache, 'set');
48
+
49
+ const flg = flagsmith({ cache });
50
+ await (await flg.getEnvironmentFlags()).allFlags();
51
+ const allFlags = await (await flg.getEnvironmentFlags()).allFlags();
52
+
53
+ expect(set).toBeCalled();
54
+ expect(await cache.has('flags')).toBe(true);
55
+
56
+ expect(fetch).toBeCalledTimes(1);
57
+ expect(allFlags[0].enabled).toBe(true);
58
+ expect(allFlags[0].value).toBe('some-value');
59
+ expect(allFlags[0].featureName).toBe('some_feature');
60
+ });
61
+
62
+ test('test_api_called_twice_when_no_cache', async () => {
63
+ // @ts-ignore
64
+ fetch.mockReturnValue(Promise.resolve(new Response(flagsJSON())));
65
+
66
+ const flg = flagsmith();
67
+ await (await flg.getEnvironmentFlags()).allFlags();
68
+ // @ts-ignore
69
+ fetch.mockReturnValue(Promise.resolve(new Response(flagsJSON())));
70
+ const allFlags = await (await flg.getEnvironmentFlags()).allFlags();
71
+
72
+ expect(fetch).toBeCalledTimes(2);
73
+ expect(allFlags[0].enabled).toBe(true);
74
+ expect(allFlags[0].value).toBe('some-value');
75
+ expect(allFlags[0].featureName).toBe('some_feature');
76
+ });
77
+
78
+ test('test_get_environment_flags_uses_local_environment_when_available', async () => {
79
+ // @ts-ignore
80
+ fetch.mockReturnValue(Promise.resolve(new Response(flagsJSON())));
81
+
82
+ const cache = new TestCache();
83
+ const set = jest.spyOn(cache, 'set');
84
+
85
+ const flg = flagsmith({ cache });
86
+ const model = environmentModel(JSON.parse(environmentJSON()));
87
+ flg.environment = model;
88
+
89
+ const allFlags = await (await flg.getEnvironmentFlags()).allFlags();
90
+
91
+ expect(set).toBeCalled();
92
+ expect(fetch).toBeCalledTimes(0);
93
+ expect(allFlags[0].enabled).toBe(model.featureStates[0].enabled);
94
+ expect(allFlags[0].value).toBe(model.featureStates[0].getValue());
95
+ expect(allFlags[0].featureName).toBe(model.featureStates[0].feature.name);
96
+ });
97
+
98
+ test('test_cache_used_for_identity_flags', async () => {
99
+ // @ts-ignore
100
+ fetch.mockReturnValue(Promise.resolve(new Response(identitiesJSON())));
101
+
102
+ const cache = new TestCache();
103
+ const set = jest.spyOn(cache, 'set');
104
+
105
+ const identifier = 'identifier';
106
+ const traits = { some_trait: 'some_value' };
107
+ const flg = flagsmith({ cache });
108
+
109
+ (await flg.getIdentityFlags(identifier, traits)).allFlags();
110
+ const identityFlags = (await flg.getIdentityFlags(identifier, traits)).allFlags();
111
+
112
+ expect(set).toBeCalled();
113
+ expect(await cache.has('flags-identifier')).toBe(true);
114
+
115
+ expect(fetch).toBeCalledTimes(1);
116
+
117
+ expect(identityFlags[0].enabled).toBe(true);
118
+ expect(identityFlags[0].value).toBe('some-value');
119
+ expect(identityFlags[0].featureName).toBe('some_feature');
120
+ });
121
+
122
+ test('test_cache_used_for_identity_flags_local_evaluation', async () => {
123
+ // @ts-ignore
124
+ fetch.mockReturnValue(Promise.resolve(new Response(environmentJSON())));
125
+
126
+ const cache = new TestCache();
127
+ const set = jest.spyOn(cache, 'set');
128
+
129
+ const identifier = 'identifier';
130
+ const traits = { some_trait: 'some_value' };
131
+ const flg = flagsmith({
132
+ cache,
133
+ environmentKey: 'ser.key',
134
+ enableLocalEvaluation: true,
135
+ });
136
+
137
+ (await flg.getIdentityFlags(identifier, traits)).allFlags();
138
+ const identityFlags = (await flg.getIdentityFlags(identifier, traits)).allFlags();
139
+
140
+ expect(set).toBeCalled();
141
+ expect(await cache.has('flags-identifier')).toBe(true);
142
+
143
+ expect(fetch).toBeCalledTimes(1);
144
+
145
+ expect(identityFlags[0].enabled).toBe(true);
146
+ expect(identityFlags[0].value).toBe('some-value');
147
+ expect(identityFlags[0].featureName).toBe('some_feature');
148
+ });
149
+
150
+ test('test_cache_used_for_all_flags', async () => { });
@@ -0,0 +1,197 @@
1
+ import Flagsmith from '../../sdk';
2
+ import fetch from 'node-fetch';
3
+ import { environmentJSON, environmentModel, flagsJSON, flagsmith, identitiesJSON } from './utils';
4
+ import { DefaultFlag } from '../../sdk/models';
5
+
6
+ jest.mock('node-fetch');
7
+ jest.mock('../../sdk/polling_manager');
8
+ const { Response } = jest.requireActual('node-fetch');
9
+
10
+ beforeEach(() => {
11
+ // @ts-ignore
12
+ jest.clearAllMocks();
13
+ });
14
+
15
+ test('test_get_environment_flags_calls_api_when_no_local_environment', async () => {
16
+ // @ts-ignore
17
+ fetch.mockReturnValue(Promise.resolve(new Response(flagsJSON())));
18
+
19
+ const flg = flagsmith();
20
+ const allFlags = await (await flg.getEnvironmentFlags()).allFlags();
21
+
22
+ expect(fetch).toBeCalledTimes(1);
23
+ expect(allFlags[0].enabled).toBe(true);
24
+ expect(allFlags[0].value).toBe('some-value');
25
+ expect(allFlags[0].featureName).toBe('some_feature');
26
+ });
27
+
28
+ test('test_get_environment_flags_uses_local_environment_when_available', async () => {
29
+ // @ts-ignore
30
+ fetch.mockReturnValue(Promise.resolve(new Response(flagsJSON())));
31
+
32
+ const flg = flagsmith();
33
+ const model = environmentModel(JSON.parse(environmentJSON()));
34
+ flg.environment = model;
35
+
36
+ const allFlags = await (await flg.getEnvironmentFlags()).allFlags();
37
+ expect(fetch).toBeCalledTimes(0);
38
+ expect(allFlags[0].enabled).toBe(model.featureStates[0].enabled);
39
+ expect(allFlags[0].value).toBe(model.featureStates[0].getValue());
40
+ expect(allFlags[0].featureName).toBe(model.featureStates[0].feature.name);
41
+ });
42
+
43
+ test('test_default_flag_is_used_when_no_environment_flags_returned', async () => {
44
+ // @ts-ignore
45
+ fetch.mockReturnValue(Promise.resolve(new Response(JSON.stringify([]))));
46
+
47
+ const defaultFlag = new DefaultFlag('some-default-value', true);
48
+
49
+ const defaultFlagHandler = (featureName: string) => defaultFlag;
50
+
51
+ const flg = new Flagsmith({
52
+ environmentKey: 'key',
53
+ defaultFlagHandler: defaultFlagHandler,
54
+ customHeaders: {
55
+ 'X-Test-Header': '1',
56
+ }
57
+ });
58
+
59
+ const flags = await flg.getEnvironmentFlags();
60
+ const flag = flags.getFlag('some_feature');
61
+ expect(flag.isDefault).toBe(true);
62
+ expect(flag.enabled).toBe(defaultFlag.enabled);
63
+ expect(flag.value).toBe(defaultFlag.value);
64
+ });
65
+
66
+ test('test_analytics_processor_tracks_flags', async () => {
67
+ // @ts-ignore
68
+ fetch.mockReturnValue(Promise.resolve(new Response(flagsJSON())));
69
+
70
+ const defaultFlag = new DefaultFlag('some-default-value', true);
71
+
72
+ const defaultFlagHandler = (featureName: string) => defaultFlag;
73
+
74
+ const flg = new Flagsmith({
75
+ environmentKey: 'key',
76
+ defaultFlagHandler: defaultFlagHandler,
77
+ enableAnalytics: true,
78
+ });
79
+
80
+ const flags = await flg.getEnvironmentFlags();
81
+ const flag = flags.getFlag('some_feature');
82
+
83
+ expect(flag.isDefault).toBe(false);
84
+ expect(flag.enabled).toBe(true);
85
+ expect(flag.value).toBe('some-value');
86
+ });
87
+
88
+ test('test_getFeatureValue', async () => {
89
+ // @ts-ignore
90
+ fetch.mockReturnValue(Promise.resolve(new Response(flagsJSON())));
91
+
92
+ const defaultFlag = new DefaultFlag('some-default-value', true);
93
+
94
+ const defaultFlagHandler = (featureName: string) => defaultFlag;
95
+
96
+ const flg = new Flagsmith({
97
+ environmentKey: 'key',
98
+ defaultFlagHandler: defaultFlagHandler,
99
+ enableAnalytics: true,
100
+ });
101
+
102
+ const flags = await flg.getEnvironmentFlags();
103
+ const featureValue = flags.getFeatureValue('some_feature');
104
+
105
+ expect(featureValue).toBe('some-value');
106
+ });
107
+
108
+ test('test_throws_when_no_default_flag_handler_after_multiple_API_errors', async () => {
109
+ fetch
110
+ // @ts-ignore
111
+ .mockRejectedValue(new Error('Error during fetching the API response'));
112
+
113
+ const flg = new Flagsmith({
114
+ environmentKey: 'key',
115
+ });
116
+
117
+ await expect(async () => {
118
+ const flags = await flg.getEnvironmentFlags();
119
+ const flag = flags.getFlag('some_feature');
120
+ }).rejects.toThrow('Error during fetching the API response');
121
+ });
122
+
123
+ test('test_non_200_response_raises_flagsmith_api_error', async () => {
124
+ const errorResponse403 = new Response('403 Forbidden', {
125
+ status: 403
126
+ });
127
+ // @ts-ignore
128
+ fetch.mockReturnValue(Promise.resolve(errorResponse403));
129
+
130
+ const flg = new Flagsmith({
131
+ environmentKey: 'some'
132
+ });
133
+
134
+ await expect(flg.getEnvironmentFlags()).rejects.toThrow();
135
+ });
136
+ test('test_default_flag_is_not_used_when_environment_flags_returned', async () => {
137
+ // @ts-ignore
138
+ fetch.mockReturnValue(Promise.resolve(new Response(flagsJSON())));
139
+
140
+ const defaultFlag = new DefaultFlag('some-default-value', true);
141
+
142
+ const defaultFlagHandler = (featureName: string) => defaultFlag;
143
+
144
+ const flg = new Flagsmith({
145
+ environmentKey: 'key',
146
+ defaultFlagHandler: defaultFlagHandler
147
+ });
148
+
149
+ const flags = await flg.getEnvironmentFlags();
150
+ const flag = flags.getFlag('some_feature');
151
+
152
+ expect(flag.isDefault).toBe(false);
153
+ expect(flag.value).not.toBe(defaultFlag.value);
154
+ expect(flag.value).toBe('some-value');
155
+ });
156
+
157
+ test('test_default_flag_is_used_when_bad_api_response_happens', async () => {
158
+ // @ts-ignore
159
+ fetch.mockReturnValue(Promise.resolve(new Response('bad-data')));
160
+
161
+ const defaultFlag = new DefaultFlag('some-default-value', true);
162
+
163
+ const defaultFlagHandler = (featureName: string) => defaultFlag;
164
+
165
+ const flg = new Flagsmith({
166
+ environmentKey: 'key',
167
+ defaultFlagHandler: defaultFlagHandler
168
+ });
169
+
170
+ const flags = await flg.getEnvironmentFlags();
171
+ const flag = flags.getFlag('some_feature');
172
+
173
+ expect(flag.isDefault).toBe(true);
174
+ expect(flag.value).toBe(defaultFlag.value);
175
+ });
176
+
177
+ test('test_local_evaluation', async () => {
178
+ // @ts-ignore
179
+ fetch.mockReturnValue(Promise.resolve(new Response(environmentJSON())));
180
+
181
+ const defaultFlag = new DefaultFlag('some-default-value', true);
182
+
183
+ const defaultFlagHandler = (featureName: string) => defaultFlag;
184
+
185
+ const flg = new Flagsmith({
186
+ environmentKey: 'ser.key',
187
+ enableLocalEvaluation: true,
188
+ defaultFlagHandler: defaultFlagHandler
189
+ });
190
+
191
+ const flags = await flg.getEnvironmentFlags();
192
+ const flag = flags.getFlag('some_feature');
193
+
194
+ expect(flag.isDefault).toBe(false);
195
+ expect(flag.value).not.toBe(defaultFlag.value);
196
+ expect(flag.value).toBe('some-value');
197
+ });