flagsmith-nodejs 6.0.1 → 6.2.0
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/conventional-commit.yml +29 -0
- package/.github/workflows/pull_request.yaml +1 -1
- package/.github/workflows/release-please.yml +18 -0
- package/.gitmodules +1 -0
- package/.husky/pre-commit +0 -0
- package/.prettierignore +2 -1
- package/.prettierrc.cjs +9 -1
- package/.release-please-manifest.json +1 -0
- package/CHANGELOG.md +552 -0
- package/CODEOWNERS +1 -0
- package/README.md +2 -3
- package/build/cjs/flagsmith-engine/features/util.js +3 -3
- package/build/cjs/flagsmith-engine/index.d.ts +1 -1
- package/build/cjs/flagsmith-engine/index.js +2 -1
- package/build/cjs/flagsmith-engine/segments/models.js +7 -7
- package/build/cjs/flagsmith-engine/utils/hashing/index.js +1 -1
- package/build/cjs/index.d.ts +4 -4
- package/build/cjs/index.js +3 -1
- package/build/cjs/sdk/analytics.d.ts +1 -1
- package/build/cjs/sdk/analytics.js +3 -1
- package/build/cjs/sdk/index.d.ts +5 -5
- package/build/cjs/sdk/index.js +48 -10
- package/build/cjs/sdk/models.d.ts +25 -0
- package/build/cjs/sdk/models.js +25 -0
- package/build/cjs/sdk/types.d.ts +14 -4
- package/build/cjs/sdk/utils.d.ts +5 -4
- package/build/cjs/sdk/utils.js +16 -3
- package/build/esm/flagsmith-engine/features/models.js +1 -1
- package/build/esm/flagsmith-engine/features/util.js +3 -3
- package/build/esm/flagsmith-engine/index.d.ts +1 -1
- package/build/esm/flagsmith-engine/index.js +1 -1
- package/build/esm/flagsmith-engine/segments/models.js +7 -7
- package/build/esm/flagsmith-engine/utils/hashing/index.js +2 -2
- package/build/esm/flagsmith-engine/utils/index.js +1 -1
- package/build/esm/index.d.ts +4 -4
- package/build/esm/index.js +3 -3
- package/build/esm/sdk/analytics.d.ts +1 -1
- package/build/esm/sdk/analytics.js +3 -1
- package/build/esm/sdk/index.d.ts +5 -5
- package/build/esm/sdk/index.js +48 -11
- package/build/esm/sdk/models.d.ts +25 -0
- package/build/esm/sdk/models.js +25 -0
- package/build/esm/sdk/types.d.ts +14 -4
- package/build/esm/sdk/utils.d.ts +5 -4
- package/build/esm/sdk/utils.js +14 -2
- package/flagsmith-engine/environments/util.ts +2 -2
- package/flagsmith-engine/features/models.ts +1 -1
- package/flagsmith-engine/features/util.ts +14 -14
- package/flagsmith-engine/identities/models.ts +1 -1
- package/flagsmith-engine/index.ts +1 -1
- package/flagsmith-engine/segments/evaluators.ts +2 -3
- package/flagsmith-engine/segments/models.ts +25 -15
- package/flagsmith-engine/utils/hashing/index.ts +3 -3
- package/flagsmith-engine/utils/index.ts +4 -2
- package/index.ts +19 -22
- package/package.json +1 -1
- package/release-please-config.json +62 -0
- package/sdk/analytics.ts +10 -6
- package/sdk/index.ts +91 -28
- package/sdk/models.ts +25 -0
- package/sdk/offline_handlers.ts +1 -1
- package/sdk/types.ts +17 -8
- package/sdk/utils.ts +21 -8
- package/tests/engine/e2e/engine.test.ts +2 -4
- package/tests/engine/unit/engine.test.ts +1 -6
- package/tests/engine/unit/features/models.test.ts +2 -2
- package/tests/engine/unit/identities/identities_builders.test.ts +1 -1
- package/tests/engine/unit/segments/segment_evaluators.test.ts +52 -23
- package/tests/engine/unit/segments/segments_model.test.ts +35 -37
- package/tests/engine/unit/utils/utils.test.ts +28 -30
- package/tests/sdk/analytics.test.ts +30 -26
- package/tests/sdk/flagsmith-cache.test.ts +84 -76
- package/tests/sdk/flagsmith-environment-flags.test.ts +121 -93
- package/tests/sdk/flagsmith-identity-flags.test.ts +155 -149
- package/tests/sdk/flagsmith.test.ts +202 -43
- package/tests/sdk/offline-handlers.test.ts +32 -32
- package/tests/sdk/polling.test.ts +0 -1
- package/tests/sdk/utils.ts +26 -18
- package/vitest.config.ts +10 -15
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
badFetch
|
|
10
10
|
} from './utils.js';
|
|
11
11
|
import { DefaultFlag, Flags } from '../../sdk/models.js';
|
|
12
|
-
import { delay } from '../../sdk/utils.js';
|
|
12
|
+
import { delay, getUserAgent } from '../../sdk/utils.js';
|
|
13
13
|
import { EnvironmentModel } from '../../flagsmith-engine/environments/models.js';
|
|
14
14
|
import { BaseOfflineHandler } from '../../sdk/offline_handlers.js';
|
|
15
15
|
import { Agent } from 'undici';
|
|
@@ -29,26 +29,180 @@ test('test_flagsmith_local_evaluation_key_required', () => {
|
|
|
29
29
|
environmentKey: 'bad.key',
|
|
30
30
|
enableLocalEvaluation: true
|
|
31
31
|
});
|
|
32
|
-
}).toThrow('Using local evaluation requires a server-side environment key')
|
|
32
|
+
}).toThrow('Using local evaluation requires a server-side environment key');
|
|
33
33
|
});
|
|
34
34
|
|
|
35
35
|
test('test_update_environment_sets_environment', async () => {
|
|
36
36
|
const flg = flagsmith({
|
|
37
|
-
environmentKey: 'ser.key'
|
|
37
|
+
environmentKey: 'ser.key'
|
|
38
38
|
});
|
|
39
39
|
const model = environmentModel(JSON.parse(environmentJSON));
|
|
40
40
|
expect(await flg.getEnvironment()).toStrictEqual(model);
|
|
41
41
|
});
|
|
42
42
|
|
|
43
|
+
test('test_update_environment_handles_paginated_document', async () => {
|
|
44
|
+
type EnvDocumentMockResponse = {
|
|
45
|
+
responseHeader: string | null;
|
|
46
|
+
page: any;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const createMockFetch = (pages: EnvDocumentMockResponse[]) => {
|
|
50
|
+
let callCount = 0;
|
|
51
|
+
return vi.fn((url: string, options?: RequestInit) => {
|
|
52
|
+
if (url.includes('/environment-document')) {
|
|
53
|
+
const document = envDocumentMockResponse[callCount];
|
|
54
|
+
if (document) {
|
|
55
|
+
callCount++;
|
|
56
|
+
|
|
57
|
+
const responseHeaders: Record<string, string> = {};
|
|
58
|
+
|
|
59
|
+
if (document.responseHeader) {
|
|
60
|
+
responseHeaders['Link'] = `<${document.responseHeader}>; rel="next"`;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return Promise.resolve(
|
|
64
|
+
new Response(JSON.stringify(document.page), {
|
|
65
|
+
status: 200,
|
|
66
|
+
headers: responseHeaders
|
|
67
|
+
})
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return Promise.resolve(new Response('unknown url ' + url, { status: 404 }));
|
|
72
|
+
});
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const envDocumentMockResponse: EnvDocumentMockResponse[] = [
|
|
76
|
+
{
|
|
77
|
+
responseHeader: '/api/v1/environment-document?page=2',
|
|
78
|
+
page: {
|
|
79
|
+
id: 1,
|
|
80
|
+
api_key: 'test-key',
|
|
81
|
+
project: {
|
|
82
|
+
id: 1,
|
|
83
|
+
name: 'test',
|
|
84
|
+
organisation: {
|
|
85
|
+
id: 1,
|
|
86
|
+
name: 'Test Org',
|
|
87
|
+
feature_analytics: false,
|
|
88
|
+
persist_trait_data: true,
|
|
89
|
+
stop_serving_flags: false
|
|
90
|
+
},
|
|
91
|
+
hide_disabled_flags: false,
|
|
92
|
+
segments: []
|
|
93
|
+
},
|
|
94
|
+
feature_states: [
|
|
95
|
+
{
|
|
96
|
+
feature_state_value: 'first_page_feature_state',
|
|
97
|
+
multivariate_feature_state_values: [],
|
|
98
|
+
django_id: 81027,
|
|
99
|
+
feature: {
|
|
100
|
+
id: 15058,
|
|
101
|
+
type: 'STANDARD',
|
|
102
|
+
name: 'string_feature'
|
|
103
|
+
},
|
|
104
|
+
enabled: false
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
feature_state_value: 'second_page_feature_state',
|
|
108
|
+
multivariate_feature_state_values: [],
|
|
109
|
+
django_id: 81027,
|
|
110
|
+
feature: {
|
|
111
|
+
id: 15058,
|
|
112
|
+
type: 'STANDARD',
|
|
113
|
+
name: 'string_feature'
|
|
114
|
+
},
|
|
115
|
+
enabled: false
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
feature_state_value: 'third_page_feature_state',
|
|
119
|
+
multivariate_feature_state_values: [],
|
|
120
|
+
django_id: 81027,
|
|
121
|
+
feature: {
|
|
122
|
+
id: 15058,
|
|
123
|
+
type: 'STANDARD',
|
|
124
|
+
name: 'string_feature'
|
|
125
|
+
},
|
|
126
|
+
enabled: false
|
|
127
|
+
}
|
|
128
|
+
],
|
|
129
|
+
identity_overrides: [{ id: 1, identifier: 'user1' }]
|
|
130
|
+
}
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
responseHeader: '/api/v1/environment-document?page=3',
|
|
134
|
+
page: {
|
|
135
|
+
api_key: 'test-key',
|
|
136
|
+
project: {
|
|
137
|
+
id: 1,
|
|
138
|
+
name: 'test',
|
|
139
|
+
organisation: {
|
|
140
|
+
id: 1,
|
|
141
|
+
name: 'Test Org',
|
|
142
|
+
feature_analytics: false,
|
|
143
|
+
persist_trait_data: true,
|
|
144
|
+
stop_serving_flags: false
|
|
145
|
+
},
|
|
146
|
+
hide_disabled_flags: false,
|
|
147
|
+
segments: []
|
|
148
|
+
},
|
|
149
|
+
feature_states: [],
|
|
150
|
+
identity_overrides: [{ id: 2, identifier: 'user2' }]
|
|
151
|
+
}
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
responseHeader: null,
|
|
155
|
+
page: {
|
|
156
|
+
api_key: 'test-key',
|
|
157
|
+
project: {
|
|
158
|
+
id: 1,
|
|
159
|
+
name: 'test',
|
|
160
|
+
organisation: {
|
|
161
|
+
id: 1,
|
|
162
|
+
name: 'Test Org',
|
|
163
|
+
feature_analytics: false,
|
|
164
|
+
persist_trait_data: true,
|
|
165
|
+
stop_serving_flags: false
|
|
166
|
+
},
|
|
167
|
+
hide_disabled_flags: false,
|
|
168
|
+
segments: []
|
|
169
|
+
},
|
|
170
|
+
feature_states: [],
|
|
171
|
+
identity_overrides: [{ id: 2, identifier: 'user3' }]
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
];
|
|
175
|
+
|
|
176
|
+
const flg = new Flagsmith({
|
|
177
|
+
environmentKey: 'ser.key',
|
|
178
|
+
enableLocalEvaluation: true,
|
|
179
|
+
fetch: createMockFetch(envDocumentMockResponse)
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
const environment = await flg.getEnvironment();
|
|
183
|
+
|
|
184
|
+
expect(environment.identityOverrides).toHaveLength(3);
|
|
185
|
+
expect(environment.identityOverrides[0].identifier).toBe('user1');
|
|
186
|
+
expect(environment.identityOverrides[1].identifier).toBe('user2');
|
|
187
|
+
expect(environment.identityOverrides[2].identifier).toBe('user3');
|
|
188
|
+
expect(environment.featureStates).toHaveLength(3);
|
|
189
|
+
expect(environment.featureStates[0].getValue()).toBe('first_page_feature_state');
|
|
190
|
+
expect(environment.featureStates[1].getValue()).toBe('second_page_feature_state');
|
|
191
|
+
expect(environment.featureStates[2].getValue()).toBe('third_page_feature_state');
|
|
192
|
+
expect(environment.project.name).toBe('test');
|
|
193
|
+
expect(environment.project.organisation.name).toBe('Test Org');
|
|
194
|
+
expect(environment.project.organisation.id).toBe(1);
|
|
195
|
+
});
|
|
196
|
+
|
|
43
197
|
test('test_set_agent_options', async () => {
|
|
44
|
-
const agent = new Agent({})
|
|
198
|
+
const agent = new Agent({});
|
|
45
199
|
|
|
46
200
|
fetch.mockImplementationOnce((url, options) => {
|
|
47
201
|
//@ts-ignore I give up
|
|
48
202
|
if (options.dispatcher !== agent) {
|
|
49
|
-
throw new Error(
|
|
203
|
+
throw new Error('Agent has not been set on retry fetch');
|
|
50
204
|
}
|
|
51
|
-
return Promise.resolve(new Response(environmentJSON))
|
|
205
|
+
return Promise.resolve(new Response(environmentJSON));
|
|
52
206
|
});
|
|
53
207
|
|
|
54
208
|
const flg = flagsmith({
|
|
@@ -69,7 +223,6 @@ test('test_get_identity_segments', async () => {
|
|
|
69
223
|
expect(segments2.length).toEqual(0);
|
|
70
224
|
});
|
|
71
225
|
|
|
72
|
-
|
|
73
226
|
test('test_get_identity_segments_empty_without_local_eval', async () => {
|
|
74
227
|
const flg = new Flagsmith({
|
|
75
228
|
environmentKey: 'ser.key',
|
|
@@ -82,7 +235,7 @@ test('test_get_identity_segments_empty_without_local_eval', async () => {
|
|
|
82
235
|
test('test_update_environment_uses_req_when_inited', async () => {
|
|
83
236
|
const flg = flagsmith({
|
|
84
237
|
environmentKey: 'ser.key',
|
|
85
|
-
enableLocalEvaluation: true
|
|
238
|
+
enableLocalEvaluation: true
|
|
86
239
|
});
|
|
87
240
|
|
|
88
241
|
delay(400);
|
|
@@ -100,7 +253,7 @@ test('test_isFeatureEnabled_environment', async () => {
|
|
|
100
253
|
const flg = new Flagsmith({
|
|
101
254
|
environmentKey: 'key',
|
|
102
255
|
defaultFlagHandler: defaultFlagHandler,
|
|
103
|
-
enableAnalytics: true
|
|
256
|
+
enableAnalytics: true
|
|
104
257
|
});
|
|
105
258
|
|
|
106
259
|
const flags = await flg.getEnvironmentFlags();
|
|
@@ -110,9 +263,9 @@ test('test_isFeatureEnabled_environment', async () => {
|
|
|
110
263
|
});
|
|
111
264
|
|
|
112
265
|
test('test_fetch_recovers_after_single_API_error', async () => {
|
|
113
|
-
fetch.mockRejectedValueOnce('Error during fetching the API response')
|
|
266
|
+
fetch.mockRejectedValueOnce('Error during fetching the API response');
|
|
114
267
|
const flg = flagsmith({
|
|
115
|
-
environmentKey: 'key'
|
|
268
|
+
environmentKey: 'key'
|
|
116
269
|
});
|
|
117
270
|
|
|
118
271
|
const flags = await flg.getEnvironmentFlags();
|
|
@@ -132,7 +285,7 @@ test.each([
|
|
|
132
285
|
enableLocalEvaluation,
|
|
133
286
|
environmentKey,
|
|
134
287
|
defaultFlagHandler: () => new DefaultFlag('some-default-value', true),
|
|
135
|
-
fetch: badFetch
|
|
288
|
+
fetch: badFetch
|
|
136
289
|
});
|
|
137
290
|
const flags = await flg.getEnvironmentFlags();
|
|
138
291
|
const flag = flags.getFlag('some_feature');
|
|
@@ -144,9 +297,9 @@ test.each([
|
|
|
144
297
|
|
|
145
298
|
test('default flag handler used when timeout occurs', async () => {
|
|
146
299
|
fetch.mockImplementation(async (...args) => {
|
|
147
|
-
const forever = new Promise(() => {})
|
|
148
|
-
await forever
|
|
149
|
-
throw new Error('waited forever')
|
|
300
|
+
const forever = new Promise(() => {});
|
|
301
|
+
await forever;
|
|
302
|
+
throw new Error('waited forever');
|
|
150
303
|
});
|
|
151
304
|
|
|
152
305
|
const defaultFlag = new DefaultFlag('some-default-value', true);
|
|
@@ -156,7 +309,7 @@ test('default flag handler used when timeout occurs', async () => {
|
|
|
156
309
|
const flg = flagsmith({
|
|
157
310
|
environmentKey: 'key',
|
|
158
311
|
defaultFlagHandler: defaultFlagHandler,
|
|
159
|
-
requestTimeoutSeconds: 0.0001
|
|
312
|
+
requestTimeoutSeconds: 0.0001
|
|
160
313
|
});
|
|
161
314
|
|
|
162
315
|
const flags = await flg.getEnvironmentFlags();
|
|
@@ -164,35 +317,32 @@ test('default flag handler used when timeout occurs', async () => {
|
|
|
164
317
|
expect(flag.isDefault).toBe(true);
|
|
165
318
|
expect(flag.enabled).toBe(defaultFlag.enabled);
|
|
166
319
|
expect(flag.value).toBe(defaultFlag.value);
|
|
167
|
-
})
|
|
320
|
+
});
|
|
168
321
|
|
|
169
322
|
test('request timeout uses default if not provided', async () => {
|
|
170
|
-
|
|
171
323
|
const flg = new Flagsmith({
|
|
172
|
-
environmentKey: 'key'
|
|
324
|
+
environmentKey: 'key'
|
|
173
325
|
});
|
|
174
326
|
|
|
175
327
|
expect(flg.requestTimeoutMs).toBe(10000);
|
|
176
|
-
})
|
|
328
|
+
});
|
|
177
329
|
|
|
178
330
|
test('test_throws_when_no_identityFlags_returned_due_to_error', async () => {
|
|
179
331
|
const flg = flagsmith({
|
|
180
332
|
environmentKey: 'key',
|
|
181
|
-
fetch: badFetch
|
|
333
|
+
fetch: badFetch
|
|
182
334
|
});
|
|
183
335
|
|
|
184
|
-
await expect(async () => await flg.getIdentityFlags('identifier'))
|
|
185
|
-
.rejects
|
|
186
|
-
.toThrow();
|
|
336
|
+
await expect(async () => await flg.getIdentityFlags('identifier')).rejects.toThrow();
|
|
187
337
|
});
|
|
188
338
|
|
|
189
339
|
test('test onEnvironmentChange is called when provided', async () => {
|
|
190
|
-
const callback = vi.fn()
|
|
340
|
+
const callback = vi.fn();
|
|
191
341
|
|
|
192
342
|
const flg = new Flagsmith({
|
|
193
343
|
environmentKey: 'ser.key',
|
|
194
344
|
enableLocalEvaluation: true,
|
|
195
|
-
onEnvironmentChange: callback
|
|
345
|
+
onEnvironmentChange: callback
|
|
196
346
|
});
|
|
197
347
|
|
|
198
348
|
fetch.mockRejectedValueOnce(new Error('API error'));
|
|
@@ -209,7 +359,7 @@ test('test onEnvironmentChange is called after error', async () => {
|
|
|
209
359
|
environmentKey: 'ser.key',
|
|
210
360
|
enableLocalEvaluation: true,
|
|
211
361
|
onEnvironmentChange: callback,
|
|
212
|
-
fetch: badFetch
|
|
362
|
+
fetch: badFetch
|
|
213
363
|
});
|
|
214
364
|
await flg.updateEnvironment();
|
|
215
365
|
expect(callback).toHaveBeenCalled();
|
|
@@ -217,15 +367,17 @@ test('test onEnvironmentChange is called after error', async () => {
|
|
|
217
367
|
|
|
218
368
|
test('getIdentityFlags throws error if identifier is empty string', async () => {
|
|
219
369
|
const flg = flagsmith({
|
|
220
|
-
environmentKey: 'key'
|
|
370
|
+
environmentKey: 'key'
|
|
221
371
|
});
|
|
222
372
|
|
|
223
|
-
await expect(flg.getIdentityFlags('')).rejects.toThrow(
|
|
224
|
-
|
|
373
|
+
await expect(flg.getIdentityFlags('')).rejects.toThrow(
|
|
374
|
+
'`identifier` argument is missing or invalid.'
|
|
375
|
+
);
|
|
376
|
+
});
|
|
225
377
|
|
|
226
378
|
test('getIdentitySegments throws error if identifier is empty string', async () => {
|
|
227
379
|
const flg = flagsmith({
|
|
228
|
-
environmentKey: 'key'
|
|
380
|
+
environmentKey: 'key'
|
|
229
381
|
});
|
|
230
382
|
|
|
231
383
|
await expect(flg.getIdentitySegments('')).rejects.toThrow(
|
|
@@ -254,15 +406,13 @@ test('offline_mode', async () => {
|
|
|
254
406
|
expect(flag.enabled).toBe(true);
|
|
255
407
|
expect(flag.value).toBe('offline-value');
|
|
256
408
|
|
|
257
|
-
|
|
258
|
-
const identityFlags: Flags = await flg.getIdentityFlags("identity");
|
|
409
|
+
const identityFlags: Flags = await flg.getIdentityFlags('identity');
|
|
259
410
|
flag = identityFlags.getFlag('some_feature');
|
|
260
411
|
expect(flag.isDefault).toBe(false);
|
|
261
412
|
expect(flag.enabled).toBe(true);
|
|
262
413
|
expect(flag.value).toBe('offline-value');
|
|
263
414
|
});
|
|
264
415
|
|
|
265
|
-
|
|
266
416
|
test('test_flagsmith_uses_offline_handler_if_set_and_no_api_response', async () => {
|
|
267
417
|
// Given
|
|
268
418
|
const environment: EnvironmentModel = environmentModel(JSON.parse(offlineEnvironmentJSON));
|
|
@@ -281,14 +431,13 @@ test('test_flagsmith_uses_offline_handler_if_set_and_no_api_response', async ()
|
|
|
281
431
|
vi.spyOn(flg, 'getEnvironmentFlags');
|
|
282
432
|
vi.spyOn(flg, 'getIdentityFlags');
|
|
283
433
|
|
|
284
|
-
|
|
285
434
|
flg.environmentFlagsUrl = 'http://some.flagsmith.com/api/v1/environment-flags';
|
|
286
435
|
flg.identitiesUrl = 'http://some.flagsmith.com/api/v1/identities';
|
|
287
436
|
|
|
288
437
|
// Mock a 500 Internal Server Error response
|
|
289
438
|
const errorResponse = new Response(null, {
|
|
290
439
|
status: 500,
|
|
291
|
-
statusText: 'Internal Server Error'
|
|
440
|
+
statusText: 'Internal Server Error'
|
|
292
441
|
});
|
|
293
442
|
|
|
294
443
|
fetch.mockResolvedValue(errorResponse);
|
|
@@ -318,25 +467,28 @@ test('cannot use offline mode without offline handler', () => {
|
|
|
318
467
|
|
|
319
468
|
test('cannot use both default handler and offline handler', () => {
|
|
320
469
|
// When and Then
|
|
321
|
-
expect(() =>
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
470
|
+
expect(() =>
|
|
471
|
+
flagsmith({
|
|
472
|
+
offlineHandler: new BaseOfflineHandler(),
|
|
473
|
+
defaultFlagHandler: () => new DefaultFlag('foo', true)
|
|
474
|
+
})
|
|
475
|
+
).toThrowError('ValueError: Cannot use both defaultFlagHandler and offlineHandler.');
|
|
325
476
|
});
|
|
326
477
|
|
|
327
478
|
test('cannot create Flagsmith client in remote evaluation without API key', () => {
|
|
328
479
|
// When and Then
|
|
329
|
-
expect(() => new Flagsmith({ environmentKey: '' })).toThrowError(
|
|
480
|
+
expect(() => new Flagsmith({ environmentKey: '' })).toThrowError(
|
|
481
|
+
'ValueError: environmentKey is required.'
|
|
482
|
+
);
|
|
330
483
|
});
|
|
331
484
|
|
|
332
|
-
|
|
333
485
|
test('test_localEvaluation_true__identity_overrides_evaluated', async () => {
|
|
334
486
|
const flg = flagsmith({
|
|
335
487
|
environmentKey: 'ser.key',
|
|
336
488
|
enableLocalEvaluation: true
|
|
337
489
|
});
|
|
338
490
|
|
|
339
|
-
await flg.updateEnvironment()
|
|
491
|
+
await flg.updateEnvironment();
|
|
340
492
|
const flags = await flg.getIdentityFlags('overridden-id');
|
|
341
493
|
expect(flags.getFeatureValue('some_feature')).toEqual('some-overridden-value');
|
|
342
494
|
});
|
|
@@ -360,3 +512,10 @@ test('getIdentityFlags succeeds if initial fetch failed then succeeded', async (
|
|
|
360
512
|
const flags2 = await flg.getIdentityFlags('test-user');
|
|
361
513
|
expect(flags2.isFeatureEnabled('some_feature')).toBe(true);
|
|
362
514
|
});
|
|
515
|
+
|
|
516
|
+
test('get_user_agent_extracts_version_from_package_json', async () => {
|
|
517
|
+
const userAgent = getUserAgent();
|
|
518
|
+
const packageJson = require('../../package.json');
|
|
519
|
+
|
|
520
|
+
expect(userAgent).toBe(`flagsmith-nodejs-sdk/${packageJson.version}`);
|
|
521
|
+
});
|
|
@@ -1,32 +1,32 @@
|
|
|
1
|
-
import * as fs from 'fs';
|
|
2
|
-
import { LocalFileHandler } from '../../sdk/offline_handlers.js';
|
|
3
|
-
import { EnvironmentModel } from '../../flagsmith-engine/index.js';
|
|
4
|
-
|
|
5
|
-
import * as offlineEnvironment from
|
|
6
|
-
|
|
7
|
-
vi.mock('fs')
|
|
8
|
-
|
|
9
|
-
const offlineEnvironmentString = JSON.stringify(offlineEnvironment)
|
|
10
|
-
|
|
11
|
-
test('local file handler', () => {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
});
|
|
1
|
+
import * as fs from 'fs';
|
|
2
|
+
import { LocalFileHandler } from '../../sdk/offline_handlers.js';
|
|
3
|
+
import { EnvironmentModel } from '../../flagsmith-engine/index.js';
|
|
4
|
+
|
|
5
|
+
import * as offlineEnvironment from './data/offline-environment.json';
|
|
6
|
+
|
|
7
|
+
vi.mock('fs');
|
|
8
|
+
|
|
9
|
+
const offlineEnvironmentString = JSON.stringify(offlineEnvironment);
|
|
10
|
+
|
|
11
|
+
test('local file handler', () => {
|
|
12
|
+
const environmentDocumentFilePath = '/some/path/environment.json';
|
|
13
|
+
|
|
14
|
+
// Mock the fs.readFileSync function to return environmentJson
|
|
15
|
+
|
|
16
|
+
const readFileSyncMock = vi.spyOn(fs, 'readFileSync');
|
|
17
|
+
readFileSyncMock.mockImplementation(() => offlineEnvironmentString);
|
|
18
|
+
|
|
19
|
+
// Given
|
|
20
|
+
const localFileHandler = new LocalFileHandler(environmentDocumentFilePath);
|
|
21
|
+
|
|
22
|
+
// When
|
|
23
|
+
const environmentModel = localFileHandler.getEnvironment();
|
|
24
|
+
|
|
25
|
+
// Then
|
|
26
|
+
expect(environmentModel).toBeInstanceOf(EnvironmentModel);
|
|
27
|
+
expect(environmentModel.apiKey).toBe('B62qaMZNwfiqT76p38ggrQ');
|
|
28
|
+
expect(readFileSyncMock).toHaveBeenCalledWith(environmentDocumentFilePath, 'utf8');
|
|
29
|
+
|
|
30
|
+
// Restore the original implementation of fs.readFileSync
|
|
31
|
+
readFileSyncMock.mockRestore();
|
|
32
|
+
});
|
|
@@ -39,7 +39,6 @@ test('test_polling_manager_handles_double_start', async () => {
|
|
|
39
39
|
expect(flagsmith.updateEnvironment).toHaveBeenCalled();
|
|
40
40
|
});
|
|
41
41
|
|
|
42
|
-
|
|
43
42
|
test('test_polling_manager_calls_update_environment_on_each_refresh', async () => {
|
|
44
43
|
const flagsmith = new Flagsmith({
|
|
45
44
|
environmentKey: 'key'
|
package/tests/sdk/utils.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { readFileSync } from 'fs';
|
|
2
2
|
import { buildEnvironmentModel } from '../../flagsmith-engine/environments/util.js';
|
|
3
3
|
import { AnalyticsProcessor } from '../../sdk/analytics.js';
|
|
4
|
-
import Flagsmith, {FlagsmithConfig} from '../../sdk/index.js';
|
|
4
|
+
import Flagsmith, { FlagsmithConfig } from '../../sdk/index.js';
|
|
5
5
|
import { Fetch, FlagsmithCache } from '../../sdk/types.js';
|
|
6
6
|
import { Flags } from '../../sdk/models.js';
|
|
7
7
|
|
|
@@ -21,31 +21,36 @@ export class TestCache implements FlagsmithCache {
|
|
|
21
21
|
|
|
22
22
|
export const fetch = vi.fn((url: string, options?: RequestInit) => {
|
|
23
23
|
const headers = options?.headers as Record<string, string>;
|
|
24
|
-
if (!headers) throw new Error('missing request headers')
|
|
24
|
+
if (!headers) throw new Error('missing request headers');
|
|
25
25
|
const env = headers['X-Environment-Key'];
|
|
26
|
-
if (!env)
|
|
26
|
+
if (!env)
|
|
27
|
+
return Promise.resolve(new Response('missing x-environment-key header', { status: 404 }));
|
|
27
28
|
if (url.includes('/environment-document')) {
|
|
28
29
|
if (env.startsWith('ser.')) {
|
|
29
|
-
return Promise.resolve(new Response(environmentJSON, { status: 200 }))
|
|
30
|
+
return Promise.resolve(new Response(environmentJSON, { status: 200 }));
|
|
30
31
|
}
|
|
31
|
-
return Promise.resolve(
|
|
32
|
+
return Promise.resolve(
|
|
33
|
+
new Response('environment-document called without a server-side key', { status: 401 })
|
|
34
|
+
);
|
|
32
35
|
}
|
|
33
|
-
if (url.includes(
|
|
34
|
-
return Promise.resolve(new Response(flagsJSON, { status: 200 }))
|
|
36
|
+
if (url.includes('/flags')) {
|
|
37
|
+
return Promise.resolve(new Response(flagsJSON, { status: 200 }));
|
|
35
38
|
}
|
|
36
|
-
if (url.includes(
|
|
37
|
-
return Promise.resolve(new Response(identitiesJSON, { status: 200 }))
|
|
39
|
+
if (url.includes('/identities')) {
|
|
40
|
+
return Promise.resolve(new Response(identitiesJSON, { status: 200 }));
|
|
38
41
|
}
|
|
39
|
-
return Promise.resolve(new Response('unknown url ' + url, { status: 404 }))
|
|
42
|
+
return Promise.resolve(new Response('unknown url ' + url, { status: 404 }));
|
|
40
43
|
});
|
|
41
44
|
|
|
42
|
-
export const badFetch: Fetch = () => {
|
|
45
|
+
export const badFetch: Fetch = () => {
|
|
46
|
+
throw new Error('fetch failed');
|
|
47
|
+
};
|
|
43
48
|
|
|
44
49
|
export function analyticsProcessor() {
|
|
45
50
|
return new AnalyticsProcessor({
|
|
46
51
|
environmentKey: 'test-key',
|
|
47
52
|
analyticsUrl: 'http://testUrl/analytics/flags/',
|
|
48
|
-
fetch: (url, options) => fetch(url.toString(), options)
|
|
53
|
+
fetch: (url, options) => fetch(url.toString(), options)
|
|
49
54
|
});
|
|
50
55
|
}
|
|
51
56
|
|
|
@@ -59,22 +64,25 @@ export function flagsmith(params: FlagsmithConfig = {}) {
|
|
|
59
64
|
environmentRefreshIntervalSeconds: 0,
|
|
60
65
|
requestRetryDelayMilliseconds: 0,
|
|
61
66
|
fetch: (url, options) => fetch(url.toString(), options),
|
|
62
|
-
...params
|
|
67
|
+
...params
|
|
63
68
|
});
|
|
64
69
|
}
|
|
65
70
|
|
|
66
71
|
export const environmentJSON = readFileSync(DATA_DIR + 'environment.json', 'utf-8');
|
|
67
72
|
|
|
68
|
-
export const offlineEnvironmentJSON = readFileSync(DATA_DIR + 'offline-environment.json', 'utf-8')
|
|
73
|
+
export const offlineEnvironmentJSON = readFileSync(DATA_DIR + 'offline-environment.json', 'utf-8');
|
|
69
74
|
|
|
70
75
|
export function environmentModel(environmentJSON: any) {
|
|
71
76
|
return buildEnvironmentModel(environmentJSON);
|
|
72
77
|
}
|
|
73
78
|
|
|
74
|
-
export const flagsJSON = readFileSync(DATA_DIR + 'flags.json', 'utf-8')
|
|
79
|
+
export const flagsJSON = readFileSync(DATA_DIR + 'flags.json', 'utf-8');
|
|
75
80
|
|
|
76
|
-
export const identitiesJSON = readFileSync(DATA_DIR + 'identities.json', 'utf-8')
|
|
81
|
+
export const identitiesJSON = readFileSync(DATA_DIR + 'identities.json', 'utf-8');
|
|
77
82
|
|
|
78
|
-
export const transientIdentityJSON = readFileSync(DATA_DIR + 'transient-identity.json', 'utf-8')
|
|
83
|
+
export const transientIdentityJSON = readFileSync(DATA_DIR + 'transient-identity.json', 'utf-8');
|
|
79
84
|
|
|
80
|
-
export const identityWithTransientTraitsJSON = readFileSync(
|
|
85
|
+
export const identityWithTransientTraitsJSON = readFileSync(
|
|
86
|
+
DATA_DIR + 'identity-with-transient-traits.json',
|
|
87
|
+
'utf-8'
|
|
88
|
+
);
|
package/vitest.config.ts
CHANGED
|
@@ -1,18 +1,13 @@
|
|
|
1
|
-
import { defineConfig } from 'vitest/config'
|
|
1
|
+
import { defineConfig } from 'vitest/config';
|
|
2
2
|
|
|
3
3
|
export default defineConfig({
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
include: [
|
|
13
|
-
'sdk/**',
|
|
14
|
-
'flagsmith-engine/**',
|
|
15
|
-
]
|
|
4
|
+
test: {
|
|
5
|
+
globals: true,
|
|
6
|
+
restoreMocks: true,
|
|
7
|
+
coverage: {
|
|
8
|
+
reporter: ['text'],
|
|
9
|
+
exclude: ['build/**'],
|
|
10
|
+
include: ['sdk/**', 'flagsmith-engine/**']
|
|
11
|
+
}
|
|
16
12
|
}
|
|
17
|
-
|
|
18
|
-
})
|
|
13
|
+
});
|