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.
Files changed (79) hide show
  1. package/.github/workflows/conventional-commit.yml +29 -0
  2. package/.github/workflows/pull_request.yaml +1 -1
  3. package/.github/workflows/release-please.yml +18 -0
  4. package/.gitmodules +1 -0
  5. package/.husky/pre-commit +0 -0
  6. package/.prettierignore +2 -1
  7. package/.prettierrc.cjs +9 -1
  8. package/.release-please-manifest.json +1 -0
  9. package/CHANGELOG.md +552 -0
  10. package/CODEOWNERS +1 -0
  11. package/README.md +2 -3
  12. package/build/cjs/flagsmith-engine/features/util.js +3 -3
  13. package/build/cjs/flagsmith-engine/index.d.ts +1 -1
  14. package/build/cjs/flagsmith-engine/index.js +2 -1
  15. package/build/cjs/flagsmith-engine/segments/models.js +7 -7
  16. package/build/cjs/flagsmith-engine/utils/hashing/index.js +1 -1
  17. package/build/cjs/index.d.ts +4 -4
  18. package/build/cjs/index.js +3 -1
  19. package/build/cjs/sdk/analytics.d.ts +1 -1
  20. package/build/cjs/sdk/analytics.js +3 -1
  21. package/build/cjs/sdk/index.d.ts +5 -5
  22. package/build/cjs/sdk/index.js +48 -10
  23. package/build/cjs/sdk/models.d.ts +25 -0
  24. package/build/cjs/sdk/models.js +25 -0
  25. package/build/cjs/sdk/types.d.ts +14 -4
  26. package/build/cjs/sdk/utils.d.ts +5 -4
  27. package/build/cjs/sdk/utils.js +16 -3
  28. package/build/esm/flagsmith-engine/features/models.js +1 -1
  29. package/build/esm/flagsmith-engine/features/util.js +3 -3
  30. package/build/esm/flagsmith-engine/index.d.ts +1 -1
  31. package/build/esm/flagsmith-engine/index.js +1 -1
  32. package/build/esm/flagsmith-engine/segments/models.js +7 -7
  33. package/build/esm/flagsmith-engine/utils/hashing/index.js +2 -2
  34. package/build/esm/flagsmith-engine/utils/index.js +1 -1
  35. package/build/esm/index.d.ts +4 -4
  36. package/build/esm/index.js +3 -3
  37. package/build/esm/sdk/analytics.d.ts +1 -1
  38. package/build/esm/sdk/analytics.js +3 -1
  39. package/build/esm/sdk/index.d.ts +5 -5
  40. package/build/esm/sdk/index.js +48 -11
  41. package/build/esm/sdk/models.d.ts +25 -0
  42. package/build/esm/sdk/models.js +25 -0
  43. package/build/esm/sdk/types.d.ts +14 -4
  44. package/build/esm/sdk/utils.d.ts +5 -4
  45. package/build/esm/sdk/utils.js +14 -2
  46. package/flagsmith-engine/environments/util.ts +2 -2
  47. package/flagsmith-engine/features/models.ts +1 -1
  48. package/flagsmith-engine/features/util.ts +14 -14
  49. package/flagsmith-engine/identities/models.ts +1 -1
  50. package/flagsmith-engine/index.ts +1 -1
  51. package/flagsmith-engine/segments/evaluators.ts +2 -3
  52. package/flagsmith-engine/segments/models.ts +25 -15
  53. package/flagsmith-engine/utils/hashing/index.ts +3 -3
  54. package/flagsmith-engine/utils/index.ts +4 -2
  55. package/index.ts +19 -22
  56. package/package.json +1 -1
  57. package/release-please-config.json +62 -0
  58. package/sdk/analytics.ts +10 -6
  59. package/sdk/index.ts +91 -28
  60. package/sdk/models.ts +25 -0
  61. package/sdk/offline_handlers.ts +1 -1
  62. package/sdk/types.ts +17 -8
  63. package/sdk/utils.ts +21 -8
  64. package/tests/engine/e2e/engine.test.ts +2 -4
  65. package/tests/engine/unit/engine.test.ts +1 -6
  66. package/tests/engine/unit/features/models.test.ts +2 -2
  67. package/tests/engine/unit/identities/identities_builders.test.ts +1 -1
  68. package/tests/engine/unit/segments/segment_evaluators.test.ts +52 -23
  69. package/tests/engine/unit/segments/segments_model.test.ts +35 -37
  70. package/tests/engine/unit/utils/utils.test.ts +28 -30
  71. package/tests/sdk/analytics.test.ts +30 -26
  72. package/tests/sdk/flagsmith-cache.test.ts +84 -76
  73. package/tests/sdk/flagsmith-environment-flags.test.ts +121 -93
  74. package/tests/sdk/flagsmith-identity-flags.test.ts +155 -149
  75. package/tests/sdk/flagsmith.test.ts +202 -43
  76. package/tests/sdk/offline-handlers.test.ts +32 -32
  77. package/tests/sdk/polling.test.ts +0 -1
  78. package/tests/sdk/utils.ts +26 -18
  79. package/vitest.config.ts +10 -15
