@featurevisor/core 1.1.1 → 1.2.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/.eslintcache +1 -1
- package/CHANGELOG.md +19 -0
- package/coverage/clover.xml +161 -5
- package/coverage/coverage-final.json +2 -0
- package/coverage/lcov-report/index.html +32 -32
- package/coverage/lcov-report/lib/builder/allocator.js.html +1 -1
- package/coverage/lcov-report/lib/builder/index.html +1 -1
- package/coverage/lcov-report/lib/builder/traffic.js.html +1 -1
- package/coverage/lcov-report/lib/tester/checkIfObjectsAreEqual.js.html +1 -1
- package/coverage/lcov-report/lib/tester/index.html +25 -10
- package/coverage/lcov-report/lib/tester/matrix.js.html +487 -0
- package/coverage/lcov-report/src/builder/allocator.ts.html +1 -1
- package/coverage/lcov-report/src/builder/index.html +1 -1
- package/coverage/lcov-report/src/builder/traffic.ts.html +1 -1
- package/coverage/lcov-report/src/tester/checkIfObjectsAreEqual.ts.html +1 -1
- package/coverage/lcov-report/src/tester/index.html +25 -10
- package/coverage/lcov-report/src/tester/matrix.ts.html +640 -0
- package/coverage/lcov.info +276 -0
- package/lib/linter/testSchema.js +19 -4
- package/lib/linter/testSchema.js.map +1 -1
- package/lib/tester/matrix.d.ts +13 -0
- package/lib/tester/matrix.js +135 -0
- package/lib/tester/matrix.js.map +1 -0
- package/lib/tester/matrix.spec.d.ts +1 -0
- package/lib/tester/matrix.spec.js +42 -0
- package/lib/tester/matrix.spec.js.map +1 -0
- package/lib/tester/testFeature.js +26 -16
- package/lib/tester/testFeature.js.map +1 -1
- package/lib/tester/testSegment.js +14 -11
- package/lib/tester/testSegment.js.map +1 -1
- package/package.json +5 -5
- package/src/linter/testSchema.ts +21 -2
- package/src/tester/matrix.spec.ts +49 -0
- package/src/tester/matrix.ts +185 -0
- package/src/tester/testFeature.ts +121 -114
- package/src/tester/testSegment.ts +17 -11
|
@@ -39,6 +39,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
39
39
|
exports.testSegment = void 0;
|
|
40
40
|
var sdk_1 = require("@featurevisor/sdk");
|
|
41
41
|
var cliFormat_1 = require("./cliFormat");
|
|
42
|
+
var matrix_1 = require("./matrix");
|
|
42
43
|
function testSegment(datasource, test, patterns) {
|
|
43
44
|
return __awaiter(this, void 0, void 0, function () {
|
|
44
45
|
var hasError, segmentKey, segmentExists, parsedSegment, conditions;
|
|
@@ -61,17 +62,19 @@ function testSegment(datasource, test, patterns) {
|
|
|
61
62
|
parsedSegment = _a.sent();
|
|
62
63
|
conditions = parsedSegment.conditions;
|
|
63
64
|
test.assertions.forEach(function (assertion, aIndex) {
|
|
64
|
-
var
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
65
|
+
var assertions = (0, matrix_1.getSegmentAssertionsFromMatrix)(aIndex, assertion);
|
|
66
|
+
assertions.forEach(function (assertion) {
|
|
67
|
+
if (patterns.assertionPattern && !patterns.assertionPattern.test(assertion.description)) {
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
console.log(assertion.description);
|
|
71
|
+
var expected = assertion.expectedToMatch;
|
|
72
|
+
var actual = (0, sdk_1.allConditionsAreMatched)(conditions, assertion.context);
|
|
73
|
+
if (actual !== expected) {
|
|
74
|
+
hasError = true;
|
|
75
|
+
console.error(cliFormat_1.CLI_FORMAT_RED, " Segment failed: expected \"".concat(expected, "\", got \"").concat(actual, "\""));
|
|
76
|
+
}
|
|
77
|
+
});
|
|
75
78
|
});
|
|
76
79
|
return [2 /*return*/, hasError];
|
|
77
80
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"testSegment.js","sourceRoot":"","sources":["../../src/tester/testSegment.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AACA,yCAA4D;AAI5D,yCAA8D;
|
|
1
|
+
{"version":3,"file":"testSegment.js","sourceRoot":"","sources":["../../src/tester/testSegment.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AACA,yCAA4D;AAI5D,yCAA8D;AAC9D,mCAA0D;AAE1D,SAAsB,WAAW,CAC/B,UAAsB,EACtB,IAAiB,EACjB,QAAQ;;;;;;oBAEJ,QAAQ,GAAG,KAAK,CAAC;oBAEf,UAAU,GAAG,IAAI,CAAC,OAAO,CAAC;oBAEhC,OAAO,CAAC,GAAG,CAAC,2BAAe,EAAE,sBAAc,UAAU,QAAI,CAAC,CAAC;oBAErC,qBAAM,UAAU,CAAC,aAAa,CAAC,UAAU,CAAC,EAAA;;oBAA1D,aAAa,GAAG,SAA0C;oBAEhE,IAAI,CAAC,aAAa,EAAE;wBAClB,OAAO,CAAC,KAAK,CAAC,0BAAc,EAAE,oCAA6B,UAAU,CAAE,CAAC,CAAC;wBACzE,QAAQ,GAAG,IAAI,CAAC;wBAEhB,sBAAO,QAAQ,EAAC;qBACjB;oBAEqB,qBAAM,UAAU,CAAC,WAAW,CAAC,UAAU,CAAC,EAAA;;oBAAxD,aAAa,GAAG,SAAwC;oBACxD,UAAU,GAAG,aAAa,CAAC,UAAqC,CAAC;oBAEvE,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,UAAU,SAAS,EAAE,MAAM;wBACjD,IAAM,UAAU,GAAG,IAAA,uCAA8B,EAAC,MAAM,EAAE,SAAS,CAAC,CAAC;wBAErE,UAAU,CAAC,OAAO,CAAC,UAAU,SAAS;4BACpC,IAAI,QAAQ,CAAC,gBAAgB,IAAI,CAAC,QAAQ,CAAC,gBAAgB,CAAC,IAAI,CAAC,SAAS,CAAC,WAAW,CAAC,EAAE;gCACvF,OAAO;6BACR;4BAED,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,WAAW,CAAC,CAAC;4BAEnC,IAAM,QAAQ,GAAG,SAAS,CAAC,eAAe,CAAC;4BAC3C,IAAM,MAAM,GAAG,IAAA,6BAAuB,EAAC,UAAU,EAAE,SAAS,CAAC,OAAO,CAAC,CAAC;4BAEtE,IAAI,MAAM,KAAK,QAAQ,EAAE;gCACvB,QAAQ,GAAG,IAAI,CAAC;gCAEhB,OAAO,CAAC,KAAK,CACX,0BAAc,EACd,yCAAiC,QAAQ,uBAAW,MAAM,OAAG,CAC9D,CAAC;6BACH;wBACH,CAAC,CAAC,CAAC;oBACL,CAAC,CAAC,CAAC;oBAEH,sBAAO,QAAQ,EAAC;;;;CACjB;AAhDD,kCAgDC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@featurevisor/core",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.1",
|
|
4
4
|
"description": "Core package of Featurevisor for Node.js usage",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"types": "lib/index.d.ts",
|
|
@@ -44,9 +44,9 @@
|
|
|
44
44
|
},
|
|
45
45
|
"license": "MIT",
|
|
46
46
|
"dependencies": {
|
|
47
|
-
"@featurevisor/sdk": "^1.0
|
|
48
|
-
"@featurevisor/site": "^1.
|
|
49
|
-
"@featurevisor/types": "1.
|
|
47
|
+
"@featurevisor/sdk": "^1.2.0",
|
|
48
|
+
"@featurevisor/site": "^1.2.1",
|
|
49
|
+
"@featurevisor/types": "^1.2.0",
|
|
50
50
|
"axios": "^1.3.4",
|
|
51
51
|
"joi": "^17.8.3",
|
|
52
52
|
"js-yaml": "^4.1.0",
|
|
@@ -57,5 +57,5 @@
|
|
|
57
57
|
"@types/js-yaml": "^4.0.5",
|
|
58
58
|
"@types/tar": "^6.1.4"
|
|
59
59
|
},
|
|
60
|
-
"gitHead": "
|
|
60
|
+
"gitHead": "2cb917005c4ab458ab4901d5d45a7db55a92a3cd"
|
|
61
61
|
}
|
package/src/linter/testSchema.ts
CHANGED
|
@@ -13,6 +13,7 @@ export function getTestsJoiSchema(
|
|
|
13
13
|
.required(),
|
|
14
14
|
assertions: Joi.array().items(
|
|
15
15
|
Joi.object({
|
|
16
|
+
matrix: Joi.object().optional(), // @TODO: make it stricter
|
|
16
17
|
description: Joi.string().optional(),
|
|
17
18
|
context: Joi.object(),
|
|
18
19
|
expectedToMatch: Joi.boolean(),
|
|
@@ -26,10 +27,28 @@ export function getTestsJoiSchema(
|
|
|
26
27
|
.required(),
|
|
27
28
|
assertions: Joi.array().items(
|
|
28
29
|
Joi.object({
|
|
30
|
+
matrix: Joi.object().optional(), // @TODO: make it stricter
|
|
29
31
|
description: Joi.string().optional(),
|
|
30
|
-
at: Joi.
|
|
32
|
+
at: Joi.alternatives().try(
|
|
33
|
+
Joi.number().precision(3).min(0).max(100).required(),
|
|
34
|
+
|
|
35
|
+
// because of supporting matrix
|
|
36
|
+
Joi.string().required(),
|
|
37
|
+
),
|
|
31
38
|
environment: Joi.string()
|
|
32
|
-
.
|
|
39
|
+
.custom((value, helpers) => {
|
|
40
|
+
if (value.indexOf("${{") === 0) {
|
|
41
|
+
// allow unknown strings for matrix
|
|
42
|
+
return value;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// otherwise only known environments should be passed
|
|
46
|
+
if (projectConfig.environments.includes(value)) {
|
|
47
|
+
return value;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return helpers.error("any.invalid");
|
|
51
|
+
})
|
|
33
52
|
.required(),
|
|
34
53
|
context: Joi.object().required(),
|
|
35
54
|
expectedToBeEnabled: Joi.boolean().required(),
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { getMatrixCombinations } from "./matrix";
|
|
2
|
+
|
|
3
|
+
describe("core :: tester :: matrix", function () {
|
|
4
|
+
test("should empty array when no keys are available", function () {
|
|
5
|
+
const matrix = {};
|
|
6
|
+
const combinations = getMatrixCombinations(matrix);
|
|
7
|
+
|
|
8
|
+
expect(combinations).toEqual([]);
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
test("should get combinations from matrix with two keys", function () {
|
|
12
|
+
const matrix = {
|
|
13
|
+
a: [1, 2],
|
|
14
|
+
b: ["x", "y"],
|
|
15
|
+
};
|
|
16
|
+
const combinations = getMatrixCombinations(matrix);
|
|
17
|
+
|
|
18
|
+
expect(combinations).toEqual([
|
|
19
|
+
{ a: 1, b: "x" },
|
|
20
|
+
{ a: 1, b: "y" },
|
|
21
|
+
|
|
22
|
+
{ a: 2, b: "x" },
|
|
23
|
+
{ a: 2, b: "y" },
|
|
24
|
+
]);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
test("should get combinations from matrix with three keys", function () {
|
|
28
|
+
const matrix = {
|
|
29
|
+
a: [1, 2],
|
|
30
|
+
b: ["x", "y"],
|
|
31
|
+
c: [true, false],
|
|
32
|
+
};
|
|
33
|
+
const combinations = getMatrixCombinations(matrix);
|
|
34
|
+
|
|
35
|
+
expect(combinations).toEqual([
|
|
36
|
+
{ a: 1, b: "x", c: true },
|
|
37
|
+
{ a: 1, b: "x", c: false },
|
|
38
|
+
|
|
39
|
+
{ a: 1, b: "y", c: true },
|
|
40
|
+
{ a: 1, b: "y", c: false },
|
|
41
|
+
|
|
42
|
+
{ a: 2, b: "x", c: true },
|
|
43
|
+
{ a: 2, b: "x", c: false },
|
|
44
|
+
|
|
45
|
+
{ a: 2, b: "y", c: true },
|
|
46
|
+
{ a: 2, b: "y", c: false },
|
|
47
|
+
]);
|
|
48
|
+
});
|
|
49
|
+
});
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import { AssertionMatrix, FeatureAssertion, SegmentAssertion } from "@featurevisor/types";
|
|
2
|
+
|
|
3
|
+
function generateCombinations(
|
|
4
|
+
keys: string[],
|
|
5
|
+
matrix: AssertionMatrix,
|
|
6
|
+
idx: number,
|
|
7
|
+
prev: any,
|
|
8
|
+
combinations: any[],
|
|
9
|
+
) {
|
|
10
|
+
const key = keys[idx];
|
|
11
|
+
const values = matrix[key];
|
|
12
|
+
|
|
13
|
+
for (let i = 0; i < values.length; i++) {
|
|
14
|
+
const combination = { ...prev, [key]: values[i] };
|
|
15
|
+
|
|
16
|
+
if (idx === keys.length - 1) {
|
|
17
|
+
combinations.push(combination);
|
|
18
|
+
} else {
|
|
19
|
+
generateCombinations(keys, matrix, idx + 1, combination, combinations);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function getMatrixCombinations(matrix: AssertionMatrix) {
|
|
25
|
+
const keys = Object.keys(matrix);
|
|
26
|
+
|
|
27
|
+
if (!keys.length) {
|
|
28
|
+
return [];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const combinations: any[] = [];
|
|
32
|
+
generateCombinations(keys, matrix, 0, {}, combinations);
|
|
33
|
+
|
|
34
|
+
return combinations;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function applyCombinationToValue(value: any, combination: any) {
|
|
38
|
+
if (typeof value === "string") {
|
|
39
|
+
const variableKeysInValue = value.match(/\${{(.+?)}}/g);
|
|
40
|
+
|
|
41
|
+
// no variables found
|
|
42
|
+
if (!variableKeysInValue) {
|
|
43
|
+
return value;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// only 1 variable found, so we can insert the value directly
|
|
47
|
+
if (variableKeysInValue.length === 1 && value.startsWith("${{") && value.endsWith("}}")) {
|
|
48
|
+
const key = value.replace("${{", "").replace("}}", "").trim();
|
|
49
|
+
|
|
50
|
+
return combination[key];
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// multiple variables found, so we can replace each as a whole string
|
|
54
|
+
return value.replace(/\${{(.+?)}}/g, (_, key) => combination[key.trim()]);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return value;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Features
|
|
62
|
+
*/
|
|
63
|
+
export function applyCombinationToFeatureAssertion(
|
|
64
|
+
combination: any,
|
|
65
|
+
assertion: FeatureAssertion,
|
|
66
|
+
): FeatureAssertion {
|
|
67
|
+
const flattenedAssertion = { ...assertion };
|
|
68
|
+
|
|
69
|
+
// environment
|
|
70
|
+
flattenedAssertion.environment = applyCombinationToValue(
|
|
71
|
+
flattenedAssertion.environment,
|
|
72
|
+
combination,
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
// context
|
|
76
|
+
flattenedAssertion.context = Object.keys(flattenedAssertion.context).reduce((acc, key) => {
|
|
77
|
+
acc[key] = applyCombinationToValue(flattenedAssertion.context[key], combination);
|
|
78
|
+
|
|
79
|
+
return acc;
|
|
80
|
+
}, {});
|
|
81
|
+
|
|
82
|
+
// at
|
|
83
|
+
flattenedAssertion.at = applyCombinationToValue(flattenedAssertion.at, combination);
|
|
84
|
+
if (typeof flattenedAssertion.at === "string") {
|
|
85
|
+
flattenedAssertion.at =
|
|
86
|
+
(flattenedAssertion.at as string).indexOf(".") > -1
|
|
87
|
+
? parseFloat(flattenedAssertion.at)
|
|
88
|
+
: parseInt(flattenedAssertion.at, 10);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// description
|
|
92
|
+
if (flattenedAssertion.description) {
|
|
93
|
+
flattenedAssertion.description = applyCombinationToValue(
|
|
94
|
+
flattenedAssertion.description,
|
|
95
|
+
combination,
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return flattenedAssertion;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export function getFeatureAssertionsFromMatrix(
|
|
103
|
+
aIndex,
|
|
104
|
+
assertionWithMatrix: FeatureAssertion,
|
|
105
|
+
): FeatureAssertion[] {
|
|
106
|
+
if (!assertionWithMatrix.matrix) {
|
|
107
|
+
const assertion = { ...assertionWithMatrix };
|
|
108
|
+
assertion.description = ` Assertion #${aIndex + 1}: (${assertion.environment}) ${
|
|
109
|
+
assertion.description || `at ${assertion.at}%`
|
|
110
|
+
}`;
|
|
111
|
+
|
|
112
|
+
return [assertion];
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const assertions: FeatureAssertion[] = [];
|
|
116
|
+
const combinations = getMatrixCombinations(assertionWithMatrix.matrix);
|
|
117
|
+
|
|
118
|
+
for (let cIndex = 0; cIndex < combinations.length; cIndex++) {
|
|
119
|
+
const combination = combinations[cIndex];
|
|
120
|
+
const assertion = applyCombinationToFeatureAssertion(combination, assertionWithMatrix);
|
|
121
|
+
assertion.description = ` Assertion #${aIndex + 1}: (${assertion.environment}) ${
|
|
122
|
+
assertion.description || `at ${assertion.at}%`
|
|
123
|
+
}`;
|
|
124
|
+
|
|
125
|
+
assertions.push(assertion);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return assertions;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Segments
|
|
133
|
+
*/
|
|
134
|
+
export function applyCombinationToSegmentAssertion(
|
|
135
|
+
combination: any,
|
|
136
|
+
assertion: SegmentAssertion,
|
|
137
|
+
): SegmentAssertion {
|
|
138
|
+
const flattenedAssertion = { ...assertion };
|
|
139
|
+
|
|
140
|
+
// context
|
|
141
|
+
flattenedAssertion.context = Object.keys(flattenedAssertion.context).reduce((acc, key) => {
|
|
142
|
+
acc[key] = applyCombinationToValue(flattenedAssertion.context[key], combination);
|
|
143
|
+
|
|
144
|
+
return acc;
|
|
145
|
+
}, {});
|
|
146
|
+
|
|
147
|
+
// description
|
|
148
|
+
if (flattenedAssertion.description) {
|
|
149
|
+
flattenedAssertion.description = applyCombinationToValue(
|
|
150
|
+
flattenedAssertion.description,
|
|
151
|
+
combination,
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return flattenedAssertion;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export function getSegmentAssertionsFromMatrix(
|
|
159
|
+
aIndex,
|
|
160
|
+
assertionWithMatrix: SegmentAssertion,
|
|
161
|
+
): SegmentAssertion[] {
|
|
162
|
+
if (!assertionWithMatrix.matrix) {
|
|
163
|
+
const assertion = { ...assertionWithMatrix };
|
|
164
|
+
assertion.description = ` Assertion #${aIndex + 1}: ${
|
|
165
|
+
assertion.description || `#${aIndex + 1}`
|
|
166
|
+
}`;
|
|
167
|
+
|
|
168
|
+
return [assertion];
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const assertions: SegmentAssertion[] = [];
|
|
172
|
+
const combinations = getMatrixCombinations(assertionWithMatrix.matrix);
|
|
173
|
+
|
|
174
|
+
for (let cIndex = 0; cIndex < combinations.length; cIndex++) {
|
|
175
|
+
const combination = combinations[cIndex];
|
|
176
|
+
const assertion = applyCombinationToSegmentAssertion(combination, assertionWithMatrix);
|
|
177
|
+
assertion.description = ` Assertion #${aIndex + 1}: ${
|
|
178
|
+
assertion.description || `#${aIndex + 1}`
|
|
179
|
+
}`;
|
|
180
|
+
|
|
181
|
+
assertions.push(assertion);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
return assertions;
|
|
185
|
+
}
|
|
@@ -9,6 +9,7 @@ import { SCHEMA_VERSION } from "../config";
|
|
|
9
9
|
import { checkIfArraysAreEqual } from "./checkIfArraysAreEqual";
|
|
10
10
|
import { checkIfObjectsAreEqual } from "./checkIfObjectsAreEqual";
|
|
11
11
|
import { CLI_FORMAT_BOLD, CLI_FORMAT_RED } from "./cliFormat";
|
|
12
|
+
import { getFeatureAssertionsFromMatrix } from "./matrix";
|
|
12
13
|
|
|
13
14
|
export async function testFeature(
|
|
14
15
|
datasource: Datasource,
|
|
@@ -23,149 +24,155 @@ export async function testFeature(
|
|
|
23
24
|
console.log(CLI_FORMAT_BOLD, ` Feature "${featureKey}":`);
|
|
24
25
|
|
|
25
26
|
for (let aIndex = 0; aIndex < test.assertions.length; aIndex++) {
|
|
26
|
-
const
|
|
27
|
-
const description = ` Assertion #${aIndex + 1}: (${assertion.environment}) ${
|
|
28
|
-
assertion.description || `at ${assertion.at}%`
|
|
29
|
-
}`;
|
|
27
|
+
const assertions = getFeatureAssertionsFromMatrix(aIndex, test.assertions[aIndex]);
|
|
30
28
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
console.log(description);
|
|
36
|
-
|
|
37
|
-
const requiredChain = await datasource.getRequiredFeaturesChain(test.feature);
|
|
38
|
-
const featuresToInclude = Array.from(requiredChain);
|
|
39
|
-
|
|
40
|
-
const existingState = await datasource.readState(assertion.environment);
|
|
41
|
-
const datafileContent = await buildDatafile(
|
|
42
|
-
projectConfig,
|
|
43
|
-
datasource,
|
|
44
|
-
{
|
|
45
|
-
schemaVersion: SCHEMA_VERSION,
|
|
46
|
-
revision: "testing",
|
|
47
|
-
environment: assertion.environment,
|
|
48
|
-
features: featuresToInclude,
|
|
49
|
-
},
|
|
50
|
-
existingState,
|
|
51
|
-
);
|
|
52
|
-
|
|
53
|
-
if (options.showDatafile) {
|
|
54
|
-
console.log("");
|
|
55
|
-
console.log(JSON.stringify(datafileContent, null, 2));
|
|
56
|
-
console.log("");
|
|
57
|
-
}
|
|
29
|
+
for (let bIndex = 0; bIndex < assertions.length; bIndex++) {
|
|
30
|
+
const assertion = assertions[bIndex];
|
|
58
31
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
return assertion.at * (MAX_BUCKETED_NUMBER / 100);
|
|
63
|
-
},
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
if (options.verbose) {
|
|
67
|
-
sdk.setLogLevels(["debug", "info", "warn", "error"]);
|
|
68
|
-
}
|
|
32
|
+
if (patterns.assertionPattern && !patterns.assertionPattern.test(assertion.description)) {
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
69
35
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
const
|
|
36
|
+
console.log(assertion.description);
|
|
37
|
+
|
|
38
|
+
const requiredChain = await datasource.getRequiredFeaturesChain(test.feature);
|
|
39
|
+
const featuresToInclude = Array.from(requiredChain);
|
|
40
|
+
|
|
41
|
+
const existingState = await datasource.readState(assertion.environment);
|
|
42
|
+
const datafileContent = await buildDatafile(
|
|
43
|
+
projectConfig,
|
|
44
|
+
datasource,
|
|
45
|
+
{
|
|
46
|
+
schemaVersion: SCHEMA_VERSION,
|
|
47
|
+
revision: "testing",
|
|
48
|
+
environment: assertion.environment,
|
|
49
|
+
features: featuresToInclude,
|
|
50
|
+
},
|
|
51
|
+
existingState,
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
if (options.showDatafile) {
|
|
55
|
+
console.log("");
|
|
56
|
+
console.log(JSON.stringify(datafileContent, null, 2));
|
|
57
|
+
console.log("");
|
|
58
|
+
}
|
|
73
59
|
|
|
74
|
-
|
|
75
|
-
|
|
60
|
+
const sdk = createInstance({
|
|
61
|
+
datafile: datafileContent,
|
|
62
|
+
configureBucketValue: () => {
|
|
63
|
+
return assertion.at * (MAX_BUCKETED_NUMBER / 100);
|
|
64
|
+
},
|
|
65
|
+
});
|
|
76
66
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
` isEnabled failed: expected "${assertion.expectedToBeEnabled}", received "${isEnabled}"`,
|
|
80
|
-
);
|
|
67
|
+
if (options.verbose) {
|
|
68
|
+
sdk.setLogLevels(["debug", "info", "warn", "error"]);
|
|
81
69
|
}
|
|
82
|
-
}
|
|
83
70
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
71
|
+
// isEnabled
|
|
72
|
+
if ("expectedToBeEnabled" in assertion) {
|
|
73
|
+
const isEnabled = sdk.isEnabled(featureKey, assertion.context);
|
|
87
74
|
|
|
88
|
-
|
|
89
|
-
|
|
75
|
+
if (isEnabled !== assertion.expectedToBeEnabled) {
|
|
76
|
+
hasError = true;
|
|
90
77
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
78
|
+
console.error(
|
|
79
|
+
CLI_FORMAT_RED,
|
|
80
|
+
` isEnabled failed: expected "${assertion.expectedToBeEnabled}", received "${isEnabled}"`,
|
|
81
|
+
);
|
|
82
|
+
}
|
|
95
83
|
}
|
|
96
|
-
}
|
|
97
84
|
|
|
98
|
-
|
|
99
|
-
|
|
85
|
+
// variation
|
|
86
|
+
if ("expectedVariation" in assertion) {
|
|
87
|
+
const variation = sdk.getVariation(featureKey, assertion.context);
|
|
100
88
|
|
|
101
|
-
|
|
102
|
-
|
|
89
|
+
if (variation !== assertion.expectedVariation) {
|
|
90
|
+
hasError = true;
|
|
103
91
|
|
|
104
|
-
|
|
92
|
+
console.error(
|
|
93
|
+
CLI_FORMAT_RED,
|
|
94
|
+
` Variation failed: expected "${assertion.expectedVariation}", received "${variation}"`,
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
105
98
|
|
|
106
|
-
|
|
107
|
-
|
|
99
|
+
// variables
|
|
100
|
+
const feature = await datasource.readFeature(featureKey);
|
|
108
101
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
const expectedValue =
|
|
112
|
-
assertion.expectedVariables && assertion.expectedVariables[variableKey];
|
|
113
|
-
const actualValue = sdk.getVariable(featureKey, variableKey, assertion.context);
|
|
102
|
+
if (!feature) {
|
|
103
|
+
hasError = true;
|
|
114
104
|
|
|
115
|
-
|
|
105
|
+
console.error(CLI_FORMAT_RED, ` Feature "${featureKey}" failed: feature not found`);
|
|
116
106
|
|
|
117
|
-
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
118
109
|
|
|
119
|
-
|
|
120
|
-
|
|
110
|
+
if (typeof assertion.expectedVariables === "object") {
|
|
111
|
+
Object.keys(assertion.expectedVariables).forEach(function (variableKey) {
|
|
112
|
+
const expectedValue =
|
|
113
|
+
assertion.expectedVariables && assertion.expectedVariables[variableKey];
|
|
114
|
+
const actualValue = sdk.getVariable(featureKey, variableKey, assertion.context);
|
|
121
115
|
|
|
122
|
-
|
|
123
|
-
CLI_FORMAT_RED,
|
|
124
|
-
` Variable "${variableKey}" failed: variable schema not found in feature`,
|
|
125
|
-
);
|
|
116
|
+
let passed;
|
|
126
117
|
|
|
127
|
-
|
|
128
|
-
}
|
|
118
|
+
const variableSchema = feature.variablesSchema?.find((v) => v.key === variableKey);
|
|
129
119
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
const parsedExpectedValue =
|
|
133
|
-
typeof expectedValue === "string" ? JSON.parse(expectedValue as string) : expectedValue;
|
|
120
|
+
if (!variableSchema) {
|
|
121
|
+
hasError = true;
|
|
134
122
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
} else {
|
|
140
|
-
passed = JSON.stringify(parsedExpectedValue) === JSON.stringify(actualValue);
|
|
141
|
-
}
|
|
123
|
+
console.error(
|
|
124
|
+
CLI_FORMAT_RED,
|
|
125
|
+
` Variable "${variableKey}" failed: variable schema not found in feature`,
|
|
126
|
+
);
|
|
142
127
|
|
|
143
|
-
|
|
144
|
-
console.error(CLI_FORMAT_RED, ` Variable "${variableKey}" failed:`);
|
|
145
|
-
console.error(CLI_FORMAT_RED, ` expected: ${JSON.stringify(parsedExpectedValue)}`);
|
|
146
|
-
console.error(CLI_FORMAT_RED, ` received: ${JSON.stringify(actualValue)}`);
|
|
128
|
+
return;
|
|
147
129
|
}
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
130
|
+
|
|
131
|
+
if (variableSchema.type === "json") {
|
|
132
|
+
// JSON type
|
|
133
|
+
const parsedExpectedValue =
|
|
134
|
+
typeof expectedValue === "string"
|
|
135
|
+
? JSON.parse(expectedValue as string)
|
|
136
|
+
: expectedValue;
|
|
137
|
+
|
|
138
|
+
if (Array.isArray(actualValue)) {
|
|
139
|
+
passed = checkIfArraysAreEqual(parsedExpectedValue, actualValue);
|
|
140
|
+
} else if (typeof actualValue === "object") {
|
|
141
|
+
passed = checkIfObjectsAreEqual(parsedExpectedValue, actualValue);
|
|
142
|
+
} else {
|
|
143
|
+
passed = JSON.stringify(parsedExpectedValue) === JSON.stringify(actualValue);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (!passed) {
|
|
147
|
+
console.error(CLI_FORMAT_RED, ` Variable "${variableKey}" failed:`);
|
|
148
|
+
console.error(
|
|
149
|
+
CLI_FORMAT_RED,
|
|
150
|
+
` expected: ${JSON.stringify(parsedExpectedValue)}`,
|
|
151
|
+
);
|
|
152
|
+
console.error(CLI_FORMAT_RED, ` received: ${JSON.stringify(actualValue)}`);
|
|
153
|
+
}
|
|
154
154
|
} else {
|
|
155
|
-
|
|
155
|
+
// other types
|
|
156
|
+
if (typeof expectedValue === "object") {
|
|
157
|
+
passed = checkIfObjectsAreEqual(expectedValue, actualValue);
|
|
158
|
+
} else if (Array.isArray(expectedValue)) {
|
|
159
|
+
passed = checkIfArraysAreEqual(expectedValue, actualValue);
|
|
160
|
+
} else {
|
|
161
|
+
passed = expectedValue === actualValue;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if (!passed) {
|
|
165
|
+
console.error(CLI_FORMAT_RED, ` Variable "${variableKey}" failed:`);
|
|
166
|
+
console.error(CLI_FORMAT_RED, ` expected: ${JSON.stringify(expectedValue)}`);
|
|
167
|
+
console.error(CLI_FORMAT_RED, ` received: ${JSON.stringify(actualValue)}`);
|
|
168
|
+
}
|
|
156
169
|
}
|
|
157
170
|
|
|
158
171
|
if (!passed) {
|
|
159
|
-
|
|
160
|
-
console.error(CLI_FORMAT_RED, ` expected: ${JSON.stringify(expectedValue)}`);
|
|
161
|
-
console.error(CLI_FORMAT_RED, ` received: ${JSON.stringify(actualValue)}`);
|
|
172
|
+
hasError = true;
|
|
162
173
|
}
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
if (!passed) {
|
|
166
|
-
hasError = true;
|
|
167
|
-
}
|
|
168
|
-
});
|
|
174
|
+
});
|
|
175
|
+
}
|
|
169
176
|
}
|
|
170
177
|
}
|
|
171
178
|
|
|
@@ -4,6 +4,7 @@ import { allConditionsAreMatched } from "@featurevisor/sdk";
|
|
|
4
4
|
import { Datasource } from "../datasource";
|
|
5
5
|
|
|
6
6
|
import { CLI_FORMAT_BOLD, CLI_FORMAT_RED } from "./cliFormat";
|
|
7
|
+
import { getSegmentAssertionsFromMatrix } from "./matrix";
|
|
7
8
|
|
|
8
9
|
export async function testSegment(
|
|
9
10
|
datasource: Datasource,
|
|
@@ -29,22 +30,27 @@ export async function testSegment(
|
|
|
29
30
|
const conditions = parsedSegment.conditions as Condition | Condition[];
|
|
30
31
|
|
|
31
32
|
test.assertions.forEach(function (assertion, aIndex) {
|
|
32
|
-
const
|
|
33
|
+
const assertions = getSegmentAssertionsFromMatrix(aIndex, assertion);
|
|
33
34
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
35
|
+
assertions.forEach(function (assertion) {
|
|
36
|
+
if (patterns.assertionPattern && !patterns.assertionPattern.test(assertion.description)) {
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
37
39
|
|
|
38
|
-
|
|
40
|
+
console.log(assertion.description);
|
|
39
41
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
+
const expected = assertion.expectedToMatch;
|
|
43
|
+
const actual = allConditionsAreMatched(conditions, assertion.context);
|
|
42
44
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
+
if (actual !== expected) {
|
|
46
|
+
hasError = true;
|
|
45
47
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
+
console.error(
|
|
49
|
+
CLI_FORMAT_RED,
|
|
50
|
+
` Segment failed: expected "${expected}", got "${actual}"`,
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
});
|
|
48
54
|
});
|
|
49
55
|
|
|
50
56
|
return hasError;
|