@featurevisor/core 1.35.3 → 2.0.1
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/CHANGELOG.md +8 -0
- package/README.md +0 -6
- package/coverage/clover.xml +321 -237
- package/coverage/coverage-final.json +8 -8
- package/coverage/lcov-report/index.html +77 -47
- package/coverage/lcov-report/lib/builder/allocator.js.html +14 -14
- package/coverage/lcov-report/lib/builder/index.html +16 -16
- package/coverage/lcov-report/lib/builder/revision.js.html +3 -3
- package/coverage/lcov-report/lib/builder/traffic.js.html +88 -64
- package/coverage/lcov-report/lib/list/index.html +116 -0
- package/coverage/lcov-report/lib/{tester → list}/matrix.js.html +90 -66
- package/coverage/lcov-report/lib/tester/helpers.js.html +295 -0
- package/coverage/lcov-report/lib/tester/index.html +20 -35
- package/coverage/lcov-report/src/builder/allocator.ts.html +2 -2
- package/coverage/lcov-report/src/builder/index.html +15 -15
- package/coverage/lcov-report/src/builder/revision.ts.html +1 -1
- package/coverage/lcov-report/src/builder/traffic.ts.html +96 -24
- package/coverage/lcov-report/src/list/index.html +116 -0
- package/coverage/lcov-report/src/{tester → list}/matrix.ts.html +87 -21
- package/coverage/lcov-report/src/tester/helpers.ts.html +313 -0
- package/coverage/lcov-report/src/tester/index.html +20 -35
- package/coverage/lcov.info +592 -436
- package/lib/assess-distribution/index.d.ts +1 -1
- package/lib/assess-distribution/index.js +102 -162
- package/lib/assess-distribution/index.js.map +1 -1
- package/lib/benchmark/index.js +87 -143
- package/lib/benchmark/index.js.map +1 -1
- package/lib/builder/allocator.d.ts +1 -1
- package/lib/builder/allocator.js +12 -12
- package/lib/builder/allocator.js.map +1 -1
- package/lib/builder/allocator.spec.js +22 -22
- package/lib/builder/allocator.spec.js.map +1 -1
- package/lib/builder/buildDatafile.d.ts +4 -3
- package/lib/builder/buildDatafile.js +311 -388
- package/lib/builder/buildDatafile.js.map +1 -1
- package/lib/builder/buildProject.d.ts +2 -1
- package/lib/builder/buildProject.js +96 -183
- package/lib/builder/buildProject.js.map +1 -1
- package/lib/builder/convertToV1.d.ts +10 -0
- package/lib/builder/convertToV1.js +119 -0
- package/lib/builder/convertToV1.js.map +1 -0
- package/lib/builder/getFeatureRanges.d.ts +1 -1
- package/lib/builder/getFeatureRanges.js +32 -105
- package/lib/builder/getFeatureRanges.js.map +1 -1
- package/lib/builder/hashes.d.ts +4 -0
- package/lib/builder/hashes.js +70 -0
- package/lib/builder/hashes.js.map +1 -0
- package/lib/builder/revision.js +2 -2
- package/lib/builder/revision.js.map +1 -1
- package/lib/builder/revision.spec.js +1 -1
- package/lib/builder/revision.spec.js.map +1 -1
- package/lib/builder/traffic.d.ts +1 -1
- package/lib/builder/traffic.js +57 -49
- package/lib/builder/traffic.js.map +1 -1
- package/lib/builder/traffic.spec.js +14 -14
- package/lib/builder/traffic.spec.js.map +1 -1
- package/lib/cli/cli.js +60 -129
- package/lib/cli/cli.js.map +1 -1
- package/lib/cli/plugins.js +14 -16
- package/lib/cli/plugins.js.map +1 -1
- package/lib/config/parsers.js +1 -1
- package/lib/config/parsers.js.map +1 -1
- package/lib/config/projectConfig.d.ts +8 -6
- package/lib/config/projectConfig.js +31 -72
- package/lib/config/projectConfig.js.map +1 -1
- package/lib/datasource/adapter.d.ts +1 -1
- package/lib/datasource/adapter.js +2 -5
- package/lib/datasource/adapter.js.map +1 -1
- package/lib/datasource/datasource.d.ts +2 -1
- package/lib/datasource/datasource.js +107 -148
- package/lib/datasource/datasource.js.map +1 -1
- package/lib/datasource/filesystemAdapter.d.ts +1 -1
- package/lib/datasource/filesystemAdapter.js +224 -360
- package/lib/datasource/filesystemAdapter.js.map +1 -1
- package/lib/evaluate/index.d.ts +1 -1
- package/lib/evaluate/index.js +120 -188
- package/lib/evaluate/index.js.map +1 -1
- package/lib/find-duplicate-segments/findDuplicateSegments.d.ts +1 -1
- package/lib/find-duplicate-segments/findDuplicateSegments.js +40 -128
- package/lib/find-duplicate-segments/findDuplicateSegments.js.map +1 -1
- package/lib/find-duplicate-segments/index.js +27 -82
- package/lib/find-duplicate-segments/index.js.map +1 -1
- package/lib/find-usage/index.d.ts +7 -5
- package/lib/find-usage/index.js +333 -507
- package/lib/find-usage/index.js.map +1 -1
- package/lib/generate-code/index.js +36 -91
- package/lib/generate-code/index.js.map +1 -1
- package/lib/generate-code/typescript.js +117 -157
- package/lib/generate-code/typescript.js.map +1 -1
- package/lib/index.d.ts +0 -1
- package/lib/index.js +0 -1
- package/lib/index.js.map +1 -1
- package/lib/info/index.js +45 -133
- package/lib/info/index.js.map +1 -1
- package/lib/init/index.d.ts +1 -1
- package/lib/init/index.js +16 -64
- package/lib/init/index.js.map +1 -1
- package/lib/linter/attributeSchema.d.ts +21 -6
- package/lib/linter/attributeSchema.js +18 -4
- package/lib/linter/attributeSchema.js.map +1 -1
- package/lib/linter/checkCircularDependency.d.ts +1 -1
- package/lib/linter/checkCircularDependency.js +22 -80
- package/lib/linter/checkCircularDependency.js.map +1 -1
- package/lib/linter/checkPercentageExceedingSlot.d.ts +1 -1
- package/lib/linter/checkPercentageExceedingSlot.js +36 -76
- package/lib/linter/checkPercentageExceedingSlot.js.map +1 -1
- package/lib/linter/conditionSchema.d.ts +1 -1
- package/lib/linter/conditionSchema.js +89 -41
- package/lib/linter/conditionSchema.js.map +1 -1
- package/lib/linter/featureSchema.d.ts +345 -197
- package/lib/linter/featureSchema.js +313 -172
- package/lib/linter/featureSchema.js.map +1 -1
- package/lib/linter/groupSchema.js +6 -6
- package/lib/linter/groupSchema.js.map +1 -1
- package/lib/linter/lintProject.js +306 -480
- package/lib/linter/lintProject.js.map +1 -1
- package/lib/linter/printError.js +7 -7
- package/lib/linter/printError.js.map +1 -1
- package/lib/linter/segmentSchema.js +2 -2
- package/lib/linter/segmentSchema.js.map +1 -1
- package/lib/linter/testSchema.d.ts +155 -3
- package/lib/linter/testSchema.js +47 -17
- package/lib/linter/testSchema.js.map +1 -1
- package/lib/list/index.d.ts +1 -0
- package/lib/list/index.js +349 -517
- package/lib/list/index.js.map +1 -1
- package/lib/{tester → list}/matrix.d.ts +1 -1
- package/lib/{tester → list}/matrix.js +50 -42
- package/lib/list/matrix.js.map +1 -0
- package/lib/{tester → list}/matrix.spec.js +7 -7
- package/lib/list/matrix.spec.js.map +1 -0
- package/lib/site/exportSite.js +25 -71
- package/lib/site/exportSite.js.map +1 -1
- package/lib/site/generateHistory.d.ts +1 -1
- package/lib/site/generateHistory.js +26 -82
- package/lib/site/generateHistory.js.map +1 -1
- package/lib/site/generateSiteSearchIndex.d.ts +1 -1
- package/lib/site/generateSiteSearchIndex.js +182 -259
- package/lib/site/generateSiteSearchIndex.js.map +1 -1
- package/lib/site/getLastModifiedFromHistory.d.ts +1 -1
- package/lib/site/getLastModifiedFromHistory.js +2 -2
- package/lib/site/getLastModifiedFromHistory.js.map +1 -1
- package/lib/site/getOwnerAndRepoFromUrl.js +6 -6
- package/lib/site/getOwnerAndRepoFromUrl.js.map +1 -1
- package/lib/site/getRelativePaths.js +7 -7
- package/lib/site/getRelativePaths.js.map +1 -1
- package/lib/site/getRepoDetails.js +20 -20
- package/lib/site/getRepoDetails.js.map +1 -1
- package/lib/site/index.js +25 -73
- package/lib/site/index.js.map +1 -1
- package/lib/site/serveSite.js +10 -10
- package/lib/site/serveSite.js.map +1 -1
- package/lib/tester/helpers.d.ts +2 -0
- package/lib/tester/helpers.js +71 -0
- package/lib/tester/helpers.js.map +1 -0
- package/lib/tester/helpers.spec.js +115 -0
- package/lib/tester/helpers.spec.js.map +1 -0
- package/lib/tester/index.d.ts +0 -1
- package/lib/tester/index.js +0 -1
- package/lib/tester/index.js.map +1 -1
- package/lib/tester/prettyDuration.js +11 -11
- package/lib/tester/prettyDuration.js.map +1 -1
- package/lib/tester/printTestResult.d.ts +1 -1
- package/lib/tester/printTestResult.js +35 -15
- package/lib/tester/printTestResult.js.map +1 -1
- package/lib/tester/testFeature.d.ts +4 -2
- package/lib/tester/testFeature.js +264 -226
- package/lib/tester/testFeature.js.map +1 -1
- package/lib/tester/testProject.d.ts +3 -7
- package/lib/tester/testProject.js +145 -246
- package/lib/tester/testProject.js.map +1 -1
- package/lib/tester/testSegment.d.ts +5 -2
- package/lib/tester/testSegment.js +65 -102
- package/lib/tester/testSegment.js.map +1 -1
- package/lib/utils/extractKeys.d.ts +2 -1
- package/lib/utils/extractKeys.js +57 -12
- package/lib/utils/extractKeys.js.map +1 -1
- package/lib/utils/git.d.ts +1 -1
- package/lib/utils/git.js +23 -23
- package/lib/utils/git.js.map +1 -1
- package/lib/utils/pretty.js +2 -4
- package/lib/utils/pretty.js.map +1 -1
- package/package.json +5 -6
- package/src/assess-distribution/index.ts +3 -2
- package/src/benchmark/index.ts +3 -3
- package/src/builder/allocator.spec.ts +1 -1
- package/src/builder/allocator.ts +1 -1
- package/src/builder/buildDatafile.ts +161 -124
- package/src/builder/buildProject.ts +6 -3
- package/src/builder/convertToV1.ts +166 -0
- package/src/builder/getFeatureRanges.ts +1 -1
- package/src/builder/hashes.ts +109 -0
- package/src/builder/traffic.ts +40 -16
- package/src/cli/cli.ts +1 -1
- package/src/cli/plugins.ts +0 -2
- package/src/config/projectConfig.ts +13 -10
- package/src/datasource/adapter.ts +1 -1
- package/src/datasource/datasource.ts +23 -2
- package/src/datasource/filesystemAdapter.ts +11 -12
- package/src/evaluate/index.ts +7 -6
- package/src/find-duplicate-segments/findDuplicateSegments.ts +1 -1
- package/src/find-usage/index.ts +111 -44
- package/src/generate-code/index.ts +1 -3
- package/src/generate-code/typescript.ts +7 -29
- package/src/index.ts +0 -1
- package/src/info/index.ts +2 -2
- package/src/init/index.ts +2 -2
- package/src/linter/attributeSchema.ts +18 -2
- package/src/linter/checkCircularDependency.ts +1 -1
- package/src/linter/checkPercentageExceedingSlot.ts +28 -8
- package/src/linter/conditionSchema.ts +66 -10
- package/src/linter/featureSchema.ts +312 -116
- package/src/linter/lintProject.ts +9 -4
- package/src/linter/testSchema.ts +42 -3
- package/src/list/index.ts +18 -30
- package/src/{tester → list}/matrix.ts +33 -11
- package/src/site/exportSite.ts +2 -4
- package/src/site/generateHistory.ts +1 -1
- package/src/site/generateSiteSearchIndex.ts +58 -50
- package/src/site/getLastModifiedFromHistory.ts +1 -1
- package/src/tester/helpers.spec.ts +149 -0
- package/src/tester/helpers.ts +76 -0
- package/src/tester/index.ts +0 -1
- package/src/tester/printTestResult.ts +25 -3
- package/src/tester/testFeature.ts +270 -124
- package/src/tester/testProject.ts +28 -49
- package/src/tester/testSegment.ts +48 -40
- package/src/utils/extractKeys.ts +58 -1
- package/src/utils/git.ts +1 -1
- package/tsconfig.cjs.json +1 -0
- package/coverage/lcov-report/lib/tester/checkIfObjectsAreEqual.js.html +0 -151
- package/coverage/lcov-report/src/tester/checkIfObjectsAreEqual.ts.html +0 -157
- package/lib/restore/index.d.ts +0 -4
- package/lib/restore/index.js +0 -91
- package/lib/restore/index.js.map +0 -1
- package/lib/tester/checkIfArraysAreEqual.d.ts +0 -1
- package/lib/tester/checkIfArraysAreEqual.js +0 -18
- package/lib/tester/checkIfArraysAreEqual.js.map +0 -1
- package/lib/tester/checkIfObjectsAreEqual.d.ts +0 -1
- package/lib/tester/checkIfObjectsAreEqual.js +0 -23
- package/lib/tester/checkIfObjectsAreEqual.js.map +0 -1
- package/lib/tester/checkIfObjectsAreEqual.spec.js +0 -26
- package/lib/tester/checkIfObjectsAreEqual.spec.js.map +0 -1
- package/lib/tester/matrix.js.map +0 -1
- package/lib/tester/matrix.spec.js.map +0 -1
- package/src/restore/index.ts +0 -42
- package/src/tester/checkIfArraysAreEqual.ts +0 -16
- package/src/tester/checkIfObjectsAreEqual.spec.ts +0 -31
- package/src/tester/checkIfObjectsAreEqual.ts +0 -24
- /package/lib/{tester → list}/matrix.spec.d.ts +0 -0
- /package/lib/tester/{checkIfObjectsAreEqual.spec.d.ts → helpers.spec.d.ts} +0 -0
- /package/src/{tester → list}/matrix.spec.ts +0 -0
|
@@ -1,26 +1,29 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type {
|
|
2
2
|
DatafileContent,
|
|
3
3
|
TestFeature,
|
|
4
4
|
TestResult,
|
|
5
5
|
TestResultAssertion,
|
|
6
6
|
TestResultAssertionError,
|
|
7
7
|
} from "@featurevisor/types";
|
|
8
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
createInstance,
|
|
10
|
+
FeaturevisorInstance,
|
|
11
|
+
LogLevel,
|
|
12
|
+
MAX_BUCKETED_NUMBER,
|
|
13
|
+
OverrideOptions,
|
|
14
|
+
} from "@featurevisor/sdk";
|
|
9
15
|
|
|
10
16
|
import { Datasource } from "../datasource";
|
|
11
17
|
import { ProjectConfig } from "../config";
|
|
12
18
|
|
|
13
|
-
import { checkIfArraysAreEqual } from "./
|
|
14
|
-
import { checkIfObjectsAreEqual } from "./checkIfObjectsAreEqual";
|
|
15
|
-
import { getFeatureAssertionsFromMatrix } from "./matrix";
|
|
19
|
+
import { checkIfArraysAreEqual, checkIfObjectsAreEqual } from "./helpers";
|
|
16
20
|
import type { DatafileContentByEnvironment } from "./testProject";
|
|
17
21
|
|
|
18
22
|
export async function testFeature(
|
|
19
23
|
datasource: Datasource,
|
|
20
24
|
projectConfig: ProjectConfig,
|
|
21
25
|
test: TestFeature,
|
|
22
|
-
options: { verbose?: boolean; showDatafile?: boolean } = {},
|
|
23
|
-
patterns,
|
|
26
|
+
options: { verbose?: boolean; quiet?: boolean; showDatafile?: boolean; [key: string]: any } = {},
|
|
24
27
|
datafileContentByEnvironment: DatafileContentByEnvironment,
|
|
25
28
|
): Promise<TestResult> {
|
|
26
29
|
const testStartTime = Date.now();
|
|
@@ -38,166 +41,309 @@ export async function testFeature(
|
|
|
38
41
|
};
|
|
39
42
|
|
|
40
43
|
for (let aIndex = 0; aIndex < test.assertions.length; aIndex++) {
|
|
41
|
-
const
|
|
44
|
+
const assertionStartTime = Date.now();
|
|
45
|
+
const assertion = test.assertions[aIndex];
|
|
46
|
+
|
|
47
|
+
const testResultAssertion: TestResultAssertion = {
|
|
48
|
+
description: assertion.description as string,
|
|
49
|
+
duration: 0,
|
|
50
|
+
passed: true,
|
|
51
|
+
errors: [],
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const datafileContent = datafileContentByEnvironment.get(assertion.environment || false);
|
|
55
|
+
|
|
56
|
+
if (options.showDatafile) {
|
|
57
|
+
console.log("");
|
|
58
|
+
console.log(JSON.stringify(datafileContent, null, 2));
|
|
59
|
+
console.log("");
|
|
60
|
+
}
|
|
42
61
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
62
|
+
let logLevel: LogLevel = "warn";
|
|
63
|
+
if (options.verbose) {
|
|
64
|
+
logLevel = "debug";
|
|
65
|
+
} else if (options.quiet) {
|
|
66
|
+
logLevel = "fatal";
|
|
67
|
+
}
|
|
46
68
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
69
|
+
const sdk: FeaturevisorInstance = createInstance({
|
|
70
|
+
datafile: datafileContent as DatafileContent,
|
|
71
|
+
sticky: assertion.sticky ? assertion.sticky : {},
|
|
72
|
+
hooks: [
|
|
73
|
+
{
|
|
74
|
+
name: "tester",
|
|
75
|
+
bucketValue: ({ bucketValue }) => {
|
|
76
|
+
if (typeof assertion.at !== "undefined") {
|
|
77
|
+
return assertion.at * (MAX_BUCKETED_NUMBER / 100);
|
|
78
|
+
}
|
|
53
79
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
80
|
+
return bucketValue;
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
],
|
|
84
|
+
logLevel,
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
const feature = await datasource.readFeature(featureKey);
|
|
88
|
+
if (!feature) {
|
|
89
|
+
testResult.notFound = true;
|
|
90
|
+
testResult.passed = false;
|
|
91
|
+
|
|
92
|
+
return testResult;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (assertion.context) {
|
|
96
|
+
sdk.setContext(assertion.context);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* expectedToBeEnabled
|
|
101
|
+
*/
|
|
102
|
+
function testExpectedToBeEnabled(sdk, assertion, details = {}) {
|
|
103
|
+
const isEnabled = sdk.isEnabled(featureKey, assertion.context || {});
|
|
57
104
|
|
|
58
|
-
|
|
105
|
+
if (isEnabled !== assertion.expectedToBeEnabled) {
|
|
106
|
+
testResult.passed = false;
|
|
107
|
+
testResultAssertion.passed = false;
|
|
59
108
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
109
|
+
(testResultAssertion.errors as TestResultAssertionError[]).push({
|
|
110
|
+
type: "flag",
|
|
111
|
+
expected: assertion.expectedToBeEnabled,
|
|
112
|
+
actual: isEnabled,
|
|
113
|
+
details,
|
|
114
|
+
});
|
|
64
115
|
}
|
|
116
|
+
}
|
|
65
117
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
return assertion.at * (MAX_BUCKETED_NUMBER / 100);
|
|
70
|
-
},
|
|
71
|
-
});
|
|
118
|
+
if ("expectedToBeEnabled" in assertion) {
|
|
119
|
+
testExpectedToBeEnabled(sdk, assertion);
|
|
120
|
+
}
|
|
72
121
|
|
|
73
|
-
|
|
74
|
-
|
|
122
|
+
/**
|
|
123
|
+
* expectedVariation
|
|
124
|
+
*/
|
|
125
|
+
function testExpectedVariation(sdk, assertion, details = {}) {
|
|
126
|
+
const overrideOptions: OverrideOptions = {};
|
|
127
|
+
if (assertion.defaultVariationValue) {
|
|
128
|
+
overrideOptions.defaultVariationValue = assertion.defaultVariationValue;
|
|
75
129
|
}
|
|
76
130
|
|
|
77
|
-
const
|
|
78
|
-
|
|
79
|
-
|
|
131
|
+
const variation = sdk.getVariation(featureKey, assertion.context || {}, overrideOptions);
|
|
132
|
+
|
|
133
|
+
if (variation !== assertion.expectedVariation) {
|
|
80
134
|
testResult.passed = false;
|
|
135
|
+
testResultAssertion.passed = false;
|
|
81
136
|
|
|
82
|
-
|
|
137
|
+
(testResultAssertion.errors as TestResultAssertionError[]).push({
|
|
138
|
+
type: "variation",
|
|
139
|
+
expected: assertion.expectedVariation,
|
|
140
|
+
actual: variation,
|
|
141
|
+
details,
|
|
142
|
+
});
|
|
83
143
|
}
|
|
144
|
+
}
|
|
84
145
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
if (isEnabled !== assertion.expectedToBeEnabled) {
|
|
90
|
-
testResult.passed = false;
|
|
91
|
-
testResultAssertion.passed = false;
|
|
146
|
+
if ("expectedVariation" in assertion) {
|
|
147
|
+
testExpectedVariation(sdk, assertion);
|
|
148
|
+
}
|
|
92
149
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
150
|
+
/**
|
|
151
|
+
* expectedVariables
|
|
152
|
+
*/
|
|
153
|
+
function testExpectedVariables(sdk, assertion, details = {}) {
|
|
154
|
+
Object.keys(assertion.expectedVariables).forEach(function (variableKey) {
|
|
155
|
+
const expectedValue =
|
|
156
|
+
assertion.expectedVariables && assertion.expectedVariables[variableKey];
|
|
157
|
+
|
|
158
|
+
const overrideOptions: OverrideOptions = {};
|
|
159
|
+
if (assertion.defaultVariableValues && assertion.defaultVariableValues[variableKey]) {
|
|
160
|
+
overrideOptions.defaultVariableValue = assertion.defaultVariableValues[variableKey];
|
|
98
161
|
}
|
|
99
|
-
}
|
|
100
162
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
163
|
+
const actualValue = sdk.getVariable(
|
|
164
|
+
featureKey,
|
|
165
|
+
variableKey,
|
|
166
|
+
assertion.context || {},
|
|
167
|
+
overrideOptions,
|
|
168
|
+
);
|
|
169
|
+
|
|
170
|
+
let passed;
|
|
171
|
+
|
|
172
|
+
const variableSchema = feature.variablesSchema?.[variableKey];
|
|
104
173
|
|
|
105
|
-
if (
|
|
174
|
+
if (!variableSchema) {
|
|
106
175
|
testResult.passed = false;
|
|
107
176
|
testResultAssertion.passed = false;
|
|
108
177
|
|
|
109
178
|
(testResultAssertion.errors as TestResultAssertionError[]).push({
|
|
110
|
-
type: "
|
|
179
|
+
type: "variable",
|
|
111
180
|
expected: assertion.expectedVariation,
|
|
112
|
-
actual:
|
|
181
|
+
actual: undefined,
|
|
182
|
+
message: `schema for variable "${variableKey}" not found in feature`,
|
|
113
183
|
});
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
184
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
Object.keys(assertion.expectedVariables).forEach(function (variableKey) {
|
|
120
|
-
const expectedValue =
|
|
121
|
-
assertion.expectedVariables && assertion.expectedVariables[variableKey];
|
|
122
|
-
const actualValue = sdk.getVariable(featureKey, variableKey, assertion.context);
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
123
187
|
|
|
124
|
-
|
|
188
|
+
if (variableSchema.type === "json") {
|
|
189
|
+
// JSON type
|
|
190
|
+
const parsedExpectedValue =
|
|
191
|
+
typeof expectedValue === "string" ? JSON.parse(expectedValue as string) : expectedValue;
|
|
125
192
|
|
|
126
|
-
|
|
193
|
+
if (Array.isArray(actualValue)) {
|
|
194
|
+
passed = checkIfArraysAreEqual(parsedExpectedValue, actualValue);
|
|
195
|
+
} else if (typeof actualValue === "object") {
|
|
196
|
+
passed = checkIfObjectsAreEqual(parsedExpectedValue, actualValue);
|
|
197
|
+
} else {
|
|
198
|
+
passed = JSON.stringify(parsedExpectedValue) === JSON.stringify(actualValue);
|
|
199
|
+
}
|
|
127
200
|
|
|
128
|
-
if (!
|
|
201
|
+
if (!passed) {
|
|
129
202
|
testResult.passed = false;
|
|
130
203
|
testResultAssertion.passed = false;
|
|
131
204
|
|
|
132
205
|
(testResultAssertion.errors as TestResultAssertionError[]).push({
|
|
133
206
|
type: "variable",
|
|
134
|
-
expected:
|
|
135
|
-
|
|
136
|
-
|
|
207
|
+
expected:
|
|
208
|
+
typeof expectedValue !== "string" ? JSON.stringify(expectedValue) : expectedValue,
|
|
209
|
+
actual: typeof actualValue !== "string" ? JSON.stringify(actualValue) : actualValue,
|
|
210
|
+
details: {
|
|
211
|
+
...details,
|
|
212
|
+
variableKey,
|
|
213
|
+
},
|
|
137
214
|
});
|
|
215
|
+
}
|
|
216
|
+
} else {
|
|
217
|
+
// other types
|
|
218
|
+
if (typeof expectedValue === "object") {
|
|
219
|
+
passed = checkIfObjectsAreEqual(expectedValue, actualValue);
|
|
220
|
+
} else if (Array.isArray(expectedValue)) {
|
|
221
|
+
passed = checkIfArraysAreEqual(expectedValue, actualValue);
|
|
222
|
+
} else {
|
|
223
|
+
passed = expectedValue === actualValue;
|
|
224
|
+
}
|
|
138
225
|
|
|
139
|
-
|
|
226
|
+
if (!passed) {
|
|
227
|
+
testResult.passed = false;
|
|
228
|
+
testResultAssertion.passed = false;
|
|
229
|
+
|
|
230
|
+
(testResultAssertion.errors as TestResultAssertionError[]).push({
|
|
231
|
+
type: "variable",
|
|
232
|
+
expected: expectedValue as string,
|
|
233
|
+
actual: actualValue as string,
|
|
234
|
+
details: {
|
|
235
|
+
...details,
|
|
236
|
+
variableKey,
|
|
237
|
+
},
|
|
238
|
+
});
|
|
140
239
|
}
|
|
240
|
+
}
|
|
241
|
+
});
|
|
242
|
+
}
|
|
141
243
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
typeof expectedValue === "string"
|
|
146
|
-
? JSON.parse(expectedValue as string)
|
|
147
|
-
: expectedValue;
|
|
148
|
-
|
|
149
|
-
if (Array.isArray(actualValue)) {
|
|
150
|
-
passed = checkIfArraysAreEqual(parsedExpectedValue, actualValue);
|
|
151
|
-
} else if (typeof actualValue === "object") {
|
|
152
|
-
passed = checkIfObjectsAreEqual(parsedExpectedValue, actualValue);
|
|
153
|
-
} else {
|
|
154
|
-
passed = JSON.stringify(parsedExpectedValue) === JSON.stringify(actualValue);
|
|
155
|
-
}
|
|
244
|
+
if (typeof assertion.expectedVariables === "object") {
|
|
245
|
+
testExpectedVariables(sdk, assertion);
|
|
246
|
+
}
|
|
156
247
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
details: {
|
|
167
|
-
variableKey,
|
|
168
|
-
},
|
|
169
|
-
});
|
|
170
|
-
}
|
|
171
|
-
} else {
|
|
172
|
-
// other types
|
|
173
|
-
if (typeof expectedValue === "object") {
|
|
174
|
-
passed = checkIfObjectsAreEqual(expectedValue, actualValue);
|
|
175
|
-
} else if (Array.isArray(expectedValue)) {
|
|
176
|
-
passed = checkIfArraysAreEqual(expectedValue, actualValue);
|
|
177
|
-
} else {
|
|
178
|
-
passed = expectedValue === actualValue;
|
|
179
|
-
}
|
|
248
|
+
/**
|
|
249
|
+
* expectedEvaluations
|
|
250
|
+
*/
|
|
251
|
+
function testExpectedEvaluations(sdk, assertion, rootDetails = {}) {
|
|
252
|
+
function testEvaluation(type, evaluation, expected, details = {}) {
|
|
253
|
+
for (const [key, value] of Object.entries(expected)) {
|
|
254
|
+
if (evaluation[key] !== value) {
|
|
255
|
+
testResult.passed = false;
|
|
256
|
+
testResultAssertion.passed = false;
|
|
180
257
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
});
|
|
193
|
-
}
|
|
258
|
+
(testResultAssertion.errors as TestResultAssertionError[]).push({
|
|
259
|
+
type: "evaluation",
|
|
260
|
+
expected: value as string | number | boolean | null | undefined,
|
|
261
|
+
actual: evaluation[key],
|
|
262
|
+
details: {
|
|
263
|
+
...rootDetails,
|
|
264
|
+
...details,
|
|
265
|
+
evaluationType: type,
|
|
266
|
+
evaluationKey: key,
|
|
267
|
+
},
|
|
268
|
+
});
|
|
194
269
|
}
|
|
195
|
-
}
|
|
270
|
+
}
|
|
196
271
|
}
|
|
197
272
|
|
|
198
|
-
|
|
199
|
-
|
|
273
|
+
if (assertion.expectedEvaluations.flag) {
|
|
274
|
+
const evaluation = sdk.evaluateFlag(featureKey, assertion.context || {});
|
|
275
|
+
testEvaluation("flag", evaluation, assertion.expectedEvaluations.flag);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
if (assertion.expectedEvaluations.variation) {
|
|
279
|
+
const evaluation = sdk.evaluateVariation(featureKey, assertion.context || {});
|
|
280
|
+
testEvaluation("variation", evaluation, assertion.expectedEvaluations.variation);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
if (assertion.expectedEvaluations.variables) {
|
|
284
|
+
const variableKeys = Object.keys(assertion.expectedEvaluations.variables);
|
|
285
|
+
|
|
286
|
+
for (const variableKey of variableKeys) {
|
|
287
|
+
const evaluation = sdk.evaluateVariable(featureKey, variableKey, assertion.context || {});
|
|
288
|
+
testEvaluation(
|
|
289
|
+
"variable",
|
|
290
|
+
evaluation,
|
|
291
|
+
assertion.expectedEvaluations.variables[variableKey],
|
|
292
|
+
{ variableKey },
|
|
293
|
+
);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
200
296
|
}
|
|
297
|
+
|
|
298
|
+
if (assertion.expectedEvaluations) {
|
|
299
|
+
testExpectedEvaluations(sdk, assertion);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* children
|
|
304
|
+
*/
|
|
305
|
+
if (Array.isArray(assertion.children)) {
|
|
306
|
+
let childIndex = 0;
|
|
307
|
+
|
|
308
|
+
for (const child of assertion.children) {
|
|
309
|
+
const childSdk = sdk.spawn(child.context || {}, {
|
|
310
|
+
sticky: assertion.sticky || {},
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
// expectedToBeEnabled
|
|
314
|
+
if (typeof child.expectedToBeEnabled !== "undefined") {
|
|
315
|
+
testExpectedToBeEnabled(childSdk, child, {
|
|
316
|
+
childIndex,
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// expectedVariation
|
|
321
|
+
if (typeof child.expectedVariation !== "undefined") {
|
|
322
|
+
testExpectedVariation(childSdk, child, {
|
|
323
|
+
childIndex,
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// expectedVariables
|
|
328
|
+
if (typeof child.expectedVariables === "object") {
|
|
329
|
+
testExpectedVariables(childSdk, child, {
|
|
330
|
+
childIndex,
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// expectedEvaluations
|
|
335
|
+
if (typeof child.expectedEvaluations === "object") {
|
|
336
|
+
testExpectedEvaluations(childSdk, child, {
|
|
337
|
+
childIndex,
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
childIndex++;
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
testResultAssertion.duration = Date.now() - assertionStartTime;
|
|
346
|
+
testResult.assertions.push(testResultAssertion);
|
|
201
347
|
}
|
|
202
348
|
|
|
203
349
|
testResult.duration = Date.now() - testStartTime;
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import * as fs from "fs";
|
|
2
|
-
import * as path from "path";
|
|
3
2
|
|
|
4
|
-
import { TestSegment, TestFeature, DatafileContent } from "@featurevisor/types";
|
|
3
|
+
import type { TestSegment, TestFeature, Test, DatafileContent } from "@featurevisor/types";
|
|
5
4
|
|
|
6
5
|
import { testSegment } from "./testSegment";
|
|
7
6
|
import { testFeature } from "./testFeature";
|
|
@@ -13,6 +12,7 @@ import { printTestResult } from "./printTestResult";
|
|
|
13
12
|
import { buildDatafile } from "../builder";
|
|
14
13
|
import { SCHEMA_VERSION } from "../config";
|
|
15
14
|
import { Plugin } from "../cli";
|
|
15
|
+
import { listEntities } from "../list";
|
|
16
16
|
|
|
17
17
|
export interface TestProjectOptions {
|
|
18
18
|
keyPattern?: string;
|
|
@@ -24,11 +24,6 @@ export interface TestProjectOptions {
|
|
|
24
24
|
inflate?: number;
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
export interface TestPatterns {
|
|
28
|
-
keyPattern?: RegExp;
|
|
29
|
-
assertionPattern?: RegExp;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
27
|
export interface ExecutionResult {
|
|
33
28
|
passed: boolean;
|
|
34
29
|
assertionsCount: {
|
|
@@ -40,22 +35,18 @@ export interface ExecutionResult {
|
|
|
40
35
|
export type DatafileContentByEnvironment = Map<string | false, DatafileContent>;
|
|
41
36
|
|
|
42
37
|
export async function executeTest(
|
|
43
|
-
|
|
38
|
+
test: Test,
|
|
44
39
|
deps: Dependencies,
|
|
45
40
|
options: TestProjectOptions,
|
|
46
|
-
patterns: TestPatterns,
|
|
47
41
|
datafileContentByEnvironment: DatafileContentByEnvironment,
|
|
48
42
|
): Promise<ExecutionResult | undefined> {
|
|
49
43
|
const { datasource, projectConfig, rootDirectoryPath } = deps;
|
|
50
44
|
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
-
.replace(rootDirectoryPath + path.sep, "");
|
|
54
|
-
|
|
55
|
-
const t = await datasource.readTest(testFile);
|
|
45
|
+
const extension = datasource.getExtension();
|
|
46
|
+
const relativeTestFilePath = test.key + (extension ? `.${extension}` : "");
|
|
56
47
|
|
|
57
|
-
const tAsSegment =
|
|
58
|
-
const tAsFeature =
|
|
48
|
+
const tAsSegment = test as TestSegment;
|
|
49
|
+
const tAsFeature = test as TestFeature;
|
|
59
50
|
const key = tAsSegment.segment || tAsFeature.feature;
|
|
60
51
|
const type = tAsSegment.segment ? "segment" : "feature";
|
|
61
52
|
|
|
@@ -68,26 +59,21 @@ export async function executeTest(
|
|
|
68
59
|
};
|
|
69
60
|
|
|
70
61
|
if (!key) {
|
|
71
|
-
console.error(` => Invalid test: ${JSON.stringify(
|
|
62
|
+
console.error(` => Invalid test: ${JSON.stringify(test)}`);
|
|
72
63
|
executionResult.passed = false;
|
|
73
64
|
|
|
74
65
|
return executionResult;
|
|
75
66
|
}
|
|
76
67
|
|
|
77
|
-
if (patterns.keyPattern && !patterns.keyPattern.test(key)) {
|
|
78
|
-
return;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
68
|
let testResult;
|
|
82
69
|
if (type === "segment") {
|
|
83
|
-
testResult = await testSegment(datasource, tAsSegment,
|
|
70
|
+
testResult = await testSegment(datasource, tAsSegment, options);
|
|
84
71
|
} else {
|
|
85
72
|
testResult = await testFeature(
|
|
86
73
|
datasource,
|
|
87
74
|
projectConfig,
|
|
88
75
|
tAsFeature,
|
|
89
76
|
options,
|
|
90
|
-
patterns,
|
|
91
77
|
datafileContentByEnvironment,
|
|
92
78
|
);
|
|
93
79
|
}
|
|
@@ -117,9 +103,9 @@ export async function executeTest(
|
|
|
117
103
|
|
|
118
104
|
export async function testProject(
|
|
119
105
|
deps: Dependencies,
|
|
120
|
-
|
|
106
|
+
testOptions: TestProjectOptions = {},
|
|
121
107
|
): Promise<boolean> {
|
|
122
|
-
const { projectConfig, datasource } = deps;
|
|
108
|
+
const { rootDirectoryPath, projectConfig, datasource, options } = deps;
|
|
123
109
|
|
|
124
110
|
let hasError = false;
|
|
125
111
|
|
|
@@ -130,22 +116,8 @@ export async function testProject(
|
|
|
130
116
|
return hasError;
|
|
131
117
|
}
|
|
132
118
|
|
|
133
|
-
const testFiles = await datasource.listTests();
|
|
134
|
-
|
|
135
|
-
if (testFiles.length === 0) {
|
|
136
|
-
console.error(`No tests found in: ${projectConfig.testsDirectoryPath}`);
|
|
137
|
-
hasError = true;
|
|
138
|
-
|
|
139
|
-
return hasError;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
119
|
const startTime = Date.now();
|
|
143
120
|
|
|
144
|
-
const patterns: TestPatterns = {
|
|
145
|
-
keyPattern: options.keyPattern ? new RegExp(options.keyPattern) : undefined,
|
|
146
|
-
assertionPattern: options.assertionPattern ? new RegExp(options.assertionPattern) : undefined,
|
|
147
|
-
};
|
|
148
|
-
|
|
149
121
|
let passedTestsCount = 0;
|
|
150
122
|
let failedTestsCount = 0;
|
|
151
123
|
|
|
@@ -170,7 +142,7 @@ export async function testProject(
|
|
|
170
142
|
existingState,
|
|
171
143
|
);
|
|
172
144
|
|
|
173
|
-
datafileContentByEnvironment.set(environment, datafileContent);
|
|
145
|
+
datafileContentByEnvironment.set(environment, datafileContent as DatafileContent);
|
|
174
146
|
}
|
|
175
147
|
}
|
|
176
148
|
|
|
@@ -189,17 +161,24 @@ export async function testProject(
|
|
|
189
161
|
existingState,
|
|
190
162
|
);
|
|
191
163
|
|
|
192
|
-
datafileContentByEnvironment.set(false, datafileContent);
|
|
164
|
+
datafileContentByEnvironment.set(false, datafileContent as DatafileContent);
|
|
193
165
|
}
|
|
194
166
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
167
|
+
const tests = await listEntities<Test>(
|
|
168
|
+
{
|
|
169
|
+
rootDirectoryPath,
|
|
170
|
+
projectConfig,
|
|
171
|
+
datasource,
|
|
172
|
+
options: {
|
|
173
|
+
...testOptions,
|
|
174
|
+
applyMatrix: true,
|
|
175
|
+
},
|
|
176
|
+
},
|
|
177
|
+
"test",
|
|
178
|
+
);
|
|
179
|
+
|
|
180
|
+
for (const test of tests) {
|
|
181
|
+
const executionResult = await executeTest(test, deps, options, datafileContentByEnvironment);
|
|
203
182
|
|
|
204
183
|
if (!executionResult) {
|
|
205
184
|
continue;
|