@@ -1,44 +1,70 @@
1
1
  import {
2
2
  ALL_RULE,
3
3
  CONDITION_OPERATORS,
4
- PERCENTAGE_SPLIT,
4
+ PERCENTAGE_SPLIT
5
5
  } from '../../../../flagsmith-engine/segments/constants.js';
6
- import {SegmentConditionModel} from '../../../../flagsmith-engine/segments/models.js';
7
- import {traitsMatchSegmentCondition, evaluateIdentityInSegment} from "../../../../flagsmith-engine/segments/evaluators.js";
8
- import {TraitModel, IdentityModel} from "../../../../flagsmith-engine/index.js";
9
- import {environment} from "../utils.js";
6
+ import { SegmentConditionModel } from '../../../../flagsmith-engine/segments/models.js';
7
+ import {
8
+ traitsMatchSegmentCondition,
9
+ evaluateIdentityInSegment
10
+ } from '../../../../flagsmith-engine/segments/evaluators.js';
11
+ import { TraitModel, IdentityModel } from '../../../../flagsmith-engine/index.js';
12
+ import { environment } from '../utils.js';
10
13
  import { buildSegmentModel } from '../../../../flagsmith-engine/segments/util.js';
11
14
  import { getHashedPercentateForObjIds } from '../../../../flagsmith-engine/utils/hashing/index.js';
12
15
 
13
-
14
16
  // todo: work out how to implement this in a test function or before hook
15
17
  vi.mock('../../../../flagsmith-engine/utils/hashing', () => ({
16
18
  getHashedPercentateForObjIds: vi.fn(() => 1)
17
19
  }));
18
20
 
