flagsmith-nodejs 2.0.0-beta.6 → 2.0.0-beta.7

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 (49) hide show
  1. package/.github/workflows/pull_request.yaml +37 -29
  2. package/README.md +7 -0
  3. package/build/flagsmith-engine/features/models.d.ts +0 -1
  4. package/build/flagsmith-engine/features/models.js +0 -3
  5. package/build/flagsmith-engine/identities/models.d.ts +1 -1
  6. package/build/flagsmith-engine/identities/models.js +1 -1
  7. package/build/flagsmith-engine/index.js +1 -1
  8. package/build/flagsmith-engine/organisations/models.d.ts +1 -1
  9. package/build/flagsmith-engine/organisations/models.js +1 -1
  10. package/build/flagsmith-engine/segments/models.js +0 -3
  11. package/build/flagsmith-engine/utils/collections.d.ts +0 -1
  12. package/build/flagsmith-engine/utils/collections.js +0 -71
  13. package/build/flagsmith-engine/utils/hashing/index.js +5 -2
  14. package/build/sdk/analytics.js +0 -1
  15. package/build/sdk/index.d.ts +5 -0
  16. package/build/sdk/index.js +152 -54
  17. package/build/sdk/polling_manager.js +0 -1
  18. package/build/sdk/types.d.ts +7 -0
  19. package/build/sdk/types.js +2 -0
  20. package/example/server/api/index.js +1 -1
  21. package/flagsmith-engine/features/models.ts +0 -4
  22. package/flagsmith-engine/identities/models.ts +1 -1
  23. package/flagsmith-engine/index.ts +1 -1
  24. package/flagsmith-engine/organisations/models.ts +1 -1
  25. package/flagsmith-engine/segments/models.ts +0 -2
  26. package/flagsmith-engine/utils/collections.ts +1 -12
  27. package/flagsmith-engine/utils/hashing/index.ts +5 -2
  28. package/package.json +2 -2
  29. package/sdk/analytics.ts +0 -2
  30. package/sdk/index.ts +83 -26
  31. package/sdk/polling_manager.ts +0 -1
  32. package/sdk/types.ts +8 -0
  33. package/tests/engine/unit/{egine.test.ts → engine.test.ts} +20 -0
  34. package/tests/engine/unit/features/models.test.ts +7 -3
  35. package/tests/engine/unit/identities/identities_builders.test.ts +13 -0
  36. package/tests/engine/unit/identities/identities_models.test.ts +3 -14
  37. package/tests/engine/unit/organization/models.test.ts +1 -1
  38. package/tests/engine/unit/segments/segments_model.test.ts +2 -1
  39. package/tests/engine/unit/utils.ts +1 -1
  40. package/tests/sdk/analytics.test.ts +11 -0
  41. package/tests/sdk/flagsmith-cache.test.ts +150 -0
  42. package/tests/sdk/flagsmith-environment-flags.test.ts +197 -0
  43. package/tests/sdk/flagsmith-identity-flags.test.ts +140 -0
  44. package/tests/sdk/flagsmith.test.ts +100 -85
  45. package/tests/sdk/polling.test.ts +25 -0
  46. package/tests/sdk/utils.ts +21 -2
  47. package/tsconfig.json +1 -1
  48. package/tests/engine/engine-tests/engine-test-data/data/environment_n9fbf9h3v4fFgH3U3ngWhb.json +0 -12393
  49. package/tests/engine/engine-tests/engine-test-data/readme.md +0 -30
@@ -1,8 +1,10 @@
1
1
  import Flagsmith from '../../sdk';
2
2
  import { EnvironmentDataPollingManager } from '../../sdk/polling_manager';
3
- import fetch, { Headers } from 'node-fetch';
3
+ import fetch from 'node-fetch';
4
4
  import { environmentJSON, environmentModel, flagsJSON, flagsmith, identitiesJSON } from './utils';
5
5
  import { DefaultFlag } from '../../sdk/models';
