@featurevisor/core 1.1.0 → 1.2.0

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 (36) hide show
  1. package/.eslintcache +1 -1
  2. package/CHANGELOG.md +19 -0
  3. package/coverage/clover.xml +161 -5
  4. package/coverage/coverage-final.json +2 -0
  5. package/coverage/lcov-report/index.html +32 -32
  6. package/coverage/lcov-report/lib/builder/allocator.js.html +1 -1
  7. package/coverage/lcov-report/lib/builder/index.html +1 -1
  8. package/coverage/lcov-report/lib/builder/traffic.js.html +1 -1
  9. package/coverage/lcov-report/lib/tester/checkIfObjectsAreEqual.js.html +1 -1
  10. package/coverage/lcov-report/lib/tester/index.html +25 -10
  11. package/coverage/lcov-report/lib/tester/matrix.js.html +487 -0
  12. package/coverage/lcov-report/src/builder/allocator.ts.html +1 -1
  13. package/coverage/lcov-report/src/builder/index.html +1 -1
  14. package/coverage/lcov-report/src/builder/traffic.ts.html +1 -1
  15. package/coverage/lcov-report/src/tester/checkIfObjectsAreEqual.ts.html +1 -1
  16. package/coverage/lcov-report/src/tester/index.html +25 -10
  17. package/coverage/lcov-report/src/tester/matrix.ts.html +640 -0
  18. package/coverage/lcov.info +276 -0
  19. package/lib/linter/testSchema.js +19 -4
  20. package/lib/linter/testSchema.js.map +1 -1
  21. package/lib/tester/matrix.d.ts +13 -0
  22. package/lib/tester/matrix.js +135 -0
  23. package/lib/tester/matrix.js.map +1 -0
  24. package/lib/tester/matrix.spec.d.ts +1 -0
  25. package/lib/tester/matrix.spec.js +42 -0
  26. package/lib/tester/matrix.spec.js.map +1 -0
  27. package/lib/tester/testFeature.js +26 -16
  28. package/lib/tester/testFeature.js.map +1 -1
  29. package/lib/tester/testSegment.js +14 -11
  30. package/lib/tester/testSegment.js.map +1 -1
  31. package/package.json +5 -5
  32. package/src/linter/testSchema.ts +21 -2
  33. package/src/tester/matrix.spec.ts +49 -0
  34. package/src/tester/matrix.ts +185 -0
  35. package/src/tester/testFeature.ts +121 -114
  36. 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 description = " Assertion #".concat(aIndex + 1, ": ").concat(assertion.description || "#".concat(aIndex + 1));
65
- if (patterns.assertionPattern && !patterns.assertionPattern.test(description)) {
66
- return;
67
- }
68
- console.log(description);
69
- var expected = assertion.expectedToMatch;
70
- var actual = (0, sdk_1.allConditionsAreMatched)(conditions, assertion.context);
71
- if (actual !== expected) {
72
- hasError = true;
73
- console.error(cliFormat_1.CLI_FORMAT_RED, " Segment failed: expected \"".concat(expected, "\", got \"").concat(actual, "\""));
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;AAE9D,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,WAAW,GAAG,uBAAgB,MAAM,GAAG,CAAC,eAAK,SAAS,CAAC,WAAW,IAAI,WAAI,MAAM,GAAG,CAAC,CAAE,CAAE,CAAC;wBAE/F,IAAI,QAAQ,CAAC,gBAAgB,IAAI,CAAC,QAAQ,CAAC,gBAAgB,CAAC,IAAI,CAAC,WAAW,CAAC,EAAE;4BAC7E,OAAO;yBACR;wBAED,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;wBAEzB,IAAM,QAAQ,GAAG,SAAS,CAAC,eAAe,CAAC;wBAC3C,IAAM,MAAM,GAAG,IAAA,6BAAuB,EAAC,UAAU,EAAE,SAAS,CAAC,OAAO,CAAC,CAAC;wBAEtE,IAAI,MAAM,KAAK,QAAQ,EAAE;4BACvB,QAAQ,GAAG,IAAI,CAAC;4BAEhB,OAAO,CAAC,KAAK,CAAC,0BAAc,EAAE,yCAAiC,QAAQ,uBAAW,MAAM,OAAG,CAAC,CAAC;yBAC9F;oBACH,CAAC,CAAC,CAAC;oBAEH,sBAAO,QAAQ,EAAC;;;;CACjB;AA3CD,kCA2CC"}
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.1.0",
3
+ "version": "1.2.0",
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.2",
48
- "@featurevisor/site": "1.0.0",
49
- "@featurevisor/types": "1.0.0",
47
+ "@featurevisor/sdk": "^1.2.0",
48
+ "@featurevisor/site": "^1.2.0",
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": "08807fe55241e7ead179ecdbb135fa5ff57461ab"
60
+ "gitHead": "e733c84d2fcdc6205df5ae75e5bbf73753141646"
61
61
  }
