flagsmith-nodejs 6.1.0 → 7.0.2

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 (117) hide show
  1. package/.github/workflows/conventional-commit.yml +29 -0
  2. package/.github/workflows/publish.yml +20 -17
  3. package/.github/workflows/pull_request.yaml +36 -33
  4. package/.github/workflows/release-please.yml +18 -0
  5. package/.gitmodules +1 -0
  6. package/.husky/pre-commit +1 -0
  7. package/.nvmrc +1 -0
  8. package/.prettierrc.cjs +9 -1
  9. package/.release-please-manifest.json +1 -0
  10. package/CHANGELOG.md +592 -0
  11. package/CODEOWNERS +1 -0
  12. package/README.md +0 -2
  13. package/build/cjs/flagsmith-engine/environments/models.d.ts +2 -1
  14. package/build/cjs/flagsmith-engine/environments/models.js +3 -1
  15. package/build/cjs/flagsmith-engine/environments/util.js +1 -1
  16. package/build/cjs/flagsmith-engine/evaluation/evaluationContext/evaluationContext.types.d.ts +230 -0
  17. package/build/cjs/flagsmith-engine/evaluation/evaluationContext/evaluationContext.types.js +8 -0
  18. package/build/cjs/flagsmith-engine/evaluation/evaluationContext/mappers.d.ts +5 -0
  19. package/build/cjs/flagsmith-engine/evaluation/evaluationContext/mappers.js +156 -0
  20. package/build/cjs/flagsmith-engine/evaluation/evaluationContext/types.d.ts +216 -0
  21. package/build/cjs/flagsmith-engine/evaluation/evaluationContext/types.js +8 -0
  22. package/build/cjs/flagsmith-engine/evaluation/evaluationResult/evaluationResult.types.d.ts +68 -0
  23. package/build/cjs/flagsmith-engine/evaluation/evaluationResult/evaluationResult.types.js +8 -0
  24. package/build/cjs/flagsmith-engine/evaluation/models.d.ts +50 -0
  25. package/build/cjs/flagsmith-engine/evaluation/models.js +26 -0
  26. package/build/cjs/flagsmith-engine/features/models.js +1 -1
  27. package/build/cjs/flagsmith-engine/features/types.d.ts +5 -0
  28. package/build/cjs/flagsmith-engine/features/types.js +9 -0
  29. package/build/cjs/flagsmith-engine/features/util.d.ts +1 -0
  30. package/build/cjs/flagsmith-engine/features/util.js +5 -1
  31. package/build/cjs/flagsmith-engine/index.d.ts +61 -9
  32. package/build/cjs/flagsmith-engine/index.js +176 -56
  33. package/build/cjs/flagsmith-engine/segments/constants.d.ts +1 -0
  34. package/build/cjs/flagsmith-engine/segments/constants.js +2 -1
  35. package/build/cjs/flagsmith-engine/segments/evaluators.d.ts +41 -7
  36. package/build/cjs/flagsmith-engine/segments/evaluators.js +136 -24
  37. package/build/cjs/flagsmith-engine/segments/models.d.ts +9 -4
  38. package/build/cjs/flagsmith-engine/segments/models.js +115 -13
  39. package/build/cjs/flagsmith-engine/utils/hashing/index.d.ts +1 -1
  40. package/build/cjs/flagsmith-engine/utils/hashing/index.js +4 -4
  41. package/build/cjs/sdk/analytics.js +3 -1
  42. package/build/cjs/sdk/index.d.ts +1 -3
  43. package/build/cjs/sdk/index.js +63 -24
  44. package/build/cjs/sdk/models.d.ts +8 -1
  45. package/build/cjs/sdk/models.js +29 -1
  46. package/build/cjs/sdk/utils.d.ts +1 -0
  47. package/build/cjs/sdk/utils.js +14 -1
  48. package/build/esm/flagsmith-engine/environments/models.d.ts +2 -1
  49. package/build/esm/flagsmith-engine/environments/models.js +3 -1
  50. package/build/esm/flagsmith-engine/environments/util.js +1 -1
  51. package/build/esm/flagsmith-engine/evaluation/evaluationContext/evaluationContext.types.d.ts +230 -0
  52. package/build/esm/flagsmith-engine/evaluation/evaluationContext/evaluationContext.types.js +7 -0
  53. package/build/esm/flagsmith-engine/evaluation/evaluationContext/mappers.d.ts +5 -0
  54. package/build/esm/flagsmith-engine/evaluation/evaluationContext/mappers.js +152 -0
  55. package/build/esm/flagsmith-engine/evaluation/evaluationContext/types.d.ts +216 -0
  56. package/build/esm/flagsmith-engine/evaluation/evaluationContext/types.js +7 -0
  57. package/build/esm/flagsmith-engine/evaluation/evaluationResult/evaluationResult.types.d.ts +68 -0
  58. package/build/esm/flagsmith-engine/evaluation/evaluationResult/evaluationResult.types.js +7 -0
  59. package/build/esm/flagsmith-engine/evaluation/models.d.ts +50 -0
  60. package/build/esm/flagsmith-engine/evaluation/models.js +9 -0
  61. package/build/esm/flagsmith-engine/features/models.js +2 -2
  62. package/build/esm/flagsmith-engine/features/types.d.ts +5 -0
  63. package/build/esm/flagsmith-engine/features/types.js +6 -0
  64. package/build/esm/flagsmith-engine/features/util.d.ts +1 -0
  65. package/build/esm/flagsmith-engine/features/util.js +3 -0
  66. package/build/esm/flagsmith-engine/index.d.ts +61 -9
  67. package/build/esm/flagsmith-engine/index.js +161 -43
  68. package/build/esm/flagsmith-engine/segments/constants.d.ts +1 -0
  69. package/build/esm/flagsmith-engine/segments/constants.js +1 -0
  70. package/build/esm/flagsmith-engine/segments/evaluators.d.ts +41 -7
  71. package/build/esm/flagsmith-engine/segments/evaluators.js +137 -25
  72. package/build/esm/flagsmith-engine/segments/models.d.ts +9 -4
  73. package/build/esm/flagsmith-engine/segments/models.js +115 -13
  74. package/build/esm/flagsmith-engine/utils/hashing/index.d.ts +1 -1
  75. package/build/esm/flagsmith-engine/utils/hashing/index.js +2 -2
  76. package/build/esm/sdk/analytics.js +3 -1
  77. package/build/esm/sdk/index.d.ts +1 -3
  78. package/build/esm/sdk/index.js +63 -24
  79. package/build/esm/sdk/models.d.ts +8 -1
  80. package/build/esm/sdk/models.js +29 -1
  81. package/build/esm/sdk/utils.d.ts +1 -0
  82. package/build/esm/sdk/utils.js +12 -0
  83. package/flagsmith-engine/environments/models.ts +3 -1
  84. package/flagsmith-engine/environments/util.ts +2 -1
  85. package/flagsmith-engine/evaluation/evaluationContext/evaluationContext.types.ts +247 -0
  86. package/flagsmith-engine/evaluation/evaluationContext/mappers.ts +204 -0
  87. package/flagsmith-engine/evaluation/evaluationContext/types.ts +233 -0
  88. package/flagsmith-engine/evaluation/evaluationResult/evaluationResult.types.ts +71 -0
  89. package/flagsmith-engine/evaluation/models.ts +96 -0
  90. package/flagsmith-engine/features/models.ts +3 -2
  91. package/flagsmith-engine/features/types.ts +5 -0
  92. package/flagsmith-engine/features/util.ts +4 -0
  93. package/flagsmith-engine/index.ts +229 -72
  94. package/flagsmith-engine/segments/constants.ts +1 -0
  95. package/flagsmith-engine/segments/evaluators.ts +178 -62
  96. package/flagsmith-engine/segments/models.ts +171 -23
  97. package/flagsmith-engine/utils/hashing/index.ts +2 -2
  98. package/package.json +13 -2
  99. package/release-please-config.json +62 -0
  100. package/sdk/analytics.ts +3 -1
  101. package/sdk/index.ts +89 -30
  102. package/sdk/models.ts +44 -2
  103. package/sdk/utils.ts +13 -0
  104. package/tests/engine/e2e/engine.test.ts +43 -38
  105. package/tests/engine/unit/engine.test.ts +306 -60
  106. package/tests/engine/unit/mappers.test.ts +353 -0
  107. package/tests/engine/unit/segments/segment_evaluators.test.ts +391 -49
  108. package/tests/engine/unit/segments/segments_model.test.ts +85 -0
  109. package/tests/engine/unit/utils/utils.test.ts +7 -7
  110. package/tests/engine/unit/utils.ts +1 -1
  111. package/tests/sdk/analytics.test.ts +6 -1
  112. package/tests/sdk/data/environment.json +1 -0
  113. package/tests/sdk/flagsmith-environment-flags.test.ts +28 -0
  114. package/tests/sdk/flagsmith-identity-flags.test.ts +11 -2
  115. package/tests/sdk/flagsmith.test.ts +190 -3
  116. package/tests/sdk/offline-handlers.test.ts +3 -1
  117. package/vitest.config.esm.ts +34 -0