19
-
20
- let traitExistenceTestCases: [string, string | null | undefined, string | null | undefined, TraitModel [],boolean][] = [
21
- [CONDITION_OPERATORS.IS_SET,'foo', null,[] , false],
22
- [CONDITION_OPERATORS.IS_SET, 'foo',undefined , [new TraitModel('foo','bar')], true],
23
- [CONDITION_OPERATORS.IS_SET, 'foo',undefined , [new TraitModel('foo','bar'), new TraitModel('fooBaz','baz')], true],
21
+ let traitExistenceTestCases: [
22
+ string,
23
+ string | null | undefined,
24
+ string | null | undefined,
25
+ TraitModel[],
26
+ boolean
27
+ ][] = [
28
+ [CONDITION_OPERATORS.IS_SET, 'foo', null, [], false],
29
+ [CONDITION_OPERATORS.IS_SET, 'foo', undefined, [new TraitModel('foo', 'bar')], true],
30
+ [
31
+ CONDITION_OPERATORS.IS_SET,
32
+ 'foo',
33
+ undefined,
34
+ [new TraitModel('foo', 'bar'), new TraitModel('fooBaz', 'baz')],
35
+ true
36
+ ],
24
37
  [CONDITION_OPERATORS.IS_NOT_SET, 'foo', undefined, [], true],
25
- [CONDITION_OPERATORS.IS_NOT_SET, 'foo', null, [new TraitModel('foo','bar')], false],
26
- [CONDITION_OPERATORS.IS_NOT_SET, 'foo', null, [new TraitModel('foo','bar'), new TraitModel('fooBaz','baz')], false]
38
+ [CONDITION_OPERATORS.IS_NOT_SET, 'foo', null, [new TraitModel('foo', 'bar')], false],
39
+ [
40
+ CONDITION_OPERATORS.IS_NOT_SET,
41
+ 'foo',
42
+ null,
43
+ [new TraitModel('foo', 'bar'), new TraitModel('fooBaz', 'baz')],
44
+ false
45
+ ]
27
46
  ];
28
47
 
29
48
  test('test_traits_match_segment_condition_for_trait_existence_operators', () => {
30
49
  for (const testCase of traitExistenceTestCases) {
31
- const [operator, conditionProperty, conditionValue, traits, expectedResult] = testCase
32
- let segmentModel = new SegmentConditionModel(operator, conditionValue, conditionProperty)
33
- expect(
34
- traitsMatchSegmentCondition (traits, segmentModel, 'any','any')
35
- ).toBe(expectedResult);
50
+ const [operator, conditionProperty, conditionValue, traits, expectedResult] = testCase;
51
+ let segmentModel = new SegmentConditionModel(operator, conditionValue, conditionProperty);
52
+ expect(traitsMatchSegmentCondition(traits, segmentModel, 'any', 'any')).toBe(
53
+ expectedResult
54
+ );
36
55
  }
37
56
  });
38
57
 
39
-
40
58
  test('evaluateIdentityInSegment uses django ID for hashed percentage when present', () => {
41
- var identityModel = new IdentityModel(Date.now().toString(), [], [], environment().apiKey, 'identity_1', undefined, 1);
59
+ var identityModel = new IdentityModel(
60
+ Date.now().toString(),
61
+ [],
62
+ [],
63
+ environment().apiKey,
64
+ 'identity_1',
65
+ undefined,
66
+ 1
67
+ );
42
68
  const segmentDefinition = {
43
69
  id: 1,
44
70
  name: 'percentage_split_segment',
@@ -49,7 +75,7 @@ test('evaluateIdentityInSegment uses django ID for hashed percentage when presen
49
75
  {
50
76
  operator: PERCENTAGE_SPLIT,
51
77
  property_: null,
52
- value: "10"
78
+ value: '10'
53
79
  }
54
80
  ],
55
81
  rules: []
@@ -62,6 +88,9 @@ test('evaluateIdentityInSegment uses django ID for hashed percentage when presen
62
88
  var result = evaluateIdentityInSegment(identityModel, segmentModel);
63
89
 
64
90
  expect(result).toBe(true);
65
- expect(getHashedPercentateForObjIds).toHaveBeenCalledTimes(1)
66
- expect(getHashedPercentateForObjIds).toHaveBeenCalledWith([segmentModel.id, identityModel.djangoID])
91
+ expect(getHashedPercentateForObjIds).toHaveBeenCalledTimes(1);
92
+ expect(getHashedPercentateForObjIds).toHaveBeenCalledWith([
93
+ segmentModel.id,
94
+ identityModel.djangoID
95
+ ]);
67
96
  });
@@ -65,49 +65,47 @@ const conditionMatchCases: [string, string | number | boolean | null, string, bo
65
65
  [CONDITION_OPERATORS.REGEX, 'foo', '[a-z]+', true],
66
66
  [CONDITION_OPERATORS.REGEX, 'FOO', '[a-z]+', false],
67
67
  [CONDITION_OPERATORS.REGEX, null, '[a-z]+', false],
68
- [CONDITION_OPERATORS.EQUAL, "1.0.0", "1.0.0:semver", true],
69
- [CONDITION_OPERATORS.EQUAL, "1.0.0", "1.0.0:semver", true],
70
- [CONDITION_OPERATORS.EQUAL, "1.0.0", "1.0.1:semver", false],
71
- [CONDITION_OPERATORS.NOT_EQUAL, "1.0.0", "1.0.0:semver", false],
72
- [CONDITION_OPERATORS.NOT_EQUAL, "1.0.0", "1.0.1:semver", true],
73
- [CONDITION_OPERATORS.GREATER_THAN, "1.0.1", "1.0.0:semver", true],
74
- [CONDITION_OPERATORS.GREATER_THAN, "1.0.0", "1.0.0-beta:semver", true],
75
- [CONDITION_OPERATORS.GREATER_THAN, "1.0.1", "1.2.0:semver", false],
76
- [CONDITION_OPERATORS.GREATER_THAN, "1.0.1", "1.0.1:semver", false],
77
- [CONDITION_OPERATORS.GREATER_THAN, "1.2.4", "1.2.3-pre.2+build.4:semver", true],
78
- [CONDITION_OPERATORS.LESS_THAN, "1.0.0", "1.0.1:semver", true],
79
- [CONDITION_OPERATORS.LESS_THAN, "1.0.0", "1.0.0:semver", false],
80
- [CONDITION_OPERATORS.LESS_THAN, "1.0.1", "1.0.0:semver", false],
81
- [CONDITION_OPERATORS.LESS_THAN, "1.0.0-rc.2", "1.0.0-rc.3:semver", true],
82
- [CONDITION_OPERATORS.GREATER_THAN_INCLUSIVE, "1.0.1", "1.0.0:semver", true],
83
- [CONDITION_OPERATORS.GREATER_THAN_INCLUSIVE, "1.0.1", "1.2.0:semver", false],
84
- [CONDITION_OPERATORS.GREATER_THAN_INCLUSIVE, "1.0.1", "1.0.1:semver", true],
85
- [CONDITION_OPERATORS.LESS_THAN_INCLUSIVE, "1.0.0", "1.0.1:semver", true],
86
- [CONDITION_OPERATORS.LESS_THAN_INCLUSIVE, "1.0.0", "1.0.0:semver", true],
87
- [CONDITION_OPERATORS.LESS_THAN_INCLUSIVE, "1.0.1", "1.0.0:semver", false],
88
- [CONDITION_OPERATORS.MODULO, 1, "2|0", false],
89
- [CONDITION_OPERATORS.MODULO, 2, "2|0", true],
90
- [CONDITION_OPERATORS.MODULO, 3, "2|0", false],
91
- [CONDITION_OPERATORS.MODULO, 34.2, "4|3", false],
92
- [CONDITION_OPERATORS.MODULO, 35.0, "4|3", true],
93
- [CONDITION_OPERATORS.MODULO, "foo", "4|3", false],
94
- [CONDITION_OPERATORS.MODULO, 35.0, "foo|bar", false],
95
- [CONDITION_OPERATORS.IN, "foo", "", false],
96
- [CONDITION_OPERATORS.IN, "foo", "foo, bar", true],
97
- [CONDITION_OPERATORS.IN, "foo", "foo", true],
98
- [CONDITION_OPERATORS.IN, 1, "1,2,3,4", true],
99
- [CONDITION_OPERATORS.IN, 1, "", false],
100
- [CONDITION_OPERATORS.IN, 1, "1", true],
68
+ [CONDITION_OPERATORS.EQUAL, '1.0.0', '1.0.0:semver', true],
69
+ [CONDITION_OPERATORS.EQUAL, '1.0.0', '1.0.0:semver', true],
70
+ [CONDITION_OPERATORS.EQUAL, '1.0.0', '1.0.1:semver', false],
71
+ [CONDITION_OPERATORS.NOT_EQUAL, '1.0.0', '1.0.0:semver', false],
72
+ [CONDITION_OPERATORS.NOT_EQUAL, '1.0.0', '1.0.1:semver', true],
73
+ [CONDITION_OPERATORS.GREATER_THAN, '1.0.1', '1.0.0:semver', true],
74
+ [CONDITION_OPERATORS.GREATER_THAN, '1.0.0', '1.0.0-beta:semver', true],
75
+ [CONDITION_OPERATORS.GREATER_THAN, '1.0.1', '1.2.0:semver', false],
76
+ [CONDITION_OPERATORS.GREATER_THAN, '1.0.1', '1.0.1:semver', false],
77
+ [CONDITION_OPERATORS.GREATER_THAN, '1.2.4', '1.2.3-pre.2+build.4:semver', true],
78
+ [CONDITION_OPERATORS.LESS_THAN, '1.0.0', '1.0.1:semver', true],
79
+ [CONDITION_OPERATORS.LESS_THAN, '1.0.0', '1.0.0:semver', false],
80
+ [CONDITION_OPERATORS.LESS_THAN, '1.0.1', '1.0.0:semver', false],
81
+ [CONDITION_OPERATORS.LESS_THAN, '1.0.0-rc.2', '1.0.0-rc.3:semver', true],
82
+ [CONDITION_OPERATORS.GREATER_THAN_INCLUSIVE, '1.0.1', '1.0.0:semver', true],
83
+ [CONDITION_OPERATORS.GREATER_THAN_INCLUSIVE, '1.0.1', '1.2.0:semver', false],
84
+ [CONDITION_OPERATORS.GREATER_THAN_INCLUSIVE, '1.0.1', '1.0.1:semver', true],
85
+ [CONDITION_OPERATORS.LESS_THAN_INCLUSIVE, '1.0.0', '1.0.1:semver', true],
86
+ [CONDITION_OPERATORS.LESS_THAN_INCLUSIVE, '1.0.0', '1.0.0:semver', true],
87
+ [CONDITION_OPERATORS.LESS_THAN_INCLUSIVE, '1.0.1', '1.0.0:semver', false],
88
+ [CONDITION_OPERATORS.MODULO, 1, '2|0', false],
89
+ [CONDITION_OPERATORS.MODULO, 2, '2|0', true],
90
+ [CONDITION_OPERATORS.MODULO, 3, '2|0', false],
91
+ [CONDITION_OPERATORS.MODULO, 34.2, '4|3', false],
92
+ [CONDITION_OPERATORS.MODULO, 35.0, '4|3', true],
93
+ [CONDITION_OPERATORS.MODULO, 'foo', '4|3', false],
94
+ [CONDITION_OPERATORS.MODULO, 35.0, 'foo|bar', false],
95
+ [CONDITION_OPERATORS.IN, 'foo', '', false],
96
+ [CONDITION_OPERATORS.IN, 'foo', 'foo, bar', true],
97
+ [CONDITION_OPERATORS.IN, 'foo', 'foo', true],
98
+ [CONDITION_OPERATORS.IN, 1, '1,2,3,4', true],
99
+ [CONDITION_OPERATORS.IN, 1, '', false],
100
+ [CONDITION_OPERATORS.IN, 1, '1', true],
101
101
  ['BAD_OP', 'a', 'a', false]
102
102
  ];
103
103
 
104
104
  test('test_segment_condition_matches_trait_value', () => {
105
105
  for (const testCase of conditionMatchCases) {
106
- const [operator, traitValue, conditionValue, expectedResult] = testCase
106
+ const [operator, traitValue, conditionValue, expectedResult] = testCase;
107
107
  expect(
108
- new SegmentConditionModel(operator, conditionValue, 'foo').matchesTraitValue(
109
- traitValue
110
- )
108
+ new SegmentConditionModel(operator, conditionValue, 'foo').matchesTraitValue(traitValue)
111
109
  ).toBe(expectedResult);
112
110
  }
113
111
  });
@@ -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
- [[12, 93]],
7
- [[uuidv4(), 99]],
8
- [[99, uuidv4()]],
9
- [[uuidv4(), uuidv4()]]
10
- ])('returns x where 0 <= x < 100', (objIds: (string|number)[]) => {
11
- let result = getHashedPercentateForObjIds(objIds);
12
- expect(result).toBeLessThan(100);
13
- expect(result).toBeGreaterThanOrEqual(0);
14
- });
15
-
16
- it.each([
17
- [[12, 93]],
18
- [[uuidv4(), 99]],
19
- [[99, uuidv4()]],
20
- [[uuidv4(), uuidv4()]]
21
- ])('returns the same value each time', (objIds: (string|number)[]) => {
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 => Array.from(Array(testSample).keys()).map(e => [d, e].flat()))
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((objIds) => getHashedPercentateForObjIds(objIds));
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 + ((i + 1) / numTestBuckets),
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,48 @@
1
- import {analyticsProcessor, fetch} from './utils.js';
1
+ import { getUserAgent } from '../../sdk/utils.js';
2
+ import { analyticsProcessor, fetch } from './utils.js';
2
3
 
3
4
  test('test_analytics_processor_track_feature_updates_analytics_data', () => {
4
5
  const aP = analyticsProcessor();
5
- aP.trackFeature("myFeature");
6
- expect(aP.analyticsData["myFeature"]).toBe(1);
6
+ aP.trackFeature('myFeature');
7
+ expect(aP.analyticsData['myFeature']).toBe(1);
7
8
 
8
- aP.trackFeature("myFeature");
9
- expect(aP.analyticsData["myFeature"]).toBe(2);
9
+ aP.trackFeature('myFeature');
10
+ expect(aP.analyticsData['myFeature']).toBe(2);
10
11
  });
11
12
 
12
13
  test('test_analytics_processor_flush_clears_analytics_data', async () => {
13
14
  const aP = analyticsProcessor();
14
- aP.trackFeature("myFeature");
15
+ aP.trackFeature('myFeature');
15
16
  await aP.flush();
16
17
  expect(aP.analyticsData).toStrictEqual({});
17
18
  });
18
19
 
19
20
  test('test_analytics_processor_flush_post_request_data_match_ananlytics_data', async () => {
20
21
  const aP = analyticsProcessor();
21
- aP.trackFeature("myFeature1");
22
- aP.trackFeature("myFeature2");
22
+ aP.trackFeature('myFeature1');
23
+ aP.trackFeature('myFeature2');
23
24
  await aP.flush();
24
25
  expect(fetch).toHaveBeenCalledTimes(1);
25
- expect(fetch).toHaveBeenCalledWith('http://testUrl/analytics/flags/', expect.objectContaining({
26
- body: '{"myFeature1":1,"myFeature2":1}',
27
- headers: { 'Content-Type': 'application/json', 'X-Environment-Key': 'test-key' },
28
- method: 'POST',
29
- }));
26
+ expect(fetch).toHaveBeenCalledWith(
27
+ 'http://testUrl/analytics/flags/',
28
+ expect.objectContaining({
29
+ body: '{"myFeature1":1,"myFeature2":1}',
30
+ headers: {
31
+ 'Content-Type': 'application/json',
32
+ 'X-Environment-Key': 'test-key',
33
+ 'User-Agent': getUserAgent()
34
+ },
35
+ method: 'POST'
36
+ })
37
+ );
30
38
  });
31
39
 
32
- vi.useFakeTimers()
40
+ vi.useFakeTimers();
33
41
  test('test_analytics_processor_flush_post_request_data_match_ananlytics_data_test', async () => {
34
42
  const aP = analyticsProcessor();
35
- aP.trackFeature("myFeature1");
43
+ aP.trackFeature('myFeature1');
36
44
  setTimeout(() => {
37
- aP.trackFeature("myFeature2");
45
+ aP.trackFeature('myFeature2');
38
46
  expect(fetch).toHaveBeenCalledTimes(1);
39
47
  }, 15000);
40
48
  vi.runOnlyPendingTimers();
@@ -46,7 +54,6 @@ test('test_analytics_processor_flush_early_exit_if_analytics_data_is_empty', asy
46
54
  expect(fetch).not.toHaveBeenCalled();
47
55
  });
48
56
 
49
-
50
57
  test('errors in fetch sending analytics data are swallowed', async () => {
51
58
  // Given
52
59
  // we mock the fetch function to throw and error to mimick a network failure
@@ -63,21 +70,18 @@ test('errors in fetch sending analytics data are swallowed', async () => {
63
70
  // Then
64
71
  // we expect that fetch was called but the exception was handled
65
72
  expect(fetch).toHaveBeenCalled();
66
- })
73
+ });
67
74
 
68
75
  test('analytics is only flushed once even if requested concurrently', async () => {
69
76
  const processor = analyticsProcessor();
70
77
  processor.trackFeature('myFeature');
71
78
  fetch.mockImplementation(() => {
72
79
  return new Promise((resolve, _) => {
73
- setTimeout(resolve, 1000)
74
- })
80
+ setTimeout(resolve, 1000);
81
+ });
75
82
  });
76
- const flushes = Promise.all([
77
- processor.flush(),
78
- processor.flush(),
79
- ])
83
+ const flushes = Promise.all([processor.flush(), processor.flush()]);
80
84
  vi.runOnlyPendingTimers();
81
85
  await flushes;
82
- expect(fetch).toHaveBeenCalledTimes(1)
83
- })
86
+ expect(fetch).toHaveBeenCalledTimes(1);
87
+ });
@@ -1,113 +1,121 @@
1
- import { fetch, environmentJSON, environmentModel, flagsJSON, flagsmith, identitiesJSON, TestCache } from './utils.js';
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
- const cache = new TestCache();
5
- const set = vi.spyOn(cache, 'set');
12
+ const cache = new TestCache();
13
+ const set = vi.spyOn(cache, 'set');
6
14
 
