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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (96) hide show
  1. package/.github/workflows/pull_request.yaml +37 -29
  2. package/.idea/flagsmith-nodejs-client.iml +12 -0
  3. package/.idea/modules.xml +8 -0
  4. package/.idea/vcs.xml +6 -0
  5. package/README.md +7 -0
  6. package/build/flagsmith-engine/environments/integrations/models.d.ts +4 -0
  7. package/build/flagsmith-engine/environments/integrations/models.js +11 -0
  8. package/build/flagsmith-engine/environments/models.d.ts +25 -0
  9. package/build/flagsmith-engine/environments/models.js +29 -0
  10. package/build/flagsmith-engine/environments/util.d.ts +3 -0
  11. package/build/flagsmith-engine/environments/util.js +21 -0
  12. package/build/flagsmith-engine/features/constants.d.ts +4 -0
  13. package/build/flagsmith-engine/features/constants.js +7 -0
  14. package/build/flagsmith-engine/features/models.d.ts +31 -0
  15. package/build/flagsmith-engine/features/models.js +99 -0
  16. package/build/flagsmith-engine/features/util.d.ts +3 -0
  17. package/build/flagsmith-engine/features/util.js +20 -0
  18. package/build/flagsmith-engine/identities/models.d.ts +15 -0
  19. package/build/flagsmith-engine/identities/models.js +112 -0
  20. package/build/flagsmith-engine/identities/traits/models.d.ts +5 -0
  21. package/build/flagsmith-engine/identities/traits/models.js +11 -0
  22. package/build/flagsmith-engine/identities/util.d.ts +4 -0
  23. package/build/flagsmith-engine/identities/util.js +46 -0
  24. package/build/flagsmith-engine/index.d.ts +8 -0
  25. package/build/flagsmith-engine/index.js +113 -0
  26. package/build/flagsmith-engine/organisations/models.d.ts +9 -0
  27. package/build/flagsmith-engine/organisations/models.js +21 -0
  28. package/build/flagsmith-engine/organisations/util.d.ts +2 -0
  29. package/build/flagsmith-engine/organisations/util.js +8 -0
  30. package/build/flagsmith-engine/projects/models.d.ts +10 -0
  31. package/build/flagsmith-engine/projects/models.js +14 -0
  32. package/build/flagsmith-engine/projects/util.d.ts +2 -0
  33. package/build/flagsmith-engine/projects/util.js +15 -0
  34. package/build/flagsmith-engine/segments/constants.d.ts +26 -0
  35. package/build/flagsmith-engine/segments/constants.js +31 -0
  36. package/build/flagsmith-engine/segments/evaluators.d.ts +6 -0
  37. package/build/flagsmith-engine/segments/evaluators.js +37 -0
  38. package/build/flagsmith-engine/segments/models.d.ts +31 -0
  39. package/build/flagsmith-engine/segments/models.js +89 -0
  40. package/build/flagsmith-engine/segments/util.d.ts +4 -0
  41. package/build/flagsmith-engine/segments/util.js +25 -0
  42. package/build/flagsmith-engine/utils/collections.d.ts +3 -0
  43. package/build/flagsmith-engine/utils/collections.js +26 -0
  44. package/build/flagsmith-engine/utils/errors.d.ts +2 -0
  45. package/build/flagsmith-engine/utils/errors.js +26 -0
  46. package/build/flagsmith-engine/utils/hashing/index.d.ts +9 -0
  47. package/build/flagsmith-engine/utils/hashing/index.js +60 -0
  48. package/build/flagsmith-engine/utils/index.d.ts +1 -0
  49. package/build/flagsmith-engine/utils/index.js +14 -0
  50. package/build/index.d.ts +1 -0
  51. package/build/index.js +11 -0
  52. package/build/sdk/analytics.d.ts +28 -0
  53. package/build/sdk/analytics.js +102 -0
  54. package/build/sdk/errors.d.ts +4 -0
  55. package/build/sdk/errors.js +34 -0
  56. package/build/sdk/index.d.ts +123 -0
  57. package/build/sdk/index.js +484 -0
  58. package/build/sdk/models.d.ts +55 -0
  59. package/build/sdk/models.js +149 -0
  60. package/build/sdk/polling_manager.d.ts +9 -0
  61. package/build/sdk/polling_manager.js +72 -0
  62. package/build/sdk/types.d.ts +7 -0
  63. package/build/sdk/types.js +2 -0
  64. package/build/sdk/utils.d.ts +12 -0
  65. package/build/sdk/utils.js +94 -0
  66. package/example/package-lock.json +1 -996
  67. package/flagsmith-engine/features/models.ts +0 -4
  68. package/flagsmith-engine/identities/models.ts +1 -1
  69. package/flagsmith-engine/index.ts +1 -1
  70. package/flagsmith-engine/organisations/models.ts +1 -1
  71. package/flagsmith-engine/segments/models.ts +0 -2
  72. package/flagsmith-engine/utils/collections.ts +1 -12
  73. package/flagsmith-engine/utils/hashing/index.ts +5 -2
  74. package/package.json +3 -2
  75. package/sdk/analytics.ts +0 -2
  76. package/sdk/index.ts +83 -26
  77. package/sdk/polling_manager.ts +0 -1
  78. package/sdk/types.ts +8 -0
  79. package/tests/engine/unit/{egine.test.ts → engine.test.ts} +20 -0
  80. package/tests/engine/unit/features/models.test.ts +7 -3
  81. package/tests/engine/unit/identities/identities_builders.test.ts +13 -0
  82. package/tests/engine/unit/identities/identities_models.test.ts +3 -14
  83. package/tests/engine/unit/organization/models.test.ts +1 -1
  84. package/tests/engine/unit/segments/segments_model.test.ts +2 -1
  85. package/tests/engine/unit/utils.ts +1 -1
  86. package/tests/sdk/analytics.test.ts +11 -0
  87. package/tests/sdk/flagsmith-cache.test.ts +150 -0
  88. package/tests/sdk/flagsmith-environment-flags.test.ts +197 -0
  89. package/tests/sdk/flagsmith-identity-flags.test.ts +140 -0
  90. package/tests/sdk/flagsmith.test.ts +100 -85
  91. package/tests/sdk/polling.test.ts +25 -0
  92. package/tests/sdk/utils.ts +21 -2
  93. package/tsconfig.json +2 -1
  94. package/.tool-versions +0 -1
  95. package/tests/engine/engine-tests/engine-test-data/data/environment_n9fbf9h3v4fFgH3U3ngWhb.json +0 -12393
  96. package/tests/engine/engine-tests/engine-test-data/readme.md +0 -30
