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.
- package/.github/workflows/conventional-commit.yml +29 -0
- package/.github/workflows/publish.yml +20 -17
- package/.github/workflows/pull_request.yaml +36 -33
- package/.github/workflows/release-please.yml +18 -0
- package/.gitmodules +1 -0
- package/.husky/pre-commit +1 -0
- package/.nvmrc +1 -0
- package/.prettierrc.cjs +9 -1
- package/.release-please-manifest.json +1 -0
- package/CHANGELOG.md +592 -0
- package/CODEOWNERS +1 -0
- package/README.md +0 -2
- package/build/cjs/flagsmith-engine/environments/models.d.ts +2 -1
- package/build/cjs/flagsmith-engine/environments/models.js +3 -1
- package/build/cjs/flagsmith-engine/environments/util.js +1 -1
- package/build/cjs/flagsmith-engine/evaluation/evaluationContext/evaluationContext.types.d.ts +230 -0
- package/build/cjs/flagsmith-engine/evaluation/evaluationContext/evaluationContext.types.js +8 -0
- package/build/cjs/flagsmith-engine/evaluation/evaluationContext/mappers.d.ts +5 -0
- package/build/cjs/flagsmith-engine/evaluation/evaluationContext/mappers.js +156 -0
- package/build/cjs/flagsmith-engine/evaluation/evaluationContext/types.d.ts +216 -0
- package/build/cjs/flagsmith-engine/evaluation/evaluationContext/types.js +8 -0
- package/build/cjs/flagsmith-engine/evaluation/evaluationResult/evaluationResult.types.d.ts +68 -0
- package/build/cjs/flagsmith-engine/evaluation/evaluationResult/evaluationResult.types.js +8 -0
- package/build/cjs/flagsmith-engine/evaluation/models.d.ts +50 -0
- package/build/cjs/flagsmith-engine/evaluation/models.js +26 -0
- package/build/cjs/flagsmith-engine/features/models.js +1 -1
- package/build/cjs/flagsmith-engine/features/types.d.ts +5 -0
- package/build/cjs/flagsmith-engine/features/types.js +9 -0
- package/build/cjs/flagsmith-engine/features/util.d.ts +1 -0
- package/build/cjs/flagsmith-engine/features/util.js +5 -1
- package/build/cjs/flagsmith-engine/index.d.ts +61 -9
- package/build/cjs/flagsmith-engine/index.js +176 -56
- package/build/cjs/flagsmith-engine/segments/constants.d.ts +1 -0
- package/build/cjs/flagsmith-engine/segments/constants.js +2 -1
- package/build/cjs/flagsmith-engine/segments/evaluators.d.ts +41 -7
- package/build/cjs/flagsmith-engine/segments/evaluators.js +136 -24
- package/build/cjs/flagsmith-engine/segments/models.d.ts +9 -4
- package/build/cjs/flagsmith-engine/segments/models.js +115 -13
- package/build/cjs/flagsmith-engine/utils/hashing/index.d.ts +1 -1
- package/build/cjs/flagsmith-engine/utils/hashing/index.js +4 -4
- package/build/cjs/sdk/analytics.js +3 -1
- package/build/cjs/sdk/index.d.ts +1 -3
- package/build/cjs/sdk/index.js +63 -24
- package/build/cjs/sdk/models.d.ts +8 -1
- package/build/cjs/sdk/models.js +29 -1
- package/build/cjs/sdk/utils.d.ts +1 -0
- package/build/cjs/sdk/utils.js +14 -1
- package/build/esm/flagsmith-engine/environments/models.d.ts +2 -1
- package/build/esm/flagsmith-engine/environments/models.js +3 -1
- package/build/esm/flagsmith-engine/environments/util.js +1 -1
- package/build/esm/flagsmith-engine/evaluation/evaluationContext/evaluationContext.types.d.ts +230 -0
- package/build/esm/flagsmith-engine/evaluation/evaluationContext/evaluationContext.types.js +7 -0
- package/build/esm/flagsmith-engine/evaluation/evaluationContext/mappers.d.ts +5 -0
- package/build/esm/flagsmith-engine/evaluation/evaluationContext/mappers.js +152 -0
- package/build/esm/flagsmith-engine/evaluation/evaluationContext/types.d.ts +216 -0
- package/build/esm/flagsmith-engine/evaluation/evaluationContext/types.js +7 -0
- package/build/esm/flagsmith-engine/evaluation/evaluationResult/evaluationResult.types.d.ts +68 -0
- package/build/esm/flagsmith-engine/evaluation/evaluationResult/evaluationResult.types.js +7 -0
- package/build/esm/flagsmith-engine/evaluation/models.d.ts +50 -0
- package/build/esm/flagsmith-engine/evaluation/models.js +9 -0
- package/build/esm/flagsmith-engine/features/models.js +2 -2
- package/build/esm/flagsmith-engine/features/types.d.ts +5 -0
- package/build/esm/flagsmith-engine/features/types.js +6 -0
- package/build/esm/flagsmith-engine/features/util.d.ts +1 -0
- package/build/esm/flagsmith-engine/features/util.js +3 -0
- package/build/esm/flagsmith-engine/index.d.ts +61 -9
- package/build/esm/flagsmith-engine/index.js +161 -43
- package/build/esm/flagsmith-engine/segments/constants.d.ts +1 -0
- package/build/esm/flagsmith-engine/segments/constants.js +1 -0
- package/build/esm/flagsmith-engine/segments/evaluators.d.ts +41 -7
- package/build/esm/flagsmith-engine/segments/evaluators.js +137 -25
- package/build/esm/flagsmith-engine/segments/models.d.ts +9 -4
- package/build/esm/flagsmith-engine/segments/models.js +115 -13
- package/build/esm/flagsmith-engine/utils/hashing/index.d.ts +1 -1
- package/build/esm/flagsmith-engine/utils/hashing/index.js +2 -2
- package/build/esm/sdk/analytics.js +3 -1
- package/build/esm/sdk/index.d.ts +1 -3
- package/build/esm/sdk/index.js +63 -24
- package/build/esm/sdk/models.d.ts +8 -1
- package/build/esm/sdk/models.js +29 -1
- package/build/esm/sdk/utils.d.ts +1 -0
- package/build/esm/sdk/utils.js +12 -0
- package/flagsmith-engine/environments/models.ts +3 -1
- package/flagsmith-engine/environments/util.ts +2 -1
- package/flagsmith-engine/evaluation/evaluationContext/evaluationContext.types.ts +247 -0
- package/flagsmith-engine/evaluation/evaluationContext/mappers.ts +204 -0
- package/flagsmith-engine/evaluation/evaluationContext/types.ts +233 -0
- package/flagsmith-engine/evaluation/evaluationResult/evaluationResult.types.ts +71 -0
- package/flagsmith-engine/evaluation/models.ts +96 -0
- package/flagsmith-engine/features/models.ts +3 -2
- package/flagsmith-engine/features/types.ts +5 -0
- package/flagsmith-engine/features/util.ts +4 -0
- package/flagsmith-engine/index.ts +229 -72
- package/flagsmith-engine/segments/constants.ts +1 -0
- package/flagsmith-engine/segments/evaluators.ts +178 -62
- package/flagsmith-engine/segments/models.ts +171 -23
- package/flagsmith-engine/utils/hashing/index.ts +2 -2
- package/package.json +13 -2
- package/release-please-config.json +62 -0
- package/sdk/analytics.ts +3 -1
- package/sdk/index.ts +89 -30
- package/sdk/models.ts +44 -2
- package/sdk/utils.ts +13 -0
- package/tests/engine/e2e/engine.test.ts +43 -38
- package/tests/engine/unit/engine.test.ts +306 -60
- package/tests/engine/unit/mappers.test.ts +353 -0
- package/tests/engine/unit/segments/segment_evaluators.test.ts +391 -49
- package/tests/engine/unit/segments/segments_model.test.ts +85 -0
- package/tests/engine/unit/utils/utils.test.ts +7 -7
- package/tests/engine/unit/utils.ts +1 -1
- package/tests/sdk/analytics.test.ts +6 -1
- package/tests/sdk/data/environment.json +1 -0
- package/tests/sdk/flagsmith-environment-flags.test.ts +28 -0
- package/tests/sdk/flagsmith-identity-flags.test.ts +11 -2
- package/tests/sdk/flagsmith.test.ts +190 -3
- package/tests/sdk/offline-handlers.test.ts +3 -1
- package/vitest.config.esm.ts +34 -0
package/sdk/index.ts
CHANGED
|
@@ -1,22 +1,21 @@
|
|
|
1
1
|
import { Dispatcher } from 'undici-types';
|
|
2
|
-
|
|
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 {
|
|
19
|
-
|
|
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
|
-
|
|
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
|
|
395
|
-
|
|
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
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
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
|
|
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.
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
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
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import
|
|
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
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
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
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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
|
-
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
|
|
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
|
-
|
|
33
|
+
describe('Engine Integration Tests', () => {
|
|
34
|
+
const testFiles = getTestFiles();
|
|
38
35
|
|
|
39
|
-
|
|
40
|
-
|
|
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
|
});
|