7
- const flg = flagsmith({ cache });
8
- const allFlags = (await flg.getEnvironmentFlags()).allFlags();
15
+ const flg = flagsmith({ cache });
16
+ const allFlags = (await flg.getEnvironmentFlags()).allFlags();
9
17
 
10
- expect(set).toBeCalled();
11
- expect(await cache.get('flags')).toBeTruthy();
18
+ expect(set).toBeCalled();
19
+ expect(await cache.get('flags')).toBeTruthy();
12
20
 
13
- expect(fetch).toBeCalledTimes(1);
14
- expect(allFlags[0].enabled).toBe(true);
15
- expect(allFlags[0].value).toBe('some-value');
16
- expect(allFlags[0].featureName).toBe('some_feature');
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
- const cache = new TestCache();
21
- const set = vi.spyOn(cache, 'set');
28
+ const cache = new TestCache();
29
+ const set = vi.spyOn(cache, 'set');
22
30
 
23
- const flg = flagsmith({ cache });
24
- await (await flg.getEnvironmentFlags()).allFlags();
25
- const allFlags = await (await flg.getEnvironmentFlags()).allFlags();
31
+ const flg = flagsmith({ cache });
32
+ await (await flg.getEnvironmentFlags()).allFlags();
33
+ const allFlags = await (await flg.getEnvironmentFlags()).allFlags();
26
34
 