@@ -79,10 +79,6 @@ export class FeatureStateModel {
79
79
  return this.value;
80
80
  }
81
81
 
82
- get_feature_state_value() {
83
- return this.getValue();
84
- }
85
-
86
82
  getMultivariateValue(identityID: number | string) {
87
83
  const percentageValue = getHashedPercentateForObjIds([
88
84
  this.djangoID || this.featurestateUUID,
@@ -37,7 +37,7 @@ export class IdentityModel {
37
37
  return `${env_key}_${identifier}`;
38
38
  }
39
39
 
40
- update_traits(traits: TraitModel[]) {
40
+ updateTraits(traits: TraitModel[]) {
41
41
  const existingTraits: Map<string, TraitModel> = new Map();
42
42
  for (const trait of this.identityTraits) {
43
43
  existingTraits.set(trait.traitKey, trait);
@@ -79,7 +79,7 @@ export function getEnvironmentFeatureState(environment: EnvironmentModel, featur
79
79
  const featuresStates = environment.featureStates.filter(f => f.feature.name === featureName);
80
80
 
81
81
  if (featuresStates.length === 0) {
82
- throw new Error('Feature State Not Found');
82
+ throw new FeatureStateNotFound('Feature State Not Found');
83
83
  }
84
84
 
85
85
  return featuresStates[0];
@@ -19,7 +19,7 @@ export class OrganisationModel {
19
19
  this.persistTraitData = persistTraitData;
20
20
  }
21
21
 
22
- get unique_slug() {
22
+ get uniqueSlug() {
23
23
  return this.id.toString() + '-' + this.name;
24
24
  }
25
25
  }
@@ -23,8 +23,6 @@ export const matchingFunctions = {
23
23
  [CONDITION_OPERATORS.NOT_EQUAL]: (thisValue: any, otherValue: any) => thisValue != otherValue,
24
24
  [CONDITION_OPERATORS.CONTAINS]: (thisValue: any, otherValue: any) =>
25
25
  otherValue.includes(thisValue),
26
- [CONDITION_OPERATORS.NOT_CONTAINS]: (thisValue: any, otherValue: any) =>
27
- !otherValue.includes(thisValue)
28
26
  };
29
27
 
30
28
  export class SegmentConditionModel {
@@ -1,14 +1,3 @@
1
1
  import { FeatureStateModel } from '../features/models';
2
2
 
3
- export class IdentityFeaturesList extends Array<FeatureStateModel> {
4
- public push(...e: FeatureStateModel[]): number {
5
- for (const [_, item] of e.entries()) {
6
- for (const [k, v] of this.entries()) {
7
- if (v.djangoID === item.djangoID) {
8
- throw new Error('feature state for this feature already exists');
9
- }
10
- }
11
- }
12
- return super.push(...e);
13
- }
14
- }
3
+ export class IdentityFeaturesList extends Array<FeatureStateModel> {}
@@ -39,12 +39,15 @@ function h2d(s: any): string {
39
39
  * @returns number number between 0 (inclusive) and 100 (exclusive)
40
40
  */
41
41
  export function getHashedPercentateForObjIds(objectIds: Array<any>, iterations = 1): number {
42
- let to_hash = makeRepeated(objectIds, iterations).join(',');
43
- const hashedValue = md5(to_hash);
42
+ let toHash = makeRepeated(objectIds, iterations).join(',');
43
+ const hashedValue = md5(toHash);
44
44
  const hashedInt = bigInt(h2d(hashedValue));
45
45
  const value = (hashedInt.mod(9999).toJSNumber() / 9998) * 100;
46
46
 
47
+ // we ignore this for it's nearly impossible use case to catch
48
+ /* istanbul ignore next */
47
49
  if (value === 100) {
50
+ /* istanbul ignore next */
48
51
  return getHashedPercentateForObjIds(objectIds, iterations + 1);
49
52
  }
50
53
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "flagsmith-nodejs",
3
- "version": "2.0.0-beta.4",
3
+ "version": "2.0.0-beta.7",
4
4
  "description": "Flagsmith lets you manage features flags and remote config across web, mobile and server side applications. Deliver true Continuous Integration. Get builds out faster. Control who has access to new features.",
5
5
  "main": "build/index.js",
6
6
  "repository": {
@@ -46,6 +46,7 @@
46
46
  "test:watch": "jest --coverage --watch --coverageReporters='text'",
47
47
  "test:debug": "node --inspect-brk node_modules/.bin/jest --coverage --watch --coverageReporters='text'",
48
48
  "build": "tsc",
49
+ "prepublish": "npm run build",
49
50
  "prepare": "husky install"
50
51
  },
51
52
  "dependencies": {
@@ -66,4 +67,4 @@
66
67
  "ts-jest": "^27.1.3",
67
68
  "typescript": "^4.6.2"
68
69
  }
69
- }
70
+ }
package/sdk/analytics.ts CHANGED
@@ -5,8 +5,6 @@ const ANALYTICS_ENDPOINT = 'analytics/flags/';
5
5
  // Used to control how often we send data(in seconds)
6
6
  const ANALYTICS_TIMER = 10;
7
7
 
8
- const delay = (ms: number) => new Promise(resolve => setTimeout(() => resolve(undefined), ms));
9
-
10
8
  export class AnalyticsProcessor {
11
9
  private analyticsEndpoint: string;
12
10
  private environmentKey: string;
package/sdk/index.ts CHANGED
@@ -12,6 +12,7 @@ import { EnvironmentDataPollingManager } from './polling_manager';
12
12
  import { generateIdentitiesData, retryFetch } from './utils';
13
13
  import { SegmentModel } from '../flagsmith-engine/segments/models';
14
14
  import { getIdentitySegments } from '../flagsmith-engine/segments/evaluators';
15
+ import { FlagsmithCache } from './types';
15
16
 
16
17
  const DEFAULT_API_URL = 'https://api.flagsmith.com/api/v1/';
17
18
 
@@ -26,12 +27,16 @@ export class Flagsmith {
26
27
  enableAnalytics: boolean = false;
27
28
  defaultFlagHandler?: (featureName: string) => DefaultFlag;
28
29
 
30
+
29
31
  environmentFlagsUrl: string;
30
32
  identitiesUrl: string;
31
33
  environmentUrl: string;
32
34
 
33
35
  environmentDataPollingManager?: EnvironmentDataPollingManager;
34
36
  environment!: EnvironmentModel;
37
+
38
+ private cache?: FlagsmithCache;
39
+ private onEnvironmentChange?: (error: Error | null, result: EnvironmentModel) => void;
35
40
  private analyticsProcessor?: AnalyticsProcessor;
36
41
  /**
37
42
  * A Flagsmith client.
@@ -73,6 +78,8 @@ export class Flagsmith {
73
78
  retries?: number;
74
79
  enableAnalytics?: boolean;
75
80
  defaultFlagHandler?: (featureName: string) => DefaultFlag;
81
+ cache?: FlagsmithCache,
82
+ onEnvironmentChange?: (error: Error | null, result: EnvironmentModel) => void,
76
83
  }) {
77
84
  this.environmentKey = data.environmentKey;
78
85
  this.apiUrl = data.apiUrl || this.apiUrl;
@@ -88,6 +95,20 @@ export class Flagsmith {
88
95
  this.environmentFlagsUrl = `${this.apiUrl}flags/`;
89
96
  this.identitiesUrl = `${this.apiUrl}identities/`;
90
97
  this.environmentUrl = `${this.apiUrl}environment-document/`;
98
+ this.onEnvironmentChange = data.onEnvironmentChange;
99
+
100
+ if (!!data.cache) {
101
+ const missingMethods: string[] = ['has', 'get', 'set'].filter(method => data.cache && !data.cache[method]);
102
+
103
+ if (missingMethods.length > 0) {
104
+ throw new Error(
105
+ `Please implement the following methods in your cache: ${missingMethods.join(
106
+ ', '
107
+ )}`
108
+ );
109
+ }
110
+ this.cache = data.cache;
111
+ }
91
112
 
92
113
  if (this.enableLocalEvaluation) {
93
114
  if (!this.environmentKey.startsWith('ser.')) {
@@ -105,10 +126,10 @@ export class Flagsmith {
105
126
 
106
127
  this.analyticsProcessor = data.enableAnalytics
107
128
  ? new AnalyticsProcessor({
108
- environmentKey: this.environmentKey,
109
- baseApiUrl: this.apiUrl,
110
- timeout: this.requestTimeoutSeconds
111
- })
129
+ environmentKey: this.environmentKey,
130
+ baseApiUrl: this.apiUrl,
131
+ timeout: this.requestTimeoutSeconds
132
+ })
112
133
  : undefined;
113
134
  }
114
135
  /**
@@ -117,8 +138,19 @@ export class Flagsmith {
117
138
  * @returns Flags object holding all the flags for the current environment.
118
139
  */
119
140
  async getEnvironmentFlags(): Promise<Flags> {
141
+ const cachedItem = !!this.cache && await this.cache.get(`flags`);
142
+ if (!!cachedItem) {
143
+ return cachedItem;
144
+ }
145
+ if (this.enableLocalEvaluation) {
146
+ return new Promise(resolve =>
147
+ this.environmentPromise!.then(() => {
148
+ resolve(this.getEnvironmentFlagsFromDocument());
149
+ })
150
+ );
151
+ }
120
152
  if (this.environment) {
121
- return new Promise(resolve => resolve(this.getEnvironmentFlagsFromDocument()));
153
+ return this.getEnvironmentFlagsFromDocument();
122
154
  }
123
155
 
124
156
  return this.getEnvironmentFlagsFromApi();
@@ -134,7 +166,11 @@ export class Flagsmith {
134
166
  Flagsmith, e.g. {"num_orders": 10}
135
167
  * @returns Flags object holding all the flags for the given identity.
136
168
  */
137
- getIdentityFlags(identifier: string, traits?: { [key: string]: any }): Promise<Flags> {
169
+ async getIdentityFlags(identifier: string, traits?: { [key: string]: any }): Promise<Flags> {
170
+ const cachedItem = !!this.cache && await this.cache.get(`flags-${identifier}`);
171
+ if (!!cachedItem) {
172
+ return cachedItem;
173
+ }
138
174
  traits = traits || {};
139
175
  if (this.enableLocalEvaluation) {
140
176
  return new Promise(resolve =>
@@ -188,14 +224,23 @@ export class Flagsmith {
188
224
  * You only need to call this if you wish to bypass environmentRefreshIntervalSeconds.
189
225
  */
190
226
  async updateEnvironment() {
191
- const request = this.getEnvironmentFromApi();
192
- if (!this.environmentPromise) {
193
- this.environmentPromise = request.then(res => {
194
- this.environment = res;
195
- });
196
- await this.environmentPromise;
197
- } else {
198
- this.environment = await request;
227
+ try {
228
+ const request = this.getEnvironmentFromApi();
229
+ if (!this.environmentPromise) {
230
+ this.environmentPromise = request.then(res => {
231
+ this.environment = res;
232
+ });
233
+ await this.environmentPromise;
234
+ } else {
235
+ this.environment = await request;
236
+ }
237
+ if (this.onEnvironmentChange) {
238
+ this.onEnvironmentChange(null, this.environment);
239
+ }
240
+ } catch (e) {
241
+ if (this.onEnvironmentChange) {
242
+ this.onEnvironmentChange(e as Error, this.environment);
243
+ }
199
244
  }
200
245
  }
201
246
 
@@ -247,15 +292,19 @@ export class Flagsmith {
247
292
  return buildEnvironmentModel(environment_data);
248
293
  }
249
294
 
250
- private getEnvironmentFlagsFromDocument() {
251
- return Flags.fromFeatureStateModels({
295
+ private async getEnvironmentFlagsFromDocument(): Promise<Flags> {
296
+ const flags = Flags.fromFeatureStateModels({
252
297
  featureStates: getEnvironmentFeatureStates(this.environment),
253
298
  analyticsProcessor: this.analyticsProcessor,
254
299
  defaultFlagHandler: this.defaultFlagHandler
255
300
  });
301
+ if (!!this.cache) {
302
+ await this.cache.set('flags', flags);
303
+ }
304
+ return flags;
256
305
  }
257
306
 
258
- private getIdentityFlagsFromDocument(identifier: string, traits: { [key: string]: any }) {
307
+ private async getIdentityFlagsFromDocument(identifier: string, traits: { [key: string]: any }): Promise<Flags> {
259
308
  const identityModel = this.buildIdentityModel(
260
309
  identifier,
261
310
  Object.keys(traits).map(key => ({
@@ -266,21 +315,31 @@ export class Flagsmith {
266
315
 
267
316
  const featureStates = getIdentityFeatureStates(this.environment, identityModel);
268
317
 
269
- return Flags.fromFeatureStateModels({
318
+ const flags = Flags.fromFeatureStateModels({
270
319
  featureStates: featureStates,
271
320
  analyticsProcessor: this.analyticsProcessor,
272
321
  defaultFlagHandler: this.defaultFlagHandler
273
322
  });
323
+
324
+ if (!!this.cache) {
325
+ await this.cache.set(`flags-${identifier}`, flags);
326
+ }
327
+
328
+ return flags;
274
329
  }
275
330
 
276
331
  private async getEnvironmentFlagsFromApi() {
277
332
  try {
278
333
  const apiFlags = await this.getJSONResponse(this.environmentFlagsUrl, 'GET');
279
- return Flags.fromAPIFlags({
334
+ const flags = Flags.fromAPIFlags({
280
335
  apiFlags: apiFlags,
281
336
  analyticsProcessor: this.analyticsProcessor,
282
337
  defaultFlagHandler: this.defaultFlagHandler
283
338
  });
339
+ if (!!this.cache) {
340
+ await this.cache.set('flags', flags);
341
+ }
342
+ return flags;
284
343
  } catch (e) {
285
344
  if (this.defaultFlagHandler) {
286
345
  return new Flags({
@@ -297,11 +356,15 @@ export class Flagsmith {
297
356
  try {
298
357
  const data = generateIdentitiesData(identifier, traits);
299
358
  const jsonResponse = await this.getJSONResponse(this.identitiesUrl, 'POST', data);
300
- return Flags.fromAPIFlags({
359
+ const flags = Flags.fromAPIFlags({
301
360
  apiFlags: jsonResponse['flags'],
302
361
  analyticsProcessor: this.analyticsProcessor,
303
362
  defaultFlagHandler: this.defaultFlagHandler
304
363
  });
364
+ if (!!this.cache) {
365
+ await this.cache.set(`flags-${identifier}`, flags);
366
+ }
367
+ return flags;
305
368
  } catch (e) {
306
369
  if (this.defaultFlagHandler) {
307
370
  return new Flags({
@@ -315,12 +378,6 @@ export class Flagsmith {
315
378
  }
316
379
 
317
380
  private buildIdentityModel(identifier: string, traits: { key: string; value: any }[]) {
318
- if (!this.environment) {
319
- throw new FlagsmithClientError(
320
- 'Unable to build identity model when no local environment present.'
321
- );
322
- }
323
-
324
381
  const traitModels = traits.map(trait => new TraitModel(trait.key, trait.value));
325
382
  return new IdentityModel('0', traitModels, [], this.environment.apiKey, identifier);
326
383
  }
@@ -17,7 +17,6 @@ export class EnvironmentDataPollingManager {
17
17
  await this.main.updateEnvironment();
18
18
  }, this.refreshIntervalSeconds * 1000);
19
19
  };
20
- // todo: this call should be awaited for getIdentityFlags/getEnvironmentFlags when enableLocalEvaluation is true
21
20
  this.main.updateEnvironment();
22
21
  updateEnvironment();
23
22
  }
package/sdk/types.ts ADDED
@@ -0,0 +1,8 @@
1
+ import { Flags } from "./models";
2
+
3
+ export interface FlagsmithCache {
4
+ get(key: string): Promise<Flags>;
5
+ set(key: string, value: Flags): Promise<void>;
6
+ has(key: string): Promise<boolean>;
7
+ [key: string]: any;
8
+ }
@@ -25,6 +25,12 @@ test('test_identity_get_feature_state_without_any_override', () => {
25
25
  expect(feature_state.feature).toStrictEqual(feature1());
26
26
  });
27
27
 
28
+ test('test_identity_get_feature_state_without_any_override_no_fs', () => {
29
+ expect(() => {
30
+ getIdentityFeatureState(environment(), identity(), 'nonExistentName');
31
+ }).toThrowError('Feature State Not Found');
32
+ });
33
+
28
34
  test('test_identity_get_all_feature_states_no_segments', () => {
29
35
  const env = environment();
30
36
  const ident = identity();
@@ -61,6 +67,20 @@ test('test_identity_get_all_feature_states_with_traits', () => {
61
67
  expect(featureStates[0].getValue()).toBe('segment_override');
62
68
  });
63
69
 
70
+ test('test_identity_get_all_feature_states_with_traits_hideDisabledFlags', () => {
71
+ const trait_models = new TraitModel(segmentConditionProperty, segmentConditionStringValue);
72
+
73
+ const env = environmentWithSegmentOverride();
74
+ env.project.hideDisabledFlags = true;
75
+
76
+ const featureStates = getIdentityFeatureStates(
77
+ env,
78
+ identityInSegment(),
79
+ [trait_models]
80
+ );
81
+ expect(featureStates.length).toBe(0);
82
+ });
83
+
64
84
  test('test_environment_get_all_feature_states', () => {
65
85
  const env = environment();
66
86
  const featureStates = getEnvironmentFeatureStates(env);
@@ -7,6 +7,12 @@ import {
7
7
  } from '../../../../flagsmith-engine/features/models';
8
8
  import { feature1 } from '../utils';
9
9
 
10
+ test('test_compare_feature_model', () => {
11
+ const fm1 = new FeatureModel(1, 'a', 'test');
12
+ const fm2 = new FeatureModel(1, 'a', 'test');
13
+ expect(fm1.eq(fm2)).toBe(true);
14
+ });
15
+
10
16
  test('test_initializing_feature_state_creates_default_feature_state_uuid', () => {
11
17
  const featureState = new FeatureStateModel(feature1(), true, 1);
12
18
  expect(featureState.featurestateUUID).toBeDefined();
@@ -65,8 +71,6 @@ test('test_feature_state_get_value_mv_values', () => {
65
71
 
66
72
  mvFeatureState.setValue(mvFeatureControlValue);
67
73
 
68
- // TODO
69
- // expect(mvFeatureState.getValue(3)).toBe(testCase[1]);
70
- expect(1).toBe(1);
74
+ expect(mvFeatureState.getValue("test")).toBe(mvFeatureValue2);
71
75
  }
72
76
  });
@@ -42,6 +42,19 @@ test('test_build_identity_model_from_dictionary_uses_identity_feature_list_for_i
42
42
  expect(identityModel.identityFeatures?.length).toBe(1);
43
43
  });
44
44
 
45
+ test('test_build_identity_model_from_dictionary_uses_identity_feature_list_for_identity_features', () => {
46
+ const identity_dict = {
47
+ id: 1,
48
+ identifier: 'test-identity',
49
+ environment_api_key: 'api-key',
50
+ created_date: '2021-08-22T06:25:23.406995Z',
51
+ };
52
+
53
+ const identityModel = buildIdentityModel(identity_dict);
54
+
55
+ expect(identityModel.identityFeatures?.length).toBe(0);
56
+ });
57
+
45
58
  test('test_build_build_identity_model_from_dict_creates_identity_uuid', () => {
46
59
  const identity_model = buildIdentityModel({
47
60
  identifier: 'test_user',
@@ -54,7 +54,7 @@ test('test_update_traits_remove_traits_with_none_value', () => {
54
54
  const trait_key = ident.identityTraits[0].traitKey;
55
55
  const trait_to_remove = new TraitModel(trait_key, undefined);
56
56
 
57
- ident.update_traits([trait_to_remove]);
57
+ ident.updateTraits([trait_to_remove]);
58
58
 
59
59
  expect(ident.identityTraits.length).toBe(0);
60
60
  });
@@ -66,7 +66,7 @@ test('test_update_identity_traits_updates_trait_value', () => {
66
66
  const traitValue = 'updated_trait_value';
67
67
  const traitToUpdate = new TraitModel(traitKey, traitValue);
68
68
 
69
- identity.update_traits([traitToUpdate]);
69
+ identity.updateTraits([traitToUpdate]);
70
70
 
71
71
  expect(identity.identityTraits.length).toBe(1);
72
72
  expect(identity.identityTraits[0]).toBe(traitToUpdate);
@@ -77,7 +77,7 @@ test('test_update_traits_adds_new_traits', () => {
77
77
 
78
78
  const newTrait = new TraitModel('new_key', 'foobar');
79
79
 
80
- identity.update_traits([newTrait]);
80
+ identity.updateTraits([newTrait]);
81
81
 
82
82
  expect(identity.identityTraits.length).toBe(2);
83
83
  expect(identity.identityTraits).toContain(newTrait);
@@ -92,14 +92,3 @@ test('test_append_feature_state', () => {
92
92
 
93
93
  expect(ident.identityFeatures).toContain(fs1);
94
94
  });
95
-
96
- test('test_appending_feature_states_raises_duplicate_feature_state_if_fs_for_the_feature_already_exists', () => {
97
- const ident = identityInSegment();
98
-
99
- const fs1 = new FeatureStateModel(feature1(), false, 1);
100
- const fs2 = new FeatureStateModel(feature1(), true, 1);
101
- ident.identityFeatures.push(fs1);
102
- expect(() => {
103
- ident.identityFeatures.push(fs2);
104
- }).toThrowError();
105
- });
@@ -8,5 +8,5 @@ test('Test builder', () => {
8
8
  stop_serving_flags: false,
9
9
  id: 13
10
10
  });
11
- expect(model.unique_slug).toBe('13-Flagsmith');
11
+ expect(model.uniqueSlug).toBe('13-Flagsmith');
12
12
  });
@@ -61,7 +61,8 @@ const conditionMatchCases: [string, string | number | boolean, string, boolean][
61
61
  [CONDITION_OPERATORS.NOT_CONTAINS, 'bar', 'bar', false],
62
62
  [CONDITION_OPERATORS.NOT_CONTAINS, 'bar', 'baz', true],
63
63
  [CONDITION_OPERATORS.REGEX, 'foo', '[a-z]+', true],
64
- [CONDITION_OPERATORS.REGEX, 'FOO', '[a-z]+', false]
64
+ [CONDITION_OPERATORS.REGEX, 'FOO', '[a-z]+', false],
65
+ ['BAD_OP', 'a', 'a', false]
65
66
  ];
66
67
 
67
68
  test('test_segment_condition_matches_trait_value', () => {
@@ -87,7 +87,7 @@ export function getEnvironmentFeatureStateForFeatureByName(
87
87
  feature_name: string
88
88
  ): FeatureStateModel | undefined {
89
89
  const features = environment.featureStates.filter(fs => fs.feature.name === feature_name);
90
- return features.length > 0 ? features[0] : undefined;
90
+ return features[0];
91
91
  }
92
92
 
93
93
  export function getEnvironmentFeatureStateForFeature(
@@ -36,6 +36,17 @@ test('test_analytics_processor_flush_post_request_data_match_ananlytics_data', a
36
36
  });
37
37
  });
38
38
 
39
+ jest.useFakeTimers()
40
+ test('test_analytics_processor_flush_post_request_data_match_ananlytics_data_test', async () => {
41
+ const aP = analyticsProcessor();
42
+ aP.trackFeature(1);
43
+ setTimeout(() => {
44
+ aP.trackFeature(2);
45
+ expect(fetch).toHaveBeenCalledTimes(1);
46
+ }, 15000);
47
+ jest.runOnlyPendingTimers();
48
+ });
49
+
39
50
  test('test_analytics_processor_flush_early_exit_if_analytics_data_is_empty', async () => {
40
51
  const aP = analyticsProcessor();
41
52
  await aP.flush();