flagsmith-nodejs 6.0.1 → 6.1.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/publish.yml +17 -17
- package/.github/workflows/pull_request.yaml +33 -33
- package/.husky/pre-commit +0 -0
- package/.prettierignore +2 -1
- package/README.md +2 -1
- 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/index.d.ts +5 -5
- package/build/cjs/sdk/index.js +7 -5
- 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 +4 -4
- package/build/cjs/sdk/utils.js +2 -2
- 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/index.d.ts +5 -5
- package/build/esm/sdk/index.js +6 -5
- 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 +4 -4
- package/build/esm/sdk/utils.js +2 -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/sdk/analytics.ts +7 -5
- package/sdk/index.ts +38 -21
- package/sdk/models.ts +25 -0
- package/sdk/offline_handlers.ts +1 -1
- package/sdk/types.ts +17 -8
- package/sdk/utils.ts +8 -8
- package/tests/engine/e2e/engine.test.ts +2 -4
- package/tests/engine/unit/engine.test.ts +1 -5
- 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 +25 -26
- package/tests/sdk/flagsmith-cache.test.ts +84 -76
- package/tests/sdk/flagsmith-environment-flags.test.ts +93 -93
- package/tests/sdk/flagsmith-identity-flags.test.ts +146 -149
- package/tests/sdk/flagsmith.test.ts +40 -42
- 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
|
@@ -2,60 +2,58 @@ import { randomUUID as uuidv4 } from 'node:crypto';
|
|
|
2
2
|
import { getHashedPercentateForObjIds } from '../../../../flagsmith-engine/utils/hashing/index.js';
|
|
3
3
|
|
|
4
4
|
describe('getHashedPercentageForObjIds', () => {
|
|
5
|
-
it.each([
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
let resultOne = getHashedPercentateForObjIds(objIds);
|
|
23
|
-
let resultTwo = getHashedPercentateForObjIds(objIds);
|
|
24
|
-
expect(resultOne).toEqual(resultTwo);
|
|
25
|
-
})
|
|
5
|
+
it.each([[[12, 93]], [[uuidv4(), 99]], [[99, uuidv4()]], [[uuidv4(), uuidv4()]]])(
|
|
6
|
+
'returns x where 0 <= x < 100',
|
|
7
|
+
(objIds: (string | number)[]) => {
|
|
8
|
+
let result = getHashedPercentateForObjIds(objIds);
|
|
9
|
+
expect(result).toBeLessThan(100);
|
|
10
|
+
expect(result).toBeGreaterThanOrEqual(0);
|
|
11
|
+
}
|
|
12
|
+
);
|
|
13
|
+
|
|
14
|
+
it.each([[[12, 93]], [[uuidv4(), 99]], [[99, uuidv4()]], [[uuidv4(), uuidv4()]]])(
|
|
15
|
+
'returns the same value each time',
|
|
16
|
+
(objIds: (string | number)[]) => {
|
|
17
|
+
let resultOne = getHashedPercentateForObjIds(objIds);
|
|
18
|
+
let resultTwo = getHashedPercentateForObjIds(objIds);
|
|
19
|
+
expect(resultOne).toEqual(resultTwo);
|
|
20
|
+
}
|
|
21
|
+
);
|
|
26
22
|
|
|
27
23
|
it('is unique for different object ids', () => {
|
|
28
24
|
let resultOne = getHashedPercentateForObjIds([14, 106]);
|
|
29
25
|
let resultTwo = getHashedPercentateForObjIds([53, 200]);
|
|
30
26
|
expect(resultOne).not.toEqual(resultTwo);
|
|
31
|
-
})
|
|
27
|
+
});
|
|
32
28
|
|
|
33
29
|
it('is evenly distributed', () => {
|
|
34
30
|
// copied from python test here:
|
|
35
31
|
// https://github.com/Flagsmith/flagsmith-engine/blob/main/tests/unit/utils/test_utils_hashing.py#L56
|
|
36
32
|
const testSample = 500;
|
|
37
33
|
const numTestBuckets = 50;
|
|
38
|
-
const testBucketSize = Math.floor(testSample / numTestBuckets)
|
|
39
|
-
const errorFactor = 0.1
|
|
34
|
+
const testBucketSize = Math.floor(testSample / numTestBuckets);
|
|
35
|
+
const errorFactor = 0.1;
|
|
40
36
|
|
|
41
37
|
// Given
|
|
42
|
-
let objectIdPairs = Array.from(Array(testSample).keys()).flatMap(d =>
|
|
38
|
+
let objectIdPairs = Array.from(Array(testSample).keys()).flatMap(d =>
|
|
39
|
+
Array.from(Array(testSample).keys()).map(e => [d, e].flat())
|
|
40
|
+
);
|
|
43
41
|
|
|
44
42
|
// When
|
|
45
|
-
let values = objectIdPairs.map(
|
|
43
|
+
let values = objectIdPairs.map(objIds => getHashedPercentateForObjIds(objIds));
|
|
46
44
|
|
|
47
45
|
// Then
|
|
48
46
|
for (let i = 0; i++; i < numTestBuckets) {
|
|
49
47
|
let bucketStart = i * testBucketSize;
|
|
50
48
|
let bucketEnd = (i + 1) * testBucketSize;
|
|
51
49
|
let bucketValueLimit = Math.min(
|
|
52
|
-
(i + 1) / numTestBuckets + errorFactor + (
|
|
50
|
+
(i + 1) / numTestBuckets + errorFactor + (i + 1) / numTestBuckets,
|
|
53
51
|
1
|
|
54
|
-
)
|
|
52
|
+
);
|
|
55
53
|
|
|
56
54
|
for (let i = bucketStart; i++; i < bucketEnd) {
|
|
57
55
|
expect(values[i]).toBeLessThanOrEqual(bucketValueLimit);
|
|
58
56
|
}
|
|
59
57
|
}
|
|
60
|
-
})
|
|
61
|
-
})
|
|
58
|
+
});
|
|
59
|
+
});
|
|
@@ -1,40 +1,43 @@
|
|
|
1
|
-
import {analyticsProcessor, fetch} from './utils.js';
|
|
1
|
+
import { analyticsProcessor, fetch } from './utils.js';
|
|
2
2
|
|
|
3
3
|
test('test_analytics_processor_track_feature_updates_analytics_data', () => {
|
|
4
4
|
const aP = analyticsProcessor();
|
|
5
|
-
aP.trackFeature(
|
|
6
|
-
expect(aP.analyticsData[
|
|
5
|
+
aP.trackFeature('myFeature');
|
|
6
|
+
expect(aP.analyticsData['myFeature']).toBe(1);
|
|
7
7
|
|
|
8
|
-
aP.trackFeature(
|
|
9
|
-
expect(aP.analyticsData[
|
|
8
|
+
aP.trackFeature('myFeature');
|
|
9
|
+
expect(aP.analyticsData['myFeature']).toBe(2);
|
|
10
10
|
});
|
|
11
11
|
|
|
12
12
|
test('test_analytics_processor_flush_clears_analytics_data', async () => {
|
|
13
13
|
const aP = analyticsProcessor();
|
|
14
|
-
aP.trackFeature(
|
|
14
|
+
aP.trackFeature('myFeature');
|
|
15
15
|
await aP.flush();
|
|
16
16
|
expect(aP.analyticsData).toStrictEqual({});
|
|
17
17
|
});
|
|
18
18
|
|
|
19
19
|
test('test_analytics_processor_flush_post_request_data_match_ananlytics_data', async () => {
|
|
20
20
|
const aP = analyticsProcessor();
|
|
21
|
-
aP.trackFeature(
|
|
22
|
-
aP.trackFeature(
|
|
21
|
+
aP.trackFeature('myFeature1');
|
|
22
|
+
aP.trackFeature('myFeature2');
|
|
23
23
|
await aP.flush();
|
|
24
24
|
expect(fetch).toHaveBeenCalledTimes(1);
|
|
25
|
-
expect(fetch).toHaveBeenCalledWith(
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
25
|
+
expect(fetch).toHaveBeenCalledWith(
|
|
26
|
+
'http://testUrl/analytics/flags/',
|
|
27
|
+
expect.objectContaining({
|
|
28
|
+
body: '{"myFeature1":1,"myFeature2":1}',
|
|
29
|
+
headers: { 'Content-Type': 'application/json', 'X-Environment-Key': 'test-key' },
|
|
30
|
+
method: 'POST'
|
|
31
|
+
})
|
|
32
|
+
);
|
|
30
33
|
});
|
|
31
34
|
|
|
32
|
-
vi.useFakeTimers()
|
|
35
|
+
vi.useFakeTimers();
|
|
33
36
|
test('test_analytics_processor_flush_post_request_data_match_ananlytics_data_test', async () => {
|
|
34
37
|
const aP = analyticsProcessor();
|
|
35
|
-
aP.trackFeature(
|
|
38
|
+
aP.trackFeature('myFeature1');
|
|
36
39
|
setTimeout(() => {
|
|
37
|
-
aP.trackFeature(
|
|
40
|
+
aP.trackFeature('myFeature2');
|
|
38
41
|
expect(fetch).toHaveBeenCalledTimes(1);
|
|
39
42
|
}, 15000);
|
|
40
43
|
vi.runOnlyPendingTimers();
|
|
@@ -46,7 +49,6 @@ test('test_analytics_processor_flush_early_exit_if_analytics_data_is_empty', asy
|
|
|
46
49
|
expect(fetch).not.toHaveBeenCalled();
|
|
47
50
|
});
|
|
48
51
|
|
|
49
|
-
|
|
50
52
|
test('errors in fetch sending analytics data are swallowed', async () => {
|
|
51
53
|
// Given
|
|
52
54
|
// we mock the fetch function to throw and error to mimick a network failure
|
|
@@ -63,21 +65,18 @@ test('errors in fetch sending analytics data are swallowed', async () => {
|
|
|
63
65
|
// Then
|
|
64
66
|
// we expect that fetch was called but the exception was handled
|
|
65
67
|
expect(fetch).toHaveBeenCalled();
|
|
66
|
-
})
|
|
68
|
+
});
|
|
67
69
|
|
|
68
70
|
test('analytics is only flushed once even if requested concurrently', async () => {
|
|
69
71
|
const processor = analyticsProcessor();
|
|
70
72
|
processor.trackFeature('myFeature');
|
|
71
73
|
fetch.mockImplementation(() => {
|
|
72
74
|
return new Promise((resolve, _) => {
|
|
73
|
-
setTimeout(resolve, 1000)
|
|
74
|
-
})
|
|
75
|
+
setTimeout(resolve, 1000);
|
|
76
|
+
});
|
|
75
77
|
});
|
|
76
|
-
const flushes = Promise.all([
|
|
77
|
-
processor.flush(),
|
|
78
|
-
processor.flush(),
|
|
79
|
-
])
|
|
78
|
+
const flushes = Promise.all([processor.flush(), processor.flush()]);
|
|
80
79
|
vi.runOnlyPendingTimers();
|
|
81
80
|
await flushes;
|
|
82
|
-
expect(fetch).toHaveBeenCalledTimes(1)
|
|
83
|
-
})
|
|
81
|
+
expect(fetch).toHaveBeenCalledTimes(1);
|
|
82
|
+
});
|
|
@@ -1,113 +1,121 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
fetch,
|
|
3
|
+
environmentJSON,
|
|
4
|
+
environmentModel,
|
|
5
|
+
flagsJSON,
|
|
6
|
+
flagsmith,
|
|
7
|
+
identitiesJSON,
|
|
8
|
+
TestCache
|
|
9
|
+
} from './utils.js';
|
|
2
10
|
|
|
3
11
|
test('test_empty_cache_not_read_but_populated', async () => {
|
|
4
|
-
|
|
5
|
-
|
|
12
|
+
const cache = new TestCache();
|
|
13
|
+
const set = vi.spyOn(cache, 'set');
|
|
6
14
|
|
|
7
|
-
|
|
8
|
-
|
|
15
|
+
const flg = flagsmith({ cache });
|
|
16
|
+
const allFlags = (await flg.getEnvironmentFlags()).allFlags();
|
|
9
17
|
|
|
10
|
-
|
|
11
|
-
|
|
18
|
+
expect(set).toBeCalled();
|
|
19
|
+
expect(await cache.get('flags')).toBeTruthy();
|
|
12
20
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
21
|
+
expect(fetch).toBeCalledTimes(1);
|
|
22
|
+
expect(allFlags[0].enabled).toBe(true);
|
|
23
|
+
expect(allFlags[0].value).toBe('some-value');
|
|
24
|
+
expect(allFlags[0].featureName).toBe('some_feature');
|
|
17
25
|
});
|
|
18
26
|
|
|
19
27
|
test('test_api_not_called_when_cache_present', async () => {
|
|
20
|
-
|
|
21
|
-
|
|
28
|
+
const cache = new TestCache();
|
|
29
|
+
const set = vi.spyOn(cache, 'set');
|
|
22
30
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
31
|
+
const flg = flagsmith({ cache });
|
|
32
|
+
await (await flg.getEnvironmentFlags()).allFlags();
|
|
33
|
+
const allFlags = await (await flg.getEnvironmentFlags()).allFlags();
|
|
26
34
|
|
|
27
|
-
|
|
28
|
-
|
|
35
|
+
expect(set).toBeCalled();
|
|
36
|
+
expect(await cache.get('flags')).toBeTruthy();
|
|
29
37
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
38
|
+
expect(fetch).toBeCalledTimes(1);
|
|
39
|
+
expect(allFlags[0].enabled).toBe(true);
|
|
40
|
+
expect(allFlags[0].value).toBe('some-value');
|
|
41
|
+
expect(allFlags[0].featureName).toBe('some_feature');
|
|
34
42
|
});
|
|
35
43
|
|
|
36
44
|
test('test_api_called_twice_when_no_cache', async () => {
|
|
37
|
-
|
|
45
|
+
fetch.mockImplementation(() => Promise.resolve(new Response(flagsJSON)));
|
|
38
46
|
|
|
39
|
-
|
|
40
|
-
|
|
47
|
+
const flg = flagsmith();
|
|
48
|
+
await (await flg.getEnvironmentFlags()).allFlags();
|
|
41
49
|
|
|
42
|
-
|
|
50
|
+
const allFlags = await (await flg.getEnvironmentFlags()).allFlags();
|
|
43
51
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
52
|
+
expect(fetch).toBeCalledTimes(2);
|
|
53
|
+
expect(allFlags[0].enabled).toBe(true);
|
|
54
|
+
expect(allFlags[0].value).toBe('some-value');
|
|
55
|
+
expect(allFlags[0].featureName).toBe('some_feature');
|
|
48
56
|
});
|
|
49
57
|
|
|
50
58
|
test('test_get_environment_flags_uses_local_environment_when_available', async () => {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
59
|
+
const cache = new TestCache();
|
|
60
|
+
const set = vi.spyOn(cache, 'set');
|
|
61
|
+
|
|
62
|
+
const flg = flagsmith({ cache, environmentKey: 'ser.key', enableLocalEvaluation: true });
|
|
63
|
+
const model = environmentModel(JSON.parse(environmentJSON));
|
|
64
|
+
const getEnvironment = vi.spyOn(flg, 'getEnvironment');
|
|
65
|
+
getEnvironment.mockResolvedValue(model);
|
|
66
|
+
|
|
67
|
+
const allFlags = (await flg.getEnvironmentFlags()).allFlags();
|
|
68
|
+
|
|
69
|
+
expect(set).toBeCalled();
|
|
70
|
+
expect(fetch).toBeCalledTimes(0);
|
|
71
|
+
expect(getEnvironment).toBeCalledTimes(1);
|
|
72
|
+
expect(allFlags[0].enabled).toBe(model.featureStates[0].enabled);
|
|
73
|
+
expect(allFlags[0].value).toBe(model.featureStates[0].getValue());
|
|
74
|
+
expect(allFlags[0].featureName).toBe(model.featureStates[0].feature.name);
|
|
67
75
|
});
|
|
68
76
|
|
|
69
77
|
test('test_cache_used_for_identity_flags', async () => {
|
|
70
|
-
|
|
71
|
-
|
|
78
|
+
const cache = new TestCache();
|
|
79
|
+
const set = vi.spyOn(cache, 'set');
|
|
72
80
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
81
|
+
const identifier = 'identifier';
|
|
82
|
+
const traits = { some_trait: 'some_value' };
|
|
83
|
+
const flg = flagsmith({ cache });
|
|
76
84
|
|
|
77
|
-
|
|
78
|
-
|
|
85
|
+
(await flg.getIdentityFlags(identifier, traits)).allFlags();
|
|
86
|
+
const identityFlags = (await flg.getIdentityFlags(identifier, traits)).allFlags();
|
|
79
87
|
|
|
80
|
-
|
|
81
|
-
|
|
88
|
+
expect(set).toBeCalled();
|
|
89
|
+
expect(await cache.get('flags-identifier')).toBeTruthy();
|
|
82
90
|
|
|
83
|
-
|
|
91
|
+
expect(fetch).toBeCalledTimes(1);
|
|
84
92
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
93
|
+
expect(identityFlags[0].enabled).toBe(true);
|
|
94
|
+
expect(identityFlags[0].value).toBe('some-value');
|
|
95
|
+
expect(identityFlags[0].featureName).toBe('some_feature');
|
|
88
96
|
});
|
|
89
97
|
|
|
90
98
|
test('test_cache_used_for_identity_flags_local_evaluation', async () => {
|
|
91
|
-
|
|
92
|
-
|
|
99
|
+
const cache = new TestCache();
|
|
100
|
+
const set = vi.spyOn(cache, 'set');
|
|
93
101
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
102
|
+
const identifier = 'identifier';
|
|
103
|
+
const traits = { some_trait: 'some_value' };
|
|
104
|
+
const flg = flagsmith({
|
|
105
|
+
cache,
|
|
106
|
+
environmentKey: 'ser.key',
|
|
107
|
+
enableLocalEvaluation: true
|
|
108
|
+
});
|
|
101
109
|
|
|
102
|
-
|
|
103
|
-
|
|
110
|
+
(await flg.getIdentityFlags(identifier, traits)).allFlags();
|
|
111
|
+
const identityFlags = (await flg.getIdentityFlags(identifier, traits)).allFlags();
|
|
104
112
|
|
|
105
|
-
|
|
106
|
-
|
|
113
|
+
expect(set).toBeCalled();
|
|
114
|
+
expect(await cache.get('flags-identifier')).toBeTruthy();
|
|
107
115
|
|
|
108
|
-
|
|
116
|
+
expect(fetch).toBeCalledTimes(1);
|
|
109
117
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
118
|
+
expect(identityFlags[0].enabled).toBe(true);
|
|
119
|
+
expect(identityFlags[0].value).toBe('some-value');
|
|
120
|
+
expect(identityFlags[0].featureName).toBe('some_feature');
|
|
113
121
|
});
|
|
@@ -5,150 +5,150 @@ import { DefaultFlag } from '../../sdk/models.js';
|
|
|
5
5
|
vi.mock('../../sdk/polling_manager');
|
|
6
6
|
|
|
7
7
|
test('test_get_environment_flags_calls_api_when_no_local_environment', async () => {
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
const flg = flagsmith();
|
|
9
|
+
const allFlags = await (await flg.getEnvironmentFlags()).allFlags();
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
11
|
+
expect(fetch).toBeCalledTimes(1);
|
|
12
|
+
expect(allFlags[0].enabled).toBe(true);
|
|
13
|
+
expect(allFlags[0].value).toBe('some-value');
|
|
14
|
+
expect(allFlags[0].featureName).toBe('some_feature');
|
|
15
15
|
});
|
|
16
16
|
|
|
17
17
|
test('test_default_flag_is_used_when_no_environment_flags_returned', async () => {
|
|
18
|
-
|
|
18
|
+
fetch.mockResolvedValue(new Response(JSON.stringify([])));
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
const defaultFlag = new DefaultFlag('some-default-value', true);
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
const defaultFlagHandler = (featureName: string) => defaultFlag;
|
|
23
23
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
24
|
+
const flg = new Flagsmith({
|
|
25
|
+
environmentKey: 'key',
|
|
26
|
+
defaultFlagHandler: defaultFlagHandler,
|
|
27
|
+
customHeaders: {
|
|
28
|
+
'X-Test-Header': '1'
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
31
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
32
|
+
const flags = await flg.getEnvironmentFlags();
|
|
33
|
+
const flag = flags.getFlag('some_feature');
|
|
34
|
+
expect(flag.isDefault).toBe(true);
|
|
35
|
+
expect(flag.enabled).toBe(defaultFlag.enabled);
|
|
36
|
+
expect(flag.value).toBe(defaultFlag.value);
|
|
37
37
|
});
|
|
38
38
|
|
|
39
39
|
test('test_analytics_processor_tracks_flags', async () => {
|
|
40
|
-
|
|
40
|
+
const defaultFlag = new DefaultFlag('some-default-value', true);
|
|
41
41
|
|
|
42
|
-
|
|
42
|
+
const defaultFlagHandler = (featureName: string) => defaultFlag;
|
|
43
43
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
44
|
+
const flg = flagsmith({
|
|
45
|
+
environmentKey: 'key',
|
|
46
|
+
defaultFlagHandler: defaultFlagHandler,
|
|
47
|
+
enableAnalytics: true
|
|
48
|
+
});
|
|
49
49
|
|
|
50
|
-
|
|
51
|
-
|
|
50
|
+
const flags = await flg.getEnvironmentFlags();
|
|
51
|
+
const flag = flags.getFlag('some_feature');
|
|
52
52
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
53
|
+
expect(flag.isDefault).toBe(false);
|
|
54
|
+
expect(flag.enabled).toBe(true);
|
|
55
|
+
expect(flag.value).toBe('some-value');
|
|
56
56
|
});
|
|
57
57
|
|
|
58
58
|
test('test_getFeatureValue', async () => {
|
|
59
|
-
|
|
59
|
+
const defaultFlag = new DefaultFlag('some-default-value', true);
|
|
60
60
|
|
|
61
|
-
|
|
61
|
+
const defaultFlagHandler = (featureName: string) => defaultFlag;
|
|
62
62
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
63
|
+
const flg = flagsmith({
|
|
64
|
+
environmentKey: 'key',
|
|
65
|
+
defaultFlagHandler: defaultFlagHandler,
|
|
66
|
+
enableAnalytics: true
|
|
67
|
+
});
|
|
68
68
|
|
|
69
|
-
|
|
70
|
-
|
|
69
|
+
const flags = await flg.getEnvironmentFlags();
|
|
70
|
+
const featureValue = flags.getFeatureValue('some_feature');
|
|
71
71
|
|
|
72
|
-
|
|
72
|
+
expect(featureValue).toBe('some-value');
|
|
73
73
|
});
|
|
74
74
|
|
|
75
75
|
test('test_throws_when_no_default_flag_handler_after_multiple_API_errors', async () => {
|
|
76
|
-
|
|
76
|
+
fetch.mockRejectedValue('Error during fetching the API response');
|
|
77
77
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
78
|
+
const flg = flagsmith({
|
|
79
|
+
environmentKey: 'key'
|
|
80
|
+
});
|
|
81
81
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
82
|
+
await expect(async () => {
|
|
83
|
+
const flags = await flg.getEnvironmentFlags();
|
|
84
|
+
const flag = flags.getFlag('some_feature');
|
|
85
|
+
}).rejects.toThrow('getEnvironmentFlags failed and no default flag handler was provided');
|
|
86
86
|
});
|
|
87
87
|
|
|
88
88
|
test('test_non_200_response_raises_flagsmith_api_error', async () => {
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
89
|
+
const errorResponse403 = new Response('403 Forbidden', {
|
|
90
|
+
status: 403
|
|
91
|
+
});
|
|
92
|
+
fetch.mockResolvedValue(errorResponse403);
|
|
93
93
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
94
|
+
const flg = new Flagsmith({
|
|
95
|
+
environmentKey: 'some'
|
|
96
|
+
});
|
|
97
97
|
|
|
98
|
-
|
|
98
|
+
await expect(flg.getEnvironmentFlags()).rejects.toThrow();
|
|
99
99
|
});
|
|
100
100
|
test('test_default_flag_is_not_used_when_environment_flags_returned', async () => {
|
|
101
|
-
|
|
101
|
+
const defaultFlag = new DefaultFlag('some-default-value', true);
|
|
102
102
|
|
|
103
|
-
|
|
103
|
+
const defaultFlagHandler = (featureName: string) => defaultFlag;
|
|
104
104
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
105
|
+
const flg = flagsmith({
|
|
106
|
+
environmentKey: 'key',
|
|
107
|
+
defaultFlagHandler: defaultFlagHandler
|
|
108
|
+
});
|
|
109
109
|
|
|
110
|
-
|
|
111
|
-
|
|
110
|
+
const flags = await flg.getEnvironmentFlags();
|
|
111
|
+
const flag = flags.getFlag('some_feature');
|
|
112
112
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
113
|
+
expect(flag.isDefault).toBe(false);
|
|
114
|
+
expect(flag.value).not.toBe(defaultFlag.value);
|
|
115
|
+
expect(flag.value).toBe('some-value');
|
|
116
116
|
});
|
|
117
117
|
|
|
118
118
|
test('test_default_flag_is_used_when_bad_api_response_happens', async () => {
|
|
119
|
-
|
|
119
|
+
fetch.mockResolvedValue(new Response('bad-data'));
|
|
120
120
|
|
|
121
|
-
|
|
121
|
+
const defaultFlag = new DefaultFlag('some-default-value', true);
|
|
122
122
|
|
|
123
|
-
|
|
123
|
+
const defaultFlagHandler = (featureName: string) => defaultFlag;
|
|
124
124
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
125
|
+
const flg = new Flagsmith({
|
|
126
|
+
environmentKey: 'key',
|
|
127
|
+
defaultFlagHandler: defaultFlagHandler
|
|
128
|
+
});
|
|
129
129
|
|
|
130
|
-
|
|
131
|
-
|
|
130
|
+
const flags = await flg.getEnvironmentFlags();
|
|
131
|
+
const flag = flags.getFlag('some_feature');
|
|
132
132
|
|
|
133
|
-
|
|
134
|
-
|
|
133
|
+
expect(flag.isDefault).toBe(true);
|
|
134
|
+
expect(flag.value).toBe(defaultFlag.value);
|
|
135
135
|
});
|
|
136
136
|
|
|
137
137
|
test('test_local_evaluation', async () => {
|
|
138
|
-
|
|
138
|
+
const defaultFlag = new DefaultFlag('some-default-value', true);
|
|
139
139
|
|
|
140
|
-
|
|
140
|
+
const defaultFlagHandler = (featureName: string) => defaultFlag;
|
|
141
141
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
142
|
+
const flg = flagsmith({
|
|
143
|
+
environmentKey: 'ser.key',
|
|
144
|
+
enableLocalEvaluation: true,
|
|
145
|
+
defaultFlagHandler: defaultFlagHandler
|
|
146
|
+
});
|
|
147
147
|
|
|
148
|
-
|
|
149
|
-
|
|
148
|
+
const flags = await flg.getEnvironmentFlags();
|
|
149
|
+
const flag = flags.getFlag('some_feature');
|
|
150
150
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
151
|
+
expect(flag.isDefault).toBe(false);
|
|
152
|
+
expect(flag.value).not.toBe(defaultFlag.value);
|
|
153
|
+
expect(flag.value).toBe('some-value');
|
|
154
154
|
});
|