27
- expect(set).toBeCalled();
28
- expect(await cache.get('flags')).toBeTruthy();
35
+ expect(set).toBeCalled();
36
+ expect(await cache.get('flags')).toBeTruthy();
29
37
 
30
- expect(fetch).toBeCalledTimes(1);
31
- expect(allFlags[0].enabled).toBe(true);
32
- expect(allFlags[0].value).toBe('some-value');
33
- expect(allFlags[0].featureName).toBe('some_feature');
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
- fetch.mockImplementation(() => Promise.resolve(new Response(flagsJSON)));
45
+ fetch.mockImplementation(() => Promise.resolve(new Response(flagsJSON)));
38
46
 
39
- const flg = flagsmith();
40
- await (await flg.getEnvironmentFlags()).allFlags();
47
+ const flg = flagsmith();
48
+ await (await flg.getEnvironmentFlags()).allFlags();
41
49
 
42
- const allFlags = await (await flg.getEnvironmentFlags()).allFlags();
50
+ const allFlags = await (await flg.getEnvironmentFlags()).allFlags();
43
51
 
44
- expect(fetch).toBeCalledTimes(2);
45
- expect(allFlags[0].enabled).toBe(true);
46
- expect(allFlags[0].value).toBe('some-value');
47
- expect(allFlags[0].featureName).toBe('some_feature');
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
- const cache = new TestCache();
52
- const set = vi.spyOn(cache, 'set');
53
-
54
- const flg = flagsmith({ cache, environmentKey: 'ser.key', enableLocalEvaluation: true });
55
- const model = environmentModel(JSON.parse(environmentJSON));
56
- const getEnvironment = vi.spyOn(flg, 'getEnvironment')
57
- getEnvironment.mockResolvedValue(model)
58
-
59
- const allFlags = (await flg.getEnvironmentFlags()).allFlags();
60
-
61
- expect(set).toBeCalled();
62
- expect(fetch).toBeCalledTimes(0);
63
- expect(getEnvironment).toBeCalledTimes(1);
64
- expect(allFlags[0].enabled).toBe(model.featureStates[0].enabled);
65
- expect(allFlags[0].value).toBe(model.featureStates[0].getValue());
66
- expect(allFlags[0].featureName).toBe(model.featureStates[0].feature.name);
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
- const cache = new TestCache();
71
- const set = vi.spyOn(cache, 'set');
78
+ const cache = new TestCache();
79
+ const set = vi.spyOn(cache, 'set');
72
80
 