6
+ import { delay } from '../../sdk/utils';
7
+ import { EnvironmentModel } from '../../flagsmith-engine/environments/models';
6
8
 
7
9
  jest.mock('node-fetch');
8
10
  jest.mock('../../sdk/polling_manager');
@@ -23,6 +25,17 @@ test('test_flagsmith_starts_polling_manager_on_init_if_enabled', () => {
23
25
  expect(EnvironmentDataPollingManager).toBeCalled();
24
26
  });
25
27
 
28
+ test('test_flagsmith_local_evaluation_key_required', () => {
29
+ // @ts-ignore
30
+ fetch.mockReturnValue(Promise.resolve(new Response(environmentJSON())));
31
+ console.error = jest.fn();
32
+ new Flagsmith({
33
+ environmentKey: 'bad.key',
34
+ enableLocalEvaluation: true
35
+ });
36
+ expect(console.error).toBeCalled();
37
+ });
38
+
26
39
  test('test_update_environment_sets_environment', async () => {
27
40
  // @ts-ignore
28
41
  fetch.mockReturnValue(Promise.resolve(new Response(environmentJSON())));
@@ -55,62 +68,45 @@ test('test_get_identity_segments', async () => {
55
68
  const segments2 = await flg.getIdentitySegments('user', { age: 41 });
56
69
  expect(segments2.length).toEqual(0);
57
70
  });
58
- test('test_get_environment_flags_calls_api_when_no_local_environment', async () => {
59
- // @ts-ignore
60
- fetch.mockReturnValue(Promise.resolve(new Response(flagsJSON())));
61
71
 
62
- const flg = flagsmith();
63
- const allFlags = await (await flg.getEnvironmentFlags()).allFlags();
64
72
 
65
- expect(fetch).toBeCalledTimes(1);
66
- expect(allFlags[0].enabled).toBe(true);
67
- expect(allFlags[0].value).toBe('some-value');
68
- expect(allFlags[0].featureName).toBe('some_feature');
69
- });
70
- test('test_get_environment_flags_uses_local_environment_when_available', async () => {
73
+ test('test_get_identity_segments_empty_without_local_eval', async () => {
71
74
  // @ts-ignore
72
- fetch.mockReturnValue(Promise.resolve(new Response(flagsJSON())));
75
+ fetch.mockReturnValue(Promise.resolve(new Response(environmentJSON())));
76
+ const flg = new Flagsmith({
77
+ environmentKey: 'ser.key',
78
+ enableLocalEvaluation: false
79
+ });
80
+ const segments = await flg.getIdentitySegments('user', { age: 21 });
81
+ expect(segments.length).toBe(0);
82
+ });
73
83
 
74
- const flg = flagsmith();
75
- const model = environmentModel(JSON.parse(environmentJSON()));
76
- flg.environment = model;
77
84
 
78
- const allFlags = await (await flg.getEnvironmentFlags()).allFlags();
79
- expect(fetch).toBeCalledTimes(0);
80
- expect(allFlags[0].enabled).toBe(model.featureStates[0].enabled);
81
- expect(allFlags[0].value).toBe(model.featureStates[0].getValue());
82
- expect(allFlags[0].featureName).toBe(model.featureStates[0].feature.name);
83
- });
84
- test('test_get_identity_flags_calls_api_when_no_local_environment_no_traits', async () => {
85
- // @ts-ignore
86
- fetch.mockReturnValue(Promise.resolve(new Response(identitiesJSON())));
87
- const identifier = 'identifier';
88
85
 
89
- const flg = flagsmith();
90
86
 
91
- const identityFlags = (await flg.getIdentityFlags(identifier)).allFlags();
92
87
 
93
- expect(identityFlags[0].enabled).toBe(true);
94
- expect(identityFlags[0].value).toBe('some-value');
95
- expect(identityFlags[0].featureName).toBe('some_feature');
96
- });
97
- test('test_get_identity_flags_calls_api_when_no_local_environment_with_traits', async () => {
88
+
89
+ test('test_update_environment_uses_req_when_inited', async () => {
98
90
  // @ts-ignore
99
- fetch.mockReturnValue(Promise.resolve(new Response(identitiesJSON())));
91
+ fetch.mockReturnValue(Promise.resolve(new Response(environmentJSON())));
100
92
  const identifier = 'identifier';
101
- const traits = { some_trait: 'some_value' };
102
- const flg = flagsmith();
103
93
 
104
- const identityFlags = (await flg.getIdentityFlags(identifier, traits)).allFlags();
94
+ const flg = flagsmith({
95
+ environmentKey: 'ser.key',
96
+ enableLocalEvaluation: true,
97
+
98
+ });
99
+
100
+ delay(400);
105
101
 
106
- expect(identityFlags[0].enabled).toBe(true);
107
- expect(identityFlags[0].value).toBe('some-value');
108
- expect(identityFlags[0].featureName).toBe('some_feature');
102
+ expect(async () => {
103
+ await flg.updateEnvironment();
104
+ }).not.toThrow();
109
105
  });
110
106
 
111
- test('test_default_flag_is_used_when_no_environment_flags_returned', async () => {
107
+ test('test_isFeatureEnabled_environment', async () => {
112
108
  // @ts-ignore
113
- fetch.mockReturnValue(Promise.resolve(new Response(JSON.stringify([]))));
109
+ fetch.mockReturnValue(Promise.resolve(new Response(flagsJSON())));
114
110
 
115
111
  const defaultFlag = new DefaultFlag('some-default-value', true);
116
112
 
@@ -118,33 +114,36 @@ test('test_default_flag_is_used_when_no_environment_flags_returned', async () =>
118
114
 
119
115
  const flg = new Flagsmith({
120
116
  environmentKey: 'key',
121
- defaultFlagHandler: defaultFlagHandler
117
+ defaultFlagHandler: defaultFlagHandler,
118
+ enableAnalytics: true,
122
119
  });
123
120
 
124
121
  const flags = await flg.getEnvironmentFlags();
125
- const flag = flags.getFlag('some_feature');
126
- expect(flag.isDefault).toBe(true);
127
- expect(flag.enabled).toBe(defaultFlag.enabled);
128
- expect(flag.value).toBe(defaultFlag.value);
129
- });
122
+ const featureValue = flags.isFeatureEnabled('some_feature');
130
123
 
131
- test('test_non_200_response_raises_flagsmith_api_error', async () => {
132
- const errorResponse403 = new Response('403 Forbidden', {
133
- status: 403
134
- });
135
- // @ts-ignore
136
- fetch.mockReturnValue(Promise.resolve(errorResponse403));
124
+ expect(featureValue).toBe(true);
125
+ });
137
126
 
127
+ test('test_fetch_recovers_after_single_API_error', async () => {
128
+ fetch
129
+ // @ts-ignore
130
+ .mockRejectedValueOnce(new Error('Error during fetching the API response'))
131
+ .mockReturnValue(Promise.resolve(new Response(flagsJSON())));
138
132
  const flg = new Flagsmith({
139
- environmentKey: 'some'
133
+ environmentKey: 'key',
140
134
  });
141
135
 
142
- await expect(flg.getEnvironmentFlags()).rejects.toThrow();
136
+ const flags = await flg.getEnvironmentFlags();
137
+ const flag = flags.getFlag('some_feature');
138
+ expect(flag.isDefault).toBe(false);
139
+ expect(flag.enabled).toBe(true);
140
+ expect(flag.value).toBe('some-value');
143
141
  });
144
- test('test_default_flag_is_not_used_when_environment_flags_returned', async () => {
145
- // @ts-ignore
146
- fetch.mockReturnValue(Promise.resolve(new Response(flagsJSON())));
147
142
 
143
+ test('test_default_flag_used_after_multiple_API_errors', async () => {
144
+ fetch
145
+ // @ts-ignore
146
+ .mockRejectedValue(new Error('Error during fetching the API response'));
148
147
  const defaultFlag = new DefaultFlag('some-default-value', true);
149
148
 
150
149
  const defaultFlagHandler = (featureName: string) => defaultFlag;
@@ -156,48 +155,64 @@ test('test_default_flag_is_not_used_when_environment_flags_returned', async () =
156
155
 
157
156
  const flags = await flg.getEnvironmentFlags();
158
157
  const flag = flags.getFlag('some_feature');
159
-
160
- expect(flag.isDefault).toBe(false);
161
- expect(flag.value).not.toBe(defaultFlag.value);
162
- expect(flag.value).toBe('some-value');
158
+ expect(flag.isDefault).toBe(true);
159
+ expect(flag.enabled).toBe(defaultFlag.enabled);
160
+ expect(flag.value).toBe(defaultFlag.value);
163
161
  });
164
- test('test_default_flag_is_not_used_when_identity_flags_returned', async () => {
165
- // @ts-ignore
166
- fetch.mockReturnValue(Promise.resolve(new Response(identitiesJSON())));
167
162
 
168
- const defaultFlag = new DefaultFlag('some-default-value', true);
169
163
 
170
- const defaultFlagHandler = (featureName: string) => defaultFlag;
164
+
165
+ test('test_throws_when_no_identity_flags_returned_due_to_error', async () => {
166
+ // @ts-ignore
167
+ fetch.mockReturnValue(Promise.resolve(new Response('bad data')));
168
+
171
169
 
172
170
  const flg = new Flagsmith({
173
171
  environmentKey: 'key',
174
- defaultFlagHandler: defaultFlagHandler
175
172
  });
176
173
 
177
- const flags = await flg.getIdentityFlags('identifier');
178
- const flag = flags.getFlag('some_feature');
174
+ expect(async () => {
175
+ await flg.getIdentityFlags('identifier');
176
+ }).rejects.toThrow();
179
177
 
180
- expect(flag.isDefault).toBe(false);
181
- expect(flag.value).not.toBe(defaultFlag.value);
182
- expect(flag.value).toBe('some-value');
183
178
  });
184
179
 
185
- test('test_default_flag_is_used_when_no_identity_flags_returned', async () => {
180
+ test('test onEnvironmentChange is called when provided', async () => {
186
181
  // @ts-ignore
187
- fetch.mockReturnValue(Promise.resolve(new Response(JSON.stringify({ flags: [], traits: [] }))));
182
+ fetch.mockReturnValue(Promise.resolve(new Response(environmentJSON())));
188
183
 
189
- const defaultFlag = new DefaultFlag('some-default-value', true);
190
- const defaultFlagHandler = (featureName: string) => defaultFlag;
184
+ const callback = {
185
+ callback: (e: Error | null, result: EnvironmentModel) => { }
186
+ };
187
+ const callbackSpy = jest.spyOn(callback, 'callback');
191
188
 
192
189
  const flg = new Flagsmith({
193
- environmentKey: 'key',
194
- defaultFlagHandler: defaultFlagHandler
190
+ environmentKey: 'ser.key',
191
+ enableLocalEvaluation: true,
192
+ onEnvironmentChange: callback.callback,
195
193
  });
196
194
 
197
- const flags = await flg.getIdentityFlags('identifier');
198
- const flag = flags.getFlag('some_feature');
195
+ await delay(200);
199
196
 
200
- expect(flag.isDefault).toBe(true);
201
- expect(flag.value).toBe(defaultFlag.value);
202
- expect(flag.enabled).toBe(defaultFlag.enabled);
197
+ expect(callbackSpy).toBeCalled();
203
198
  });
199
+
200
+ test('test onEnvironmentChange is called after error', async () => {
201
+ // @ts-ignore
202
+ fetch.mockReturnValue(Promise.resolve(new Response('aaa')));
203
+
204
+ const callback = {
205
+ callback: (e: Error | null, result: EnvironmentModel) => { }
206
+ };
207
+ const callbackSpy = jest.spyOn(callback, 'callback');
208
+
209
+ const flg = new Flagsmith({
210
+ environmentKey: 'ser.key',
211
+ enableLocalEvaluation: true,
212
+ onEnvironmentChange: callback.callback,
213
+ });
214
+
215
+ await delay(200);
216
+
217
+ expect(callbackSpy).toBeCalled();
218
+ });
@@ -9,6 +9,16 @@ beforeEach(() => {
9
9
  Flagsmith.mockClear();
10
10
  });
11
11
 
12
+ test('test_polling_manager_correctly_stops_if_never_started', async () => {
13
+ const flagsmith = new Flagsmith({
14
+ environmentKey: 'key'
15
+ });
16
+
17
+ const pollingManager = new EnvironmentDataPollingManager(flagsmith, 0.1);
18
+ pollingManager.stop();
19
+ expect(flagsmith.updateEnvironment).not.toHaveBeenCalled();
20
+ });
21
+
12
22
  test('test_polling_manager_calls_update_environment_on_start', async () => {
13
23
  const flagsmith = new Flagsmith({
14
24
  environmentKey: 'key'
@@ -21,6 +31,21 @@ test('test_polling_manager_calls_update_environment_on_start', async () => {
21
31
  expect(flagsmith.updateEnvironment).toHaveBeenCalled();
22
32
  });
23
33
 
34
+ test('test_polling_manager_handles_double_start', async () => {
35
+ const flagsmith = new Flagsmith({
36
+ environmentKey: 'key'
37
+ });
38
+
39
+ const pollingManager = new EnvironmentDataPollingManager(flagsmith, 0.1);
40
+ pollingManager.start();
41
+ await delay(100);
42
+ pollingManager.start();
43
+ await delay(500);
44
+ pollingManager.stop();
45
+ expect(flagsmith.updateEnvironment).toHaveBeenCalled();
46
+ });
47
+
48
+
24
49
  test('test_polling_manager_calls_update_environment_on_each_refresh', async () => {
25
50
  const flagsmith = new Flagsmith({
26
51
  environmentKey: 'key'
@@ -2,9 +2,27 @@ import { readFileSync } from 'fs';
2
2
  import { buildEnvironmentModel } from '../../flagsmith-engine/environments/util';
3
3
  import { AnalyticsProcessor } from '../../sdk/analytics';
4
4
  import Flagsmith from '../../sdk';
5
+ import { FlagsmithCache } from '../../sdk/types';
6
+ import { Flag, Flags } from '../../sdk/models';
5
7
 
6
8
  const DATA_DIR = __dirname + '/data/';
7
9
 
10
+ export class TestCache implements FlagsmithCache {
11
+ cache: Record<string, Flags> = {};
12
+
13
+ async get(name: string): Promise<Flags> {
14
+ return this.cache[name];
15
+ }
16
+
17
+ async has(name: string): Promise<boolean> {
18
+ return !!this.cache[name];
19
+ }
20
+
21
+ async set(name: string, value: Flags) {
22
+ this.cache[name] = value;
23
+ }
24
+ }
25
+
8
26
  export function analyticsProcessor() {
9
27
  return new AnalyticsProcessor({
10
28
  environmentKey: 'test-key',
@@ -16,9 +34,10 @@ export function apiKey(): string {
16
34
  return 'sometestfakekey';
17
35
  }
18
36
 
19
- export function flagsmith() {
37
+ export function flagsmith(params = {}) {
20
38
  return new Flagsmith({
21
- environmentKey: apiKey()
39
+ environmentKey: apiKey(),
40
+ ...params,
22
41
  });
23
42
  }
24
43
 
package/tsconfig.json CHANGED
@@ -7,7 +7,7 @@
7
7
  "compilerOptions": {
8
8
  "outDir": "./build",
9
9
  "target": "es5", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
10
- "downlevelIteration": true,
10
+ "downlevelIteration": true, // Used to allow typescript to interpret Object.entries
11
11
  /* Modules */
12
12
  "module": "commonjs", /* Specify what module code is generated. */
13
13
  "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables `allowSyntheticDefaultImports` for type compatibility. */