@@ -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.number().precision(3).min(0).max(100).required(),
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
- .valid(...projectConfig.environments)
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 assertion = test.assertions[aIndex];
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
- if (patterns.assertionPattern && !patterns.assertionPattern.test(description)) {
32
- continue;
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
- const sdk = createInstance({
60
- datafile: datafileContent,
61
- configureBucketValue: () => {
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
- // isEnabled
71
- if ("expectedToBeEnabled" in assertion) {
72
- const isEnabled = sdk.isEnabled(featureKey, assertion.context);
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
- if (isEnabled !== assertion.expectedToBeEnabled) {
75
- hasError = true;
60
+ const sdk = createInstance({
61
+ datafile: datafileContent,
62
+ configureBucketValue: () => {
63
+ return assertion.at * (MAX_BUCKETED_NUMBER / 100);
64
+ },
65
+ });
76
66
 
77
- console.error(
78
- CLI_FORMAT_RED,
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
- // variation
85
- if ("expectedVariation" in assertion) {
86
- const variation = sdk.getVariation(featureKey, assertion.context);
71
+ // isEnabled
72
+ if ("expectedToBeEnabled" in assertion) {
73
+ const isEnabled = sdk.isEnabled(featureKey, assertion.context);
87
74
 
88
- if (variation !== assertion.expectedVariation) {
89
- hasError = true;
75
+ if (isEnabled !== assertion.expectedToBeEnabled) {
76
+ hasError = true;
90
77
 
91
- console.error(
92
- CLI_FORMAT_RED,
93
- ` Variation failed: expected "${assertion.expectedVariation}", received "${variation}"`,
94
- );
78
+ console.error(
79
+ CLI_FORMAT_RED,
80
+ ` isEnabled failed: expected "${assertion.expectedToBeEnabled}", received "${isEnabled}"`,
81
+ );
82
+ }
95
83
  }
96
- }
97
84
 
98
- // variables
99
- const feature = await datasource.readFeature(featureKey);
85
+ // variation
86
+ if ("expectedVariation" in assertion) {
87
+ const variation = sdk.getVariation(featureKey, assertion.context);
100
88
 
101
- if (!feature) {
102
- hasError = true;
89
+ if (variation !== assertion.expectedVariation) {
90
+ hasError = true;
103
91
 
104
- console.error(CLI_FORMAT_RED, ` Feature "${featureKey}" failed: feature not found`);
92
+ console.error(
93
+ CLI_FORMAT_RED,
94
+ ` Variation failed: expected "${assertion.expectedVariation}", received "${variation}"`,
95
+ );
96
+ }
97
+ }
105
98
 
106
- continue;
107
- }
99
+ // variables
100
+ const feature = await datasource.readFeature(featureKey);
108
101
 
109
- if (typeof assertion.expectedVariables === "object") {
110
- Object.keys(assertion.expectedVariables).forEach(function (variableKey) {
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
- let passed;
105
+ console.error(CLI_FORMAT_RED, ` Feature "${featureKey}" failed: feature not found`);
116
106
 
117
- const variableSchema = feature.variablesSchema?.find((v) => v.key === variableKey);
107
+ continue;
108
+ }
118
109
 
119
- if (!variableSchema) {
120
- hasError = true;
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
- console.error(
123
- CLI_FORMAT_RED,
124
- ` Variable "${variableKey}" failed: variable schema not found in feature`,
125
- );
116
+ let passed;
126
117
 
127
- return;
128
- }
118
+ const variableSchema = feature.variablesSchema?.find((v) => v.key === variableKey);
129
119
 
130
- if (variableSchema.type === "json") {
131
- // JSON type
132
- const parsedExpectedValue =
133
- typeof expectedValue === "string" ? JSON.parse(expectedValue as string) : expectedValue;
120
+ if (!variableSchema) {
121
+ hasError = true;
134
122
 
135
- if (Array.isArray(actualValue)) {
136
- passed = checkIfArraysAreEqual(parsedExpectedValue, actualValue);
137
- } else if (typeof actualValue === "object") {
138
- passed = checkIfObjectsAreEqual(parsedExpectedValue, actualValue);
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
- if (!passed) {
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
- } else {
149
- // other types
150
- if (typeof expectedValue === "object") {
151
- passed = checkIfObjectsAreEqual(expectedValue, actualValue);
152
- } else if (Array.isArray(expectedValue)) {
153
- passed = checkIfArraysAreEqual(expectedValue, actualValue);
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
- passed = expectedValue === actualValue;
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
- console.error(CLI_FORMAT_RED, ` Variable "${variableKey}" failed:`);
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 description = ` Assertion #${aIndex + 1}: ${assertion.description || `#${aIndex + 1}`}`;
33
+ const assertions = getSegmentAssertionsFromMatrix(aIndex, assertion);
33
34
 
34
- if (patterns.assertionPattern && !patterns.assertionPattern.test(description)) {
35
- return;
36
- }
35
+ assertions.forEach(function (assertion) {
36
+ if (patterns.assertionPattern && !patterns.assertionPattern.test(assertion.description)) {
37
+ return;
38
+ }
37
39
 
38
- console.log(description);
40
+ console.log(assertion.description);
39
41
 
40
- const expected = assertion.expectedToMatch;
41
- const actual = allConditionsAreMatched(conditions, assertion.context);
42
+ const expected = assertion.expectedToMatch;
43
+ const actual = allConditionsAreMatched(conditions, assertion.context);
42
44
 
43
- if (actual !== expected) {
44
- hasError = true;
45
+ if (actual !== expected) {
46
+ hasError = true;
45
47
 
46
- console.error(CLI_FORMAT_RED, ` Segment failed: expected "${expected}", got "${actual}"`);
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;