73
- const identifier = 'identifier';
74
- const traits = { some_trait: 'some_value' };
75
- const flg = flagsmith({ cache });
81
+ const identifier = 'identifier';
82
+ const traits = { some_trait: 'some_value' };
83
+ const flg = flagsmith({ cache });
76
84
 
77
- (await flg.getIdentityFlags(identifier, traits)).allFlags();
78
- const identityFlags = (await flg.getIdentityFlags(identifier, traits)).allFlags();
85
+ (await flg.getIdentityFlags(identifier, traits)).allFlags();
86
+ const identityFlags = (await flg.getIdentityFlags(identifier, traits)).allFlags();
79
87
 
80
- expect(set).toBeCalled();
81
- expect(await cache.get('flags-identifier')).toBeTruthy();
88
+ expect(set).toBeCalled();
89
+ expect(await cache.get('flags-identifier')).toBeTruthy();
82
90
 
83
- expect(fetch).toBeCalledTimes(1);
91
+ expect(fetch).toBeCalledTimes(1);
84
92
 
85
- expect(identityFlags[0].enabled).toBe(true);
86
- expect(identityFlags[0].value).toBe('some-value');
87
- expect(identityFlags[0].featureName).toBe('some_feature');
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
- const cache = new TestCache();
92
- const set = vi.spyOn(cache, 'set');
99
+ const cache = new TestCache();
100
+ const set = vi.spyOn(cache, 'set');
93
101
 
94
- const identifier = 'identifier';
95
- const traits = { some_trait: 'some_value' };
96
- const flg = flagsmith({
97
- cache,
98
- environmentKey: 'ser.key',
99
- enableLocalEvaluation: true,
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
- (await flg.getIdentityFlags(identifier, traits)).allFlags();
103
- const identityFlags = (await flg.getIdentityFlags(identifier, traits)).allFlags();
110
+ (await flg.getIdentityFlags(identifier, traits)).allFlags();
111
+ const identityFlags = (await flg.getIdentityFlags(identifier, traits)).allFlags();
104
112
 
105
- expect(set).toBeCalled();
106
- expect(await cache.get('flags-identifier')).toBeTruthy();
113
+ expect(set).toBeCalled();
114
+ expect(await cache.get('flags-identifier')).toBeTruthy();
107
115
 
108
- expect(fetch).toBeCalledTimes(1);
116
+ expect(fetch).toBeCalledTimes(1);
109
117
 
110
- expect(identityFlags[0].enabled).toBe(true);
111
- expect(identityFlags[0].value).toBe('some-value');
112
- expect(identityFlags[0].featureName).toBe('some_feature');
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
  });