@sun-asterisk/sungen 3.2.20-beta.5 → 3.2.21-beta.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/dist/cli/commands/delivery.d.ts.map +1 -1
- package/dist/cli/commands/delivery.js +2 -0
- package/dist/cli/commands/delivery.js.map +1 -1
- package/dist/exporters/api-testcase-formatter.d.ts +8 -0
- package/dist/exporters/api-testcase-formatter.d.ts.map +1 -1
- package/dist/exporters/api-testcase-formatter.js +44 -22
- package/dist/exporters/api-testcase-formatter.js.map +1 -1
- package/dist/exporters/matrix/build.d.ts +4 -2
- package/dist/exporters/matrix/build.d.ts.map +1 -1
- package/dist/exporters/matrix/build.js +25 -2
- package/dist/exporters/matrix/build.js.map +1 -1
- package/dist/exporters/matrix/export.d.ts +6 -0
- package/dist/exporters/matrix/export.d.ts.map +1 -1
- package/dist/exporters/matrix/export.js +9 -1
- package/dist/exporters/matrix/export.js.map +1 -1
- package/dist/exporters/matrix/gates.js +7 -1
- package/dist/exporters/matrix/gates.js.map +1 -1
- package/dist/exporters/matrix/types.d.ts +5 -0
- package/dist/exporters/matrix/types.d.ts.map +1 -1
- package/dist/exporters/matrix/types.js.map +1 -1
- package/dist/harness/script-check.d.ts.map +1 -1
- package/dist/harness/script-check.js +17 -4
- package/dist/harness/script-check.js.map +1 -1
- package/dist/harness/spec-coverage.d.ts.map +1 -1
- package/dist/harness/spec-coverage.js +7 -2
- package/dist/harness/spec-coverage.js.map +1 -1
- package/package.json +3 -3
- package/src/cli/commands/delivery.ts +2 -0
- package/src/exporters/api-testcase-formatter.ts +46 -20
- package/src/exporters/matrix/build.ts +30 -4
- package/src/exporters/matrix/export.ts +17 -1
- package/src/exporters/matrix/gates.ts +7 -1
- package/src/exporters/matrix/types.ts +5 -0
- package/src/harness/script-check.ts +18 -4
- package/src/harness/spec-coverage.ts +7 -2
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import * as fs from 'fs';
|
|
11
|
+
import { loadApiCatalog } from '../api-catalog-loader';
|
|
11
12
|
import { parseFeatureMetadata } from '../feature-parser';
|
|
12
13
|
import { parseSpecFile } from '../spec-parser';
|
|
13
14
|
import { loadTestData } from '../test-data-resolver';
|
|
@@ -23,11 +24,18 @@ import { buildMatrix, deriveVariants } from './build';
|
|
|
23
24
|
import { renderMatrixXlsx } from './render-xlsx';
|
|
24
25
|
import { renderMatrixCsv } from './render-csv';
|
|
25
26
|
import { DeliveryMap, MatrixFinding, MatrixModel } from './types';
|
|
27
|
+
import { ApiCatalogEntry } from '../types';
|
|
26
28
|
|
|
27
29
|
export interface MatrixTargetPaths {
|
|
28
30
|
cwd: string;
|
|
29
31
|
/** Feature basename — names the deliverable files. */
|
|
30
32
|
unit: string;
|
|
33
|
+
/** Unit kind — 'api' loads the endpoint catalog so cells render request blocks.
|
|
34
|
+
* Optional for back-compat: absent means non-api (no catalog). */
|
|
35
|
+
kind?: 'screen' | 'flow' | 'api';
|
|
36
|
+
/** Area/screen directory name (differs from `unit` for sub-features) — keys the
|
|
37
|
+
* unit-level apis.yaml path. Defaults to `unit`. */
|
|
38
|
+
area?: string;
|
|
31
39
|
/** Display label (flow/… prefix for flows). */
|
|
32
40
|
label: string;
|
|
33
41
|
featureFile: string;
|
|
@@ -45,6 +53,11 @@ function readSpecText(paths: MatrixTargetPaths): string {
|
|
|
45
53
|
: '';
|
|
46
54
|
}
|
|
47
55
|
|
|
56
|
+
/** apis.yaml endpoint catalog for an api unit ({} for screen/flow — loader no-ops). */
|
|
57
|
+
function catalogFor(paths: MatrixTargetPaths): Record<string, ApiCatalogEntry> {
|
|
58
|
+
return loadApiCatalog(paths.cwd, paths.kind ?? 'screen', paths.area ?? paths.unit);
|
|
59
|
+
}
|
|
60
|
+
|
|
48
61
|
export interface MatrixLoadResult {
|
|
49
62
|
model?: MatrixModel;
|
|
50
63
|
map?: DeliveryMap;
|
|
@@ -75,6 +88,7 @@ export function loadMatrixModel(paths: MatrixTargetPaths): MatrixLoadResult {
|
|
|
75
88
|
map,
|
|
76
89
|
transformerVersion: getPackageVersion(),
|
|
77
90
|
specText: readSpecText(paths),
|
|
91
|
+
apiCatalog: catalogFor(paths),
|
|
78
92
|
});
|
|
79
93
|
// Perf verdict (#569): budgets are config (qa/perf.yaml), samples are the run's
|
|
80
94
|
// per-test durations. Advisory — reported here because delivery is where runs
|
|
@@ -106,15 +120,17 @@ export function approveMatrix(paths: MatrixTargetPaths, groupIds?: string[]): {
|
|
|
106
120
|
const merged = mergeFeatureAndSpec(feature, spec);
|
|
107
121
|
const testData = fs.existsSync(paths.testDataFile) ? loadTestData(paths.testDataFile, process.env.SUNGEN_ENV ?? null) : null;
|
|
108
122
|
|
|
123
|
+
const apiCatalog = catalogFor(paths);
|
|
109
124
|
const model = buildMatrix({
|
|
110
125
|
unit: paths.unit, feature, merged, testData, results: null, map,
|
|
111
126
|
transformerVersion: getPackageVersion(),
|
|
112
127
|
specText: readSpecText(paths),
|
|
128
|
+
apiCatalog,
|
|
113
129
|
});
|
|
114
130
|
const blocking = model.findings.filter((f) => f.severity === 'error');
|
|
115
131
|
if (blocking.length > 0) return { findings: blocking, approved: [] };
|
|
116
132
|
|
|
117
|
-
const variants = deriveVariants({ feature, merged, testData, results: null });
|
|
133
|
+
const variants = deriveVariants({ feature, merged, testData, results: null, apiCatalog });
|
|
118
134
|
const byRef = new Map(variants.map((v) => [v.ref, v] as const));
|
|
119
135
|
const byVp = new Map<string, typeof variants>();
|
|
120
136
|
for (const v of variants) {
|
|
@@ -248,7 +248,13 @@ function gateDExecutability(ctx: GateContext, findings: MatrixFinding[]): void {
|
|
|
248
248
|
for (const g of map.groups) {
|
|
249
249
|
const vs = g.variants.flatMap((ref) => expandMapRef(ref, variantsByVp));
|
|
250
250
|
for (const v of vs) {
|
|
251
|
-
|
|
251
|
+
// An api-first variant (`@api:` calls, no UI steps) legitimately has no UI start
|
|
252
|
+
// state — the request blocks in its trigger (Method/API/Header/Body) carry
|
|
253
|
+
// everything a tester needs. Demanding a Background/Given here blocked 100% of
|
|
254
|
+
// api-first units (field report 3.2.19).
|
|
255
|
+
const hasPrecondition = v.precondition.length > 0
|
|
256
|
+
|| bucketTexts(feature.backgroundOrderedSteps, 'given').length > 0
|
|
257
|
+
|| v.apiCalls > 0;
|
|
252
258
|
if (!hasPrecondition) {
|
|
253
259
|
findings.push({
|
|
254
260
|
gate: 'D', severity: 'error', ref: v.ref,
|
|
@@ -98,6 +98,11 @@ export interface CoverageVariant {
|
|
|
98
98
|
/** Variables this scenario produces at RUN TIME (`remember … as {{v}}`, capability
|
|
99
99
|
* bindings) — they never exist in test-data, so Gate D must not demand them. */
|
|
100
100
|
runtimeVars: string[];
|
|
101
|
+
/** Count of `@api:<name>` invocations. An api-first variant's start state lives in
|
|
102
|
+
* its request blocks (Method/API/Header/Body), so Gate D's precondition demand is
|
|
103
|
+
* waived when this is > 0 — requiring a Background/Given blocked EVERY api-first
|
|
104
|
+
* unit (field report 3.2.19). */
|
|
105
|
+
apiCalls: number;
|
|
101
106
|
/** Condition summary: differentiating dimension shown on the sub-row. */
|
|
102
107
|
condition: string;
|
|
103
108
|
/** Resolved data pairs (test-data values / dataset-row columns), `key: value` lines. */
|
|
@@ -43,7 +43,10 @@ export function extractTestBlocks(specSrc: string): { title: string; body: strin
|
|
|
43
43
|
const lines = specSrc.split('\n');
|
|
44
44
|
const blocks: { title: string; body: string[] }[] = [];
|
|
45
45
|
for (let i = 0; i < lines.length; i++) {
|
|
46
|
-
|
|
46
|
+
// Same escape-aware title match as extractTestTitles — with `[^'"`]+` a
|
|
47
|
+
// quote-bearing title extracted truncated (or not at all), missed the
|
|
48
|
+
// automatedTitles lookup, and silently dropped out of the BYPASS scan.
|
|
49
|
+
const m = lines[i].match(/\btest(?:\.(?:only|skip|fixme))?\(\s*(['"`])((?:\\.|(?!\1).)+)\1/);
|
|
47
50
|
if (!m) continue;
|
|
48
51
|
let depth = 0, started = false;
|
|
49
52
|
const body: string[] = [];
|
|
@@ -52,7 +55,7 @@ export function extractTestBlocks(specSrc: string): { title: string; body: strin
|
|
|
52
55
|
body.push(lines[j]);
|
|
53
56
|
if (started && depth <= 0) break;
|
|
54
57
|
}
|
|
55
|
-
blocks.push({ title: m[2].trim(), body });
|
|
58
|
+
blocks.push({ title: unescapeJsLiteral(m[2]).trim(), body });
|
|
56
59
|
}
|
|
57
60
|
return blocks;
|
|
58
61
|
}
|
|
@@ -104,13 +107,24 @@ function extractTestTitles(specSrc: string): string[] {
|
|
|
104
107
|
// Count real test cases only. Web/Playwright specs use test(...) (+ .only/.skip/.fixme);
|
|
105
108
|
// mobile/Appium specs use mocha it(...) (#429 — matching only test() reported EVERY mobile
|
|
106
109
|
// scenario as MISSING). Exclude describe / hooks (not test cases).
|
|
110
|
+
// The content match is escape-aware and excludes ONLY the actual delimiter (same
|
|
111
|
+
// pattern as spec-parser.ts): the old `[^'"`]+` class truncated a title at its
|
|
112
|
+
// escaped same-quote (`lottery\'s` → EXTRA `lottery\` + the real title MISSING)
|
|
113
|
+
// and dropped a title containing an other-quote (`the "x" case`) entirely — so
|
|
114
|
+
// in-sync specs of quote-bearing scenarios failed the CI gate (field report 3.2.19).
|
|
107
115
|
const titles: string[] = [];
|
|
108
|
-
const re = /\b(?:test|it)(?:\.(?:only|skip|fixme))?\(\s*(['"`])(
|
|
116
|
+
const re = /\b(?:test|it)(?:\.(?:only|skip|fixme))?\(\s*(['"`])((?:\\.|(?!\1).)+)\1/g;
|
|
109
117
|
let m: RegExpExecArray | null;
|
|
110
|
-
while ((m = re.exec(specSrc))) titles.push(m[2].trim());
|
|
118
|
+
while ((m = re.exec(specSrc))) titles.push(unescapeJsLiteral(m[2]).trim());
|
|
111
119
|
return titles;
|
|
112
120
|
}
|
|
113
121
|
|
|
122
|
+
/** Undo the compiler's escapeQuotes escaping (\' \\ \n \r \t) so extracted titles
|
|
123
|
+
* compare against the raw scenario names from the .feature. */
|
|
124
|
+
function unescapeJsLiteral(s: string): string {
|
|
125
|
+
return s.replace(/\\(.)/g, (_m, c: string) => (c === 'n' ? '\n' : c === 'r' ? '\r' : c === 't' ? '\t' : c));
|
|
126
|
+
}
|
|
127
|
+
|
|
114
128
|
function normalize(src: string): string {
|
|
115
129
|
return src
|
|
116
130
|
.split('\n')
|
|
@@ -90,14 +90,19 @@ export function parseSpecClauses(specPath: string): { frs: FrClause[]; valRows:
|
|
|
90
90
|
}
|
|
91
91
|
|
|
92
92
|
// Validation Rules table: a row carries a Constraint, a Trigger cell, and (often) a code.
|
|
93
|
+
// A "Trigger" column alone is NOT enough to claim the table (#578): a screen-STATES table
|
|
94
|
+
// ("State ID | Trigger | URL/heading oracle | …") uses Trigger for the user action that
|
|
95
|
+
// enters the state, and reading it as validation rows invented three gate-relevant
|
|
96
|
+
// TRIGGER-UNCOVERED gaps on a real spec. The table must also name a Constraint/Rule/
|
|
97
|
+
// Validation column — the thing a validation row is ABOUT.
|
|
93
98
|
const valRows: ValRow[] = [];
|
|
94
99
|
let cTrigger = -1, cConstraint = -1, cCode = -1, inTable = false;
|
|
95
100
|
for (const raw of lines) {
|
|
96
101
|
const line = raw.trim();
|
|
97
|
-
if (line.startsWith('|') && /\btrigger\b/i.test(line) && cTrigger < 0) {
|
|
102
|
+
if (line.startsWith('|') && /\btrigger\b/i.test(line) && /\b(constraint|rule|validation)\b/i.test(line) && cTrigger < 0) {
|
|
98
103
|
const cells = line.split('|').map((c) => c.trim());
|
|
99
104
|
cTrigger = cells.findIndex((c) => /^trigger$/i.test(c));
|
|
100
|
-
cConstraint = cells.findIndex((c) => /constraint/i.test(c));
|
|
105
|
+
cConstraint = cells.findIndex((c) => /constraint|rule|validation/i.test(c));
|
|
101
106
|
cCode = cells.findIndex((c) => /code/i.test(c));
|
|
102
107
|
inTable = cTrigger >= 0;
|
|
103
108
|
continue;
|