package/sdk/index.ts CHANGED
@@ -1,22 +1,21 @@
1
1
  import { Dispatcher } from 'undici-types';
2
- import {
3
- getEnvironmentFeatureStates,
4
- getIdentityFeatureStates
5
- } from '../flagsmith-engine/index.js';
6
- import { EnvironmentModel } from '../flagsmith-engine/index.js';
2
+
7
3
  import { buildEnvironmentModel } from '../flagsmith-engine/environments/util.js';
8
- import { IdentityModel } from '../flagsmith-engine/index.js';
9
- import { TraitModel } from '../flagsmith-engine/index.js';
10
4
 
11
5
  import { ANALYTICS_ENDPOINT, AnalyticsProcessor } from './analytics.js';
12
6
  import { BaseOfflineHandler } from './offline_handlers.js';
13
- import { FlagsmithAPIError } from './errors.js';
7
+ import { FlagsmithAPIError, FlagsmithClientError } from './errors.js';
14
8
 
15
9
  import { DefaultFlag, Flags } from './models.js';
16
10
  import { EnvironmentDataPollingManager } from './polling_manager.js';
17
- import { Deferred, generateIdentitiesData, retryFetch } from './utils.js';
18
- import { SegmentModel } from '../flagsmith-engine/index.js';
19
- import { getIdentitySegments } from '../flagsmith-engine/segments/evaluators.js';
11
+ import { Deferred, generateIdentitiesData, getUserAgent, retryFetch } from './utils.js';
12
+ import {
13
+ SegmentModel,
14
+ EnvironmentModel,
15
+ IdentityModel,
16
+ TraitModel,
17
+ getEvaluationResult
18
+ } from '../flagsmith-engine/index.js';
20
19
  import {
21
20
  Fetch,
22
21
  FlagsmithCache,
@@ -25,6 +24,8 @@ import {
25
24
  TraitConfig
26
25
  } from './types.js';
27
26
  import { pino, Logger } from 'pino';
27
+ import { getEvaluationContext } from '../flagsmith-engine/evaluation/evaluationContext/mappers.js';
28
+ import { EvaluationContextWithMetadata } from '../flagsmith-engine/evaluation/models.js';
28
29
 
29
30
  export { AnalyticsProcessor, AnalyticsProcessorOptions } from './analytics.js';
30
31
  export { FlagsmithAPIError, FlagsmithClientError } from './errors.js';
@@ -278,7 +279,13 @@ export class Flagsmith {
278
279
  }))
