flagsmith-nodejs 3.1.1 → 3.3.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/.husky/pre-commit +1 -1
- package/README.md +2 -1
- package/build/flagsmith-engine/environments/models.d.ts +2 -5
- package/build/flagsmith-engine/environments/models.js +1 -0
- package/build/flagsmith-engine/environments/util.js +8 -2
- package/build/flagsmith-engine/features/util.js +2 -2
- package/build/flagsmith-engine/index.d.ts +0 -1
- package/build/flagsmith-engine/index.js +11 -13
- package/build/index.d.ts +2 -1
- package/build/index.js +1 -2
- package/build/sdk/index.d.ts +20 -9
- package/build/sdk/index.js +119 -53
- package/build/sdk/offline_handlers.d.ts +9 -0
- package/build/sdk/offline_handlers.js +66 -0
- package/build/sdk/types.d.ts +4 -1
- package/flagsmith-engine/environments/models.ts +2 -5
- package/flagsmith-engine/environments/util.ts +6 -0
- package/flagsmith-engine/features/util.ts +14 -13
- package/flagsmith-engine/index.ts +0 -1
- package/index.ts +4 -1
- package/package.json +1 -1
- package/sdk/index.ts +119 -48
- package/sdk/offline_handlers.ts +22 -0
- package/sdk/types.ts +4 -1
- package/tests/sdk/data/environment.json +27 -1
- package/tests/sdk/data/offline-environment.json +93 -0
- package/tests/sdk/flagsmith.test.ts +115 -27
- package/tests/sdk/offline-handlers.test.ts +33 -0
- package/tests/sdk/utils.ts +2 -2
- package/build/flagsmith-engine/environments/integrations/models.d.ts +0 -4
- package/build/flagsmith-engine/environments/integrations/models.js +0 -11
- package/examples/README.md +0 -3
- package/flagsmith-engine/environments/integrations/models.ts +0 -4
|
@@ -0,0 +1,93 @@
|
|
|
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
|
+
"name": "regular_segment",
|
|
17
|
+
"feature_states": [
|
|
18
|
+
{
|
|
19
|
+
"feature_state_value": "segment_override",
|
|
20
|
+
"multivariate_feature_state_values": [],
|
|
21
|
+
"django_id": 81027,
|
|
22
|
+
"feature": {
|
|
23
|
+
"name": "some_feature",
|
|
24
|
+
"type": "STANDARD",
|
|
25
|
+
"id": 1
|
|
26
|
+
},
|
|
27
|
+
"enabled": false
|
|
28
|
+
}
|
|
29
|
+
],
|
|
30
|
+
"id": 1,
|
|
31
|
+
"rules": [
|
|
32
|
+
{
|
|
33
|
+
"type": "ALL",
|
|
34
|
+
"conditions": [],
|
|
35
|
+
"rules": [
|
|
36
|
+
{
|
|
37
|
+
"type": "ANY",
|
|
38
|
+
"conditions": [
|
|
39
|
+
{
|
|
40
|
+
"value": "40",
|
|
41
|
+
"property_": "age",
|
|
42
|
+
"operator": "LESS_THAN"
|
|
43
|
+
}
|
|
44
|
+
],
|
|
45
|
+
"rules": []
|
|
46
|
+
}
|
|
47
|
+
]
|
|
48
|
+
}
|
|
49
|
+
]
|
|
50
|
+
}
|
|
51
|
+
]
|
|
52
|
+
},
|
|
53
|
+
"segment_overrides": [],
|
|
54
|
+
"id": 1,
|
|
55
|
+
"feature_states": [
|
|
56
|
+
{
|
|
57
|
+
"multivariate_feature_state_values": [],
|
|
58
|
+
"feature_state_value": "offline-value",
|
|
59
|
+
"id": 1,
|
|
60
|
+
"featurestate_uuid": "40eb539d-3713-4720-bbd4-829dbef10d51",
|
|
61
|
+
"feature": {
|
|
62
|
+
"name": "some_feature",
|
|
63
|
+
"type": "STANDARD",
|
|
64
|
+
"id": 1
|
|
65
|
+
},
|
|
66
|
+
"feature_segment": null,
|
|
67
|
+
"enabled": true
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
"multivariate_feature_state_values": [
|
|
71
|
+
{
|
|
72
|
+
"percentage_allocation": 100,
|
|
73
|
+
"multivariate_feature_option": {
|
|
74
|
+
"value": "bar",
|
|
75
|
+
"id": 1
|
|
76
|
+
},
|
|
77
|
+
"mv_fs_value_uuid": "42d5cdf9-8ec9-4b8d-a3ca-fd43c64d5f05",
|
|
78
|
+
"id": 1
|
|
79
|
+
}
|
|
80
|
+
],
|
|
81
|
+
"feature_state_value": "foo",
|
|
82
|
+
"feature": {
|
|
83
|
+
"name": "mv_feature",
|
|
84
|
+
"type": "MULTIVARIATE",
|
|
85
|
+
"id": 2
|
|
86
|
+
},
|
|
87
|
+
"feature_segment": null,
|
|
88
|
+
"featurestate_uuid": "96fc3503-09d7-48f1-a83b-2dc903d5c08a",
|
|
89
|
+
"enabled": false
|
|
90
|
+
}
|
|
91
|
+
]
|
|
92
|
+
}
|
|
93
|
+
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import Flagsmith from '../../sdk';
|
|
2
2
|
import { EnvironmentDataPollingManager } from '../../sdk/polling_manager';
|
|
3
|
-
import fetch, {RequestInit} from 'node-fetch';
|
|
3
|
+
import fetch, { RequestInit } from 'node-fetch';
|
|
4
4
|
import { environmentJSON, environmentModel, flagsJSON, flagsmith, identitiesJSON } from './utils';
|
|
5
|
-
import { DefaultFlag } from '../../sdk/models';
|
|
6
|
-
import {delay
|
|
7
|
-
import * as utils from '../../sdk/utils';
|
|
5
|
+
import { DefaultFlag, Flags } from '../../sdk/models';
|
|
6
|
+
import { delay } from '../../sdk/utils';
|
|
8
7
|
import { EnvironmentModel } from '../../flagsmith-engine/environments/models';
|
|
9
8
|
import https from 'https'
|
|
9
|
+
import { BaseOfflineHandler } from '../../sdk/offline_handlers';
|
|
10
10
|
|
|
11
11
|
jest.mock('node-fetch');
|
|
12
12
|
jest.mock('../../sdk/polling_manager');
|
|
@@ -47,9 +47,6 @@ test('test_update_environment_sets_environment', async () => {
|
|
|
47
47
|
|
|
48
48
|
const model = environmentModel(JSON.parse(environmentJSON()));
|
|
49
49
|
|
|
50
|
-
wipeFeatureStateUUIDs(flg.environment)
|
|
51
|
-
wipeFeatureStateUUIDs(model)
|
|
52
|
-
|
|
53
50
|
expect(flg.environment).toStrictEqual(model);
|
|
54
51
|
});
|
|
55
52
|
|
|
@@ -57,8 +54,8 @@ test('test_set_agent_options', async () => {
|
|
|
57
54
|
const agent = new https.Agent({})
|
|
58
55
|
|
|
59
56
|
// @ts-ignore
|
|
60
|
-
fetch.mockImplementation((url:string, options:RequestInit)=>{
|
|
61
|
-
if(options.agent!==agent) {
|
|
57
|
+
fetch.mockImplementation((url: string, options: RequestInit) => {
|
|
58
|
+
if (options.agent !== agent) {
|
|
62
59
|
throw new Error("Agent has not been set on retry fetch")
|
|
63
60
|
}
|
|
64
61
|
return Promise.resolve(new Response(environmentJSON()))
|
|
@@ -202,7 +199,7 @@ test('request timeout uses default if not provided', async () => {
|
|
|
202
199
|
expect(flg.requestTimeoutMs).toBe(10000);
|
|
203
200
|
})
|
|
204
201
|
|
|
205
|
-
test('
|
|
202
|
+
test('test_throws_when_no_identityFlags_returned_due_to_error', async () => {
|
|
206
203
|
// @ts-ignore
|
|
207
204
|
fetch.mockReturnValue(Promise.resolve(new Response('bad data')));
|
|
208
205
|
|
|
@@ -275,27 +272,118 @@ test('getIdentitySegments throws error if identifier is empty string', () => {
|
|
|
275
272
|
})
|
|
276
273
|
|
|
277
274
|
|
|
278
|
-
async
|
|
279
|
-
//
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
275
|
+
test('offline_mode', async () => {
|
|
276
|
+
// Given
|
|
277
|
+
const environment: EnvironmentModel = environmentModel(JSON.parse(environmentJSON('offline-environment.json')));
|
|
278
|
+
|
|
279
|
+
class DummyOfflineHandler extends BaseOfflineHandler {
|
|
280
|
+
getEnvironment(): EnvironmentModel {
|
|
281
|
+
return environment;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// When
|
|
286
|
+
const flagsmith = new Flagsmith({ offlineMode: true, offlineHandler: new DummyOfflineHandler() });
|
|
287
|
+
|
|
288
|
+
// Then
|
|
289
|
+
// we can request the flags from the client successfully
|
|
290
|
+
const environmentFlags: Flags = await flagsmith.getEnvironmentFlags();
|
|
291
|
+
let flag = environmentFlags.getFlag('some_feature');
|
|
292
|
+
expect(flag.isDefault).toBe(false);
|
|
293
|
+
expect(flag.enabled).toBe(true);
|
|
294
|
+
expect(flag.value).toBe('offline-value');
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
const identityFlags: Flags = await flagsmith.getIdentityFlags("identity");
|
|
298
|
+
flag = identityFlags.getFlag('some_feature');
|
|
299
|
+
expect(flag.isDefault).toBe(false);
|
|
300
|
+
expect(flag.enabled).toBe(true);
|
|
301
|
+
expect(flag.value).toBe('offline-value');
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
test('test_flagsmith_uses_offline_handler_if_set_and_no_api_response', async () => {
|
|
306
|
+
// Given
|
|
307
|
+
const environment: EnvironmentModel = environmentModel(JSON.parse(environmentJSON('offline-environment.json')));
|
|
308
|
+
const api_url = 'http://some.flagsmith.com/api/v1/';
|
|
309
|
+
const mock_offline_handler = new BaseOfflineHandler() as jest.Mocked<BaseOfflineHandler>;
|
|
310
|
+
|
|
311
|
+
jest.spyOn(mock_offline_handler, 'getEnvironment').mockReturnValue(environment);
|
|
312
|
+
|
|
313
|
+
const flagsmith = new Flagsmith({
|
|
314
|
+
environmentKey: 'some-key',
|
|
315
|
+
apiUrl: api_url,
|
|
316
|
+
offlineHandler: mock_offline_handler,
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
jest.spyOn(flagsmith, 'getEnvironmentFlags');
|
|
320
|
+
jest.spyOn(flagsmith, 'getIdentityFlags');
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
flagsmith.environmentFlagsUrl = 'http://some.flagsmith.com/api/v1/environment-flags';
|
|
324
|
+
flagsmith.identitiesUrl = 'http://some.flagsmith.com/api/v1/identities';
|
|
325
|
+
|
|
326
|
+
// Mock a 500 Internal Server Error response
|
|
327
|
+
const errorResponse = new Response(null, {
|
|
328
|
+
status: 500,
|
|
329
|
+
statusText: 'Internal Server Error',
|
|
288
330
|
});
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
}
|
|
331
|
+
|
|
332
|
+
// @ts-ignore
|
|
333
|
+
fetch.mockReturnValue(Promise.resolve(errorResponse));
|
|
334
|
+
|
|
335
|
+
// When
|
|
336
|
+
const environmentFlags: Flags = await flagsmith.getEnvironmentFlags();
|
|
337
|
+
const identityFlags: Flags = await flagsmith.getIdentityFlags('identity', {});
|
|
338
|
+
|
|
339
|
+
// Then
|
|
340
|
+
expect(mock_offline_handler.getEnvironment).toHaveBeenCalledTimes(1);
|
|
341
|
+
expect(flagsmith.getEnvironmentFlags).toHaveBeenCalled();
|
|
342
|
+
expect(flagsmith.getIdentityFlags).toHaveBeenCalled();
|
|
343
|
+
|
|
344
|
+
expect(environmentFlags.isFeatureEnabled('some_feature')).toBe(true);
|
|
345
|
+
expect(environmentFlags.getFeatureValue('some_feature')).toBe('offline-value');
|
|
346
|
+
|
|
347
|
+
expect(identityFlags.isFeatureEnabled('some_feature')).toBe(true);
|
|
348
|
+
expect(identityFlags.getFeatureValue('some_feature')).toBe('offline-value');
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
test('cannot use offline mode without offline handler', () => {
|
|
352
|
+
// When and Then
|
|
353
|
+
expect(() => new Flagsmith({ offlineMode: true, offlineHandler: undefined })).toThrowError(
|
|
354
|
+
'ValueError: offlineHandler must be provided to use offline mode.'
|
|
355
|
+
);
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
test('cannot use both default handler and offline handler', () => {
|
|
359
|
+
// When and Then
|
|
360
|
+
expect(() => new Flagsmith({
|
|
361
|
+
offlineHandler: new BaseOfflineHandler(),
|
|
362
|
+
defaultFlagHandler: (flagName) => new DefaultFlag('foo', true)
|
|
363
|
+
})).toThrowError('ValueError: Cannot use both defaultFlagHandler and offlineHandler.');
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
test('cannot create Flagsmith client in remote evaluation without API key', () => {
|
|
367
|
+
// When and Then
|
|
368
|
+
// @ts-ignore
|
|
369
|
+
expect(() => new Flagsmith()).toThrowError('ValueError: environmentKey is required.');
|
|
370
|
+
});
|
|
371
|
+
|
|
296
372
|
|
|
297
373
|
function sleep(ms: number) {
|
|
298
374
|
return new Promise((resolve) => {
|
|
299
375
|
setTimeout(resolve, ms);
|
|
300
376
|
});
|
|
301
377
|
}
|
|
378
|
+
test('test_localEvaluation_true__identity_overrides_evaluated', async () => {
|
|
379
|
+
// @ts-ignore
|
|
380
|
+
fetch.mockReturnValue(Promise.resolve(new Response(environmentJSON())));
|
|
381
|
+
|
|
382
|
+
const flg = new Flagsmith({
|
|
383
|
+
environmentKey: 'ser.key',
|
|
384
|
+
enableLocalEvaluation: true,
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
const flags = await flg.getIdentityFlags("overridden-id");
|
|
388
|
+
expect(flags.getFeatureValue("some_feature")).toEqual("some-overridden-value");
|
|
389
|
+
});
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import * as fs from 'fs';
|
|
2
|
+
import { LocalFileHandler } from '../../sdk/offline_handlers';
|
|
3
|
+
import { EnvironmentModel } from '../../flagsmith-engine';
|
|
4
|
+
|
|
5
|
+
const offlineEnvironment = require('./data/offline-environment.json');
|
|
6
|
+
|
|
7
|
+
jest.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
|
+
// @ts-ignore
|
|
17
|
+
const readFileSyncMock = jest.spyOn(fs, 'readFileSync');
|
|
18
|
+
readFileSyncMock.mockImplementation(() => offlineEnvironmentString);
|
|
19
|
+
|
|
20
|
+
// Given
|
|
21
|
+
const localFileHandler = new LocalFileHandler(environmentDocumentFilePath);
|
|
22
|
+
|
|
23
|
+
// When
|
|
24
|
+
const environmentModel = localFileHandler.getEnvironment();
|
|
25
|
+
|
|
26
|
+
// Then
|
|
27
|
+
expect(environmentModel).toBeInstanceOf(EnvironmentModel);
|
|
28
|
+
expect(environmentModel.apiKey).toBe('B62qaMZNwfiqT76p38ggrQ');
|
|
29
|
+
expect(readFileSyncMock).toHaveBeenCalledWith(environmentDocumentFilePath, 'utf8');
|
|
30
|
+
|
|
31
|
+
// Restore the original implementation of fs.readFileSync
|
|
32
|
+
readFileSyncMock.mockRestore();
|
|
33
|
+
});
|
package/tests/sdk/utils.ts
CHANGED
|
@@ -42,8 +42,8 @@ export function flagsmith(params = {}) {
|
|
|
42
42
|
});
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
-
export function environmentJSON() {
|
|
46
|
-
return readFileSync(DATA_DIR +
|
|
45
|
+
export function environmentJSON(environmentFilename: string = 'environment.json') {
|
|
46
|
+
return readFileSync(DATA_DIR + environmentFilename, 'utf-8');
|
|
47
47
|
}
|
|
48
48
|
|
|
49
49
|
export function environmentModel(environmentJSON: any) {
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.IntegrationModel = void 0;
|
|
4
|
-
var IntegrationModel = /** @class */ (function () {
|
|
5
|
-
function IntegrationModel() {
|
|
6
|
-
this.api_key = undefined;
|
|
7
|
-
this.base_url = undefined;
|
|
8
|
-
}
|
|
9
|
-
return IntegrationModel;
|
|
10
|
-
}());
|
|
11
|
-
exports.IntegrationModel = IntegrationModel;
|
package/examples/README.md
DELETED