279
280
  );
280
281
 
281
- return getIdentitySegments(environment, identityModel);
282
+ const context = getEvaluationContext(environment, identityModel);
283
+ if (!context) {
284
+ throw new FlagsmithClientError('Local evaluation required to obtain identity segments');
285
+ }
286
+ const evaluationResult = getEvaluationResult(context as EvaluationContextWithMetadata);
287
+
288
+ return SegmentModel.fromSegmentResult(evaluationResult.segments, context);
282
289
  }
283
290
 
284
291
  private async fetchEnvironment(): Promise<EnvironmentModel> {
@@ -329,12 +336,14 @@ export class Flagsmith {
329
336
  url: string,
330
337
  method: string,
331
338
  body?: { [key: string]: any }
332
- ): Promise<any> {
339
+ ): Promise<{ response: Response; data: any }> {
333
340
  const headers: { [key: string]: any } = { 'Content-Type': 'application/json' };
334
341
  if (this.environmentKey) {
335
342
  headers['X-Environment-Key'] = this.environmentKey as string;
336
343
  }
337
344
 
345
+ headers['User-Agent'] = getUserAgent();
346
+
338
347
  if (this.customHeaders) {
339
348
  for (const [k, v] of Object.entries(this.customHeaders)) {
340
349
  headers[k] = v;
@@ -361,7 +370,7 @@ export class Flagsmith {
361
370
  );
362
371
  }
363
372
 
364
- return data.json();
373
+ return { response: data, data: await data.json() };
365
374
  }
366
375
 
367
376
  /**
@@ -391,20 +400,67 @@ export class Flagsmith {
391
400
  if (!this.environmentUrl) {
392
401
  throw new Error('`apiUrl` argument is missing or invalid.');
393
402
  }
394
- const environment_data = await this.getJSONResponse(this.environmentUrl, 'GET');
395
- return buildEnvironmentModel(environment_data);
403
+ const startTime = Date.now();
404
+ const documents: any[] = [];
405
+ let url = this.environmentUrl;
406
+ let loggedWarning = false;
407
+
408
+ while (true) {
409
+ try {
410
+ if (!loggedWarning) {
411
+ const elapsedMs = Date.now() - startTime;
412
+ if (elapsedMs > this.environmentRefreshIntervalSeconds * 1000) {
413
+ this.logger.warn(
414
+ `Environment document retrieval exceeded the polling interval of ${this.environmentRefreshIntervalSeconds} seconds.`
415
+ );
416
+ loggedWarning = true;
417
+ }
418
+ }
419
+
420
+ const { response, data } = await this.getJSONResponse(url, 'GET');
421
+
422
+ documents.push(data);
423
+
424
+ const linkHeader = response.headers.get('link');
425
+ if (linkHeader) {
426
+ const nextMatch = linkHeader.match(/<([^>]+)>;\s*rel="next"/);
427
+
428
+ if (nextMatch) {
429
+ const relativeUrl = decodeURIComponent(nextMatch[1]);
430
+ url = new URL(relativeUrl, this.apiUrl).href;
431
+
432
+ continue;
433
+ }
434
+ }
435
+ break;
436
+ } catch (error) {
437
+ throw error;
438
+ }
439
+ }
440
+
441
+ // Compile the document
442
+ const compiledDocument = documents[0];
443
+ for (let i = 1; i < documents.length; i++) {
444
+ compiledDocument.identity_overrides = compiledDocument.identity_overrides || [];
445
+ compiledDocument.identity_overrides.push(...(documents[i].identity_overrides || []));
446
+ }
447
+
448
+ return buildEnvironmentModel(compiledDocument);
396
449
  }
397
450
 
398
451
  private async getEnvironmentFlagsFromDocument(): Promise<Flags> {
399
452
  const environment = await this.getEnvironment();
400
- const flags = Flags.fromFeatureStateModels({
401
- featureStates: getEnvironmentFeatureStates(environment),
402
- analyticsProcessor: this.analyticsProcessor,
403
- defaultFlagHandler: this.defaultFlagHandler
404
- });
453
+ const context = getEvaluationContext(environment, undefined, undefined, true);
454
+ if (!context) {
455
+ throw new FlagsmithClientError('Unable to get flags. No environment present.');
456
+ }
457
+ const evaluationResult = getEvaluationResult(context as EvaluationContextWithMetadata);
458
+ const flags = Flags.fromEvaluationResult(evaluationResult);
459
+
405
460
  if (!!this.cache) {
406
461
  await this.cache.set('flags', flags);
407
462
  }
463
+
408
464
  return flags;
409
465
  }
410
466
 
@@ -422,14 +478,17 @@ export class Flagsmith {
422
478
  }))
423
479
  );
424
480
 
425
- const featureStates = getIdentityFeatureStates(environment, identityModel);
481
+ const context = getEvaluationContext(environment, identityModel);
482
+ if (!context) {
483
+ throw new FlagsmithClientError('Unable to get flags. No environment present.');
484
+ }
485
+ const evaluationResult = getEvaluationResult(context as EvaluationContextWithMetadata);
426
486
 
427
- const flags = Flags.fromFeatureStateModels({
428
- featureStates: featureStates,
429
- analyticsProcessor: this.analyticsProcessor,
430
- defaultFlagHandler: this.defaultFlagHandler,
431
- identityID: identityModel.djangoID || identityModel.compositeKey
432
- });
487
+ const flags = Flags.fromEvaluationResult(
488
+ evaluationResult,
489
+ this.defaultFlagHandler,
490
+ this.analyticsProcessor
491
+ );
433
492
 
434
493
  if (!!this.cache) {
435
494
  await this.cache.set(`flags-${identifier}`, flags);
@@ -442,7 +501,7 @@ export class Flagsmith {
442
501
  if (!this.environmentFlagsUrl) {
443
502
  throw new Error('`apiUrl` argument is missing or invalid.');
444
503
  }
445
- const apiFlags = await this.getJSONResponse(this.environmentFlagsUrl, 'GET');
504
+ const { data: apiFlags } = await this.getJSONResponse(this.environmentFlagsUrl, 'GET');
446
505
  const flags = Flags.fromAPIFlags({
447
506
  apiFlags: apiFlags,
448
507
  analyticsProcessor: this.analyticsProcessor,
@@ -463,7 +522,7 @@ export class Flagsmith {
463
522
  throw new Error('`apiUrl` argument is missing or invalid.');
464
523
  }
465
524
  const data = generateIdentitiesData(identifier, traits, transient);
466
- const jsonResponse = await this.getJSONResponse(this.identitiesUrl, 'POST', data);
525
+ const { data: jsonResponse } = await this.getJSONResponse(this.identitiesUrl, 'POST', data);
467
526
  const flags = Flags.fromAPIFlags({
468
527
  apiFlags: jsonResponse['flags'],
469
528
  analyticsProcessor: this.analyticsProcessor,
package/sdk/models.ts CHANGED
@@ -1,7 +1,12 @@
1
+ import {
2
+ SDKFeatureMetadata,
3
+ FlagResultWithMetadata,
4
+ EvaluationResultWithMetadata
5
+ } from '../flagsmith-engine/evaluation/models.js';
1
6
  import { FeatureStateModel } from '../flagsmith-engine/features/models.js';
2
7
  import { AnalyticsProcessor } from './analytics.js';
3
8
 
4
- type FlagValue = string | number | boolean | undefined;
9
+ type FlagValue = string | number | boolean | undefined | null;
5
10
 
6
11
  /**
7
12
  * A Flagsmith feature. It has an enabled/disabled state, and an optional {@link FlagValue}.
@@ -49,6 +54,10 @@ export class Flag extends BaseFlag {
49
54
  * The programmatic name for this feature, unique per Flagsmith project.
50
55
  */
51
56
  featureName: string;
57
+ /**
58
+ * The reason for this feature, unique per Flagsmith project.
59
+ */
60
+ reason?: string;
52
61
 
53
62
  constructor(params: {
54
63
  value: FlagValue;
@@ -56,10 +65,12 @@ export class Flag extends BaseFlag {
56
65
  isDefault?: boolean;
57
66
  featureId: number;
58
67
  featureName: string;
68
+ reason?: string;
59
69
  }) {
60
70
  super(params.value, params.enabled, !!params.isDefault);
61
71
  this.featureId = params.featureId;
62
72
  this.featureName = params.featureName;
73
+ this.reason = params.reason;
63
74
  }
64
75
 
65
76
  static fromFeatureStateModel(
@@ -79,7 +90,8 @@ export class Flag extends BaseFlag {
79
90
  enabled: flagData['enabled'],
80
91
  value: flagData['feature_state_value'] ?? flagData['value'],
81
92
  featureId: flagData['feature']['id'],
82
- featureName: flagData['feature']['name']
93
+ featureName: flagData['feature']['name'],
94
+ reason: flagData['feature']['reason']
83
95
  });
84
96
  }
85
97
  }
@@ -99,6 +111,36 @@ export class Flags {
99
111
  this.analyticsProcessor = data.analyticsProcessor;
100
112
  }
101
113
 
114
+ static fromEvaluationResult(
115
+ evaluationResult: EvaluationResultWithMetadata,
116
+ defaultFlagHandler?: (v: string) => DefaultFlag,
117
+ analyticsProcessor?: AnalyticsProcessor
118
+ ): Flags {
119
+ const flags: { [key: string]: Flag } = {};
120
+ for (const flag of Object.values(evaluationResult.flags)) {
121
+ const flagMetadataId = flag.metadata?.id;
122
+ if (!flagMetadataId) {
123
+ throw new Error(
124
+ `FlagResult metadata.id is missing for feature "${flag.name}". ` +
125
+ `This indicates a bug in the SDK, please report it.`
126
+ );
127
+ }
128
+
129
+ flags[flag.name] = new Flag({
130
+ enabled: flag.enabled,
131
+ value: flag.value ?? null,
132
+ featureId: flagMetadataId,
133
+ featureName: flag.name,
134
+ reason: flag.reason
135
+ });
136
+ }
137
+ return new Flags({
138
+ flags: flags,
139
+ defaultFlagHandler: defaultFlagHandler,
140
+ analyticsProcessor: analyticsProcessor
141
+ });
142
+ }
143
+
102
144
  static fromFeatureStateModels(data: {
103
145
  featureStates: FeatureStateModel[];
104
146
  analyticsProcessor?: AnalyticsProcessor;
package/sdk/utils.ts CHANGED
@@ -3,6 +3,9 @@ import { Dispatcher } from 'undici-types';
3
3
 
4
4
  type Traits = { [key: string]: TraitConfig | FlagsmithTraitValue };
5
5
 
6
+ const FLAGSMITH_USER_AGENT = 'flagsmith-nodejs-sdk';
7
+ const FLAGSMITH_UNKNOWN_VERSION = 'unknown';
8
+
6
9
  export function isTraitConfig(
7
10
  traitValue: TraitConfig | FlagsmithTraitValue
8
11
  ): traitValue is TraitConfig {
@@ -102,3 +105,13 @@ export class Deferred<T> {
102
105
  this.rejectPromise(reason);
103
106
  }
104
107
  }
108
+
109
+ export function getUserAgent(): string {
110
+ try {
111
+ const packageJson = require('../package.json');
112
+ const version = packageJson?.version;
113
+ return version ? `${FLAGSMITH_USER_AGENT}/${version}` : FLAGSMITH_UNKNOWN_VERSION;
114
+ } catch {
115
+ return FLAGSMITH_UNKNOWN_VERSION;
116
+ }
117
+ }
@@ -1,46 +1,51 @@
1
- import { getIdentityFeatureStates } from '../../../flagsmith-engine/index.js';
2
- import { EnvironmentModel } from '../../../flagsmith-engine/environments/models.js';
3
- import { buildEnvironmentModel } from '../../../flagsmith-engine/environments/util.js';
4
- import { IdentityModel } from '../../../flagsmith-engine/identities/models.js';
5
- import { buildIdentityModel } from '../../../flagsmith-engine/identities/util.js';
6
- import * as testData from '../engine-tests/engine-test-data/data/environment_n9fbf9h3v4fFgH3U3ngWhb.json';
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import { fileURLToPath } from 'url';
4
+ import { getEvaluationResult } from '../../../flagsmith-engine/index.js';
5
+ import { Flags } from '../../../sdk/models.js';
6
+ import { EvaluationContext } from '../../../flagsmith-engine/evaluation/evaluationContext/evaluationContext.types.js';
7
+ import { parse as parseJsonc } from 'jsonc-parser';
8
+ import {
9
+ EvaluationContextWithMetadata,
10
+ EvaluationResult
11
+ } from '../../../flagsmith-engine/evaluation/models.js';
7
12
 
8
- function extractTestCases(data: any): {
9
- environment: EnvironmentModel;
10
- identity: IdentityModel;
11
- response: any;
12
- }[] {
13
- const environmentModel = buildEnvironmentModel(data['environment']);
14
- const test_data = data['identities_and_responses'].map((test_case: any) => {
15
- const identity = buildIdentityModel(test_case['identity']);
13
+ const __filename = fileURLToPath(import.meta.url);
14
+ const __dirname = path.dirname(__filename);
15
+ const TEST_DATA_DIR = path.join(__dirname, '../engine-tests/engine-test-data/test_cases');
16
+ interface TestCase {
17
+ context: EvaluationContext;
18
+ result: EvaluationResult;
19
+ }
16
20
 
17
- return {
18
- environment: environmentModel,
19
- identity: identity,
20
- response: test_case['response']
21
- };
22
- });
23
- return test_data;
21
+ function getTestFiles(): string[] {
22
+ const files = fs.readdirSync(TEST_DATA_DIR);
23
+ return files
24
+ .filter(f => f.endsWith('.json') || f.endsWith('.jsonc'))
25
+ .map(f => path.join(TEST_DATA_DIR, f));
24
26
  }
25
27
 
26
- test('Test Engine', () => {
27
- const testCases = extractTestCases(testData);
28
- for (const testCase of testCases) {
29
- const engine_response = getIdentityFeatureStates(testCase.environment, testCase.identity);
30
- const sortedEngineFlags = engine_response.sort((a, b) =>
31
- a.feature.name > b.feature.name ? 1 : -1
32
- );
33
- const sortedAPIFlags = testCase.response['flags'].sort((a: any, b: any) =>
34
- a.feature.name > b.feature.name ? 1 : -1
35
- );
28
+ function loadTestFile(filePath: string): TestCase {
29
+ const content = fs.readFileSync(filePath, 'utf-8');
30
+ return parseJsonc(content);
31
+ }
36
32
 
37
- expect(sortedEngineFlags.length).toBe(sortedAPIFlags.length);
33
+ describe('Engine Integration Tests', () => {
34
+ const testFiles = getTestFiles();
38
35
 
39
- for (let i = 0; i < sortedEngineFlags.length; i++) {
40
- expect(sortedEngineFlags[i].getValue(testCase.identity.djangoID)).toBe(
41
- sortedAPIFlags[i]['feature_state_value']
42
- );
43
- expect(sortedEngineFlags[i].enabled).toBe(sortedAPIFlags[i]['enabled']);
44
- }
36
+ if (testFiles.length === 0) {
37
+ throw new Error(`No test files found in ${TEST_DATA_DIR}`);
45
38
  }
39
+
40
+ testFiles.forEach(filePath => {
41
+ const testName = path.basename(filePath, path.extname(filePath));
42
+
43
+ test(testName, () => {
44
+ const testCase = loadTestFile(filePath);
45
+ const engine_response = getEvaluationResult(
46
+ testCase.context as EvaluationContextWithMetadata
47
+ );
48
+ expect(engine_response).toStrictEqual(testCase.result);
49
+ });
50
+ });
46
51
  });