@vijaypjavvadi/bdd2pw 3.10.0 → 4.0.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.
- package/CHANGELOG.md +253 -0
- package/README.md +19 -12
- package/dist/cli.js +39 -7
- package/dist/cli.js.map +1 -1
- package/dist/data/dataLoader.d.ts +78 -0
- package/dist/data/dataLoader.js +339 -0
- package/dist/data/dataLoader.js.map +1 -0
- package/dist/data/examplesInjector.d.ts +64 -0
- package/dist/data/examplesInjector.js +149 -0
- package/dist/data/examplesInjector.js.map +1 -0
- package/dist/data/synthGenerator.d.ts +71 -0
- package/dist/data/synthGenerator.js +265 -0
- package/dist/data/synthGenerator.js.map +1 -0
- package/dist/index.js +50 -0
- package/dist/index.js.map +1 -1
- package/dist/llm/anthropicClient.d.ts +11 -0
- package/dist/llm/anthropicClient.js +89 -0
- package/dist/llm/anthropicClient.js.map +1 -1
- package/dist/llm/geminiClient.d.ts +92 -0
- package/dist/llm/geminiClient.js +559 -0
- package/dist/llm/geminiClient.js.map +1 -0
- package/dist/llm/index.d.ts +2 -0
- package/dist/llm/index.js +13 -2
- package/dist/llm/index.js.map +1 -1
- package/dist/llm/openaiClient.d.ts +83 -0
- package/dist/llm/openaiClient.js +529 -0
- package/dist/llm/openaiClient.js.map +1 -0
- package/dist/llm/telemetry.js +14 -0
- package/dist/llm/telemetry.js.map +1 -1
- package/dist/llm/types.d.ts +45 -2
- package/dist/types.d.ts +41 -1
- package/package.json +5 -1
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* v4.0.0 — DataLoader.
|
|
4
|
+
*
|
|
5
|
+
* Reads CSV / JSON / XLSX into a normalized `DataRow[]` for injection
|
|
6
|
+
* into Scenario Outline Examples tables.
|
|
7
|
+
*
|
|
8
|
+
* Normalization contract:
|
|
9
|
+
* - All values are coerced to strings (Gherkin Examples tables are
|
|
10
|
+
* always string-typed; the test code does the parsing).
|
|
11
|
+
* - Headers are trimmed; empty headers are rejected up-front so the
|
|
12
|
+
* placeholder-to-column matching downstream is deterministic.
|
|
13
|
+
* - Empty rows (all-blank cells) are dropped silently — they're
|
|
14
|
+
* a frequent artifact of hand-edited CSVs.
|
|
15
|
+
*
|
|
16
|
+
* Format detection is by file extension. .xlsx loading is lazy via
|
|
17
|
+
* the `xlsx` optional dep — if missing, the user gets a clear
|
|
18
|
+
* `npm install xlsx` error instead of a module-resolution crash.
|
|
19
|
+
*
|
|
20
|
+
* Error model: every loader function throws `DataLoaderError` with
|
|
21
|
+
* a human-readable message. The orchestrator in `scaffold()` catches
|
|
22
|
+
* this and surfaces it as a `ReviewItem` so the failure shows up in
|
|
23
|
+
* BDD_REVIEW.md alongside other diagnostics — not just on stderr.
|
|
24
|
+
*/
|
|
25
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
26
|
+
if (k2 === undefined) k2 = k;
|
|
27
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
28
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
29
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
30
|
+
}
|
|
31
|
+
Object.defineProperty(o, k2, desc);
|
|
32
|
+
}) : (function(o, m, k, k2) {
|
|
33
|
+
if (k2 === undefined) k2 = k;
|
|
34
|
+
o[k2] = m[k];
|
|
35
|
+
}));
|
|
36
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
37
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
38
|
+
}) : function(o, v) {
|
|
39
|
+
o["default"] = v;
|
|
40
|
+
});
|
|
41
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
42
|
+
var ownKeys = function(o) {
|
|
43
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
44
|
+
var ar = [];
|
|
45
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
46
|
+
return ar;
|
|
47
|
+
};
|
|
48
|
+
return ownKeys(o);
|
|
49
|
+
};
|
|
50
|
+
return function (mod) {
|
|
51
|
+
if (mod && mod.__esModule) return mod;
|
|
52
|
+
var result = {};
|
|
53
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
54
|
+
__setModuleDefault(result, mod);
|
|
55
|
+
return result;
|
|
56
|
+
};
|
|
57
|
+
})();
|
|
58
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
59
|
+
exports.DataLoaderError = void 0;
|
|
60
|
+
exports.loadDataFile = loadDataFile;
|
|
61
|
+
exports.parseCsv = parseCsv;
|
|
62
|
+
exports.parseJson = parseJson;
|
|
63
|
+
exports.parseXlsx = parseXlsx;
|
|
64
|
+
exports.validateColumnsForPlaceholders = validateColumnsForPlaceholders;
|
|
65
|
+
const fs = __importStar(require("fs"));
|
|
66
|
+
const path = __importStar(require("path"));
|
|
67
|
+
class DataLoaderError extends Error {
|
|
68
|
+
constructor(message, file) {
|
|
69
|
+
super(message);
|
|
70
|
+
this.file = file;
|
|
71
|
+
this.name = "DataLoaderError";
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
exports.DataLoaderError = DataLoaderError;
|
|
75
|
+
/**
|
|
76
|
+
* Entry point. Routes by extension to the right parser.
|
|
77
|
+
*
|
|
78
|
+
* @param filePath Absolute or relative path to a .csv / .json / .xlsx file.
|
|
79
|
+
* @returns Array of rows, each a string-keyed dict.
|
|
80
|
+
* @throws DataLoaderError on any read / parse / shape error.
|
|
81
|
+
*/
|
|
82
|
+
function loadDataFile(filePath) {
|
|
83
|
+
const abs = path.resolve(filePath);
|
|
84
|
+
if (!fs.existsSync(abs)) {
|
|
85
|
+
throw new DataLoaderError(`Data file not found: ${filePath}`, filePath);
|
|
86
|
+
}
|
|
87
|
+
const ext = path.extname(abs).toLowerCase();
|
|
88
|
+
switch (ext) {
|
|
89
|
+
case ".csv":
|
|
90
|
+
return parseCsv(fs.readFileSync(abs, "utf-8"), filePath);
|
|
91
|
+
case ".json":
|
|
92
|
+
return parseJson(fs.readFileSync(abs, "utf-8"), filePath);
|
|
93
|
+
case ".xlsx":
|
|
94
|
+
case ".xls":
|
|
95
|
+
return parseXlsx(abs, filePath);
|
|
96
|
+
default:
|
|
97
|
+
throw new DataLoaderError(`Unsupported data file extension "${ext}". Supported: .csv, .json, .xlsx`, filePath);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* CSV parser — RFC 4180 compliant for the practical subset:
|
|
102
|
+
* - Quoted fields with embedded commas
|
|
103
|
+
* - Quoted fields with embedded newlines
|
|
104
|
+
* - Escaped quotes (`""` inside a quoted field)
|
|
105
|
+
* - Mixed CRLF / LF line endings
|
|
106
|
+
*
|
|
107
|
+
* Does NOT delegate to papaparse — keeping this dependency-free since
|
|
108
|
+
* the format is small enough to handle inline and we want bdd2pw to
|
|
109
|
+
* scaffold without an `npm install` step on the user's machine.
|
|
110
|
+
*/
|
|
111
|
+
function parseCsv(text, fileLabel) {
|
|
112
|
+
// Strip UTF-8 BOM if present. PowerShell's `Out-File -Encoding utf8`
|
|
113
|
+
// adds one by default, as do several Windows editors. Without this
|
|
114
|
+
// strip, the first header gets a U+FEFF prefix and downstream column
|
|
115
|
+
// matching silently fails.
|
|
116
|
+
if (text.charCodeAt(0) === 0xfeff)
|
|
117
|
+
text = text.slice(1);
|
|
118
|
+
// Normalize line endings, drop a trailing newline if present.
|
|
119
|
+
const normalized = text.replace(/\r\n?/g, "\n").replace(/\n$/, "");
|
|
120
|
+
if (normalized.length === 0) {
|
|
121
|
+
throw new DataLoaderError("CSV file is empty", fileLabel);
|
|
122
|
+
}
|
|
123
|
+
const lines = [];
|
|
124
|
+
let current = [];
|
|
125
|
+
let field = "";
|
|
126
|
+
let inQuotes = false;
|
|
127
|
+
let i = 0;
|
|
128
|
+
while (i < normalized.length) {
|
|
129
|
+
const c = normalized[i];
|
|
130
|
+
if (inQuotes) {
|
|
131
|
+
if (c === '"') {
|
|
132
|
+
if (normalized[i + 1] === '"') {
|
|
133
|
+
field += '"';
|
|
134
|
+
i += 2;
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
inQuotes = false;
|
|
138
|
+
i += 1;
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
field += c;
|
|
142
|
+
i += 1;
|
|
143
|
+
}
|
|
144
|
+
else {
|
|
145
|
+
if (c === '"') {
|
|
146
|
+
inQuotes = true;
|
|
147
|
+
i += 1;
|
|
148
|
+
}
|
|
149
|
+
else if (c === ",") {
|
|
150
|
+
current.push(field);
|
|
151
|
+
field = "";
|
|
152
|
+
i += 1;
|
|
153
|
+
}
|
|
154
|
+
else if (c === "\n") {
|
|
155
|
+
current.push(field);
|
|
156
|
+
field = "";
|
|
157
|
+
lines.push(current);
|
|
158
|
+
current = [];
|
|
159
|
+
i += 1;
|
|
160
|
+
}
|
|
161
|
+
else {
|
|
162
|
+
field += c;
|
|
163
|
+
i += 1;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
// Flush last field + last row.
|
|
168
|
+
current.push(field);
|
|
169
|
+
lines.push(current);
|
|
170
|
+
if (lines.length < 2) {
|
|
171
|
+
throw new DataLoaderError("CSV must have at least a header row and one data row", fileLabel);
|
|
172
|
+
}
|
|
173
|
+
const headers = lines[0].map((h) => h.trim());
|
|
174
|
+
validateHeaders(headers, fileLabel);
|
|
175
|
+
const rows = [];
|
|
176
|
+
for (let r = 1; r < lines.length; r++) {
|
|
177
|
+
const cells = lines[r];
|
|
178
|
+
if (isBlankRow(cells))
|
|
179
|
+
continue;
|
|
180
|
+
const row = {};
|
|
181
|
+
for (let c = 0; c < headers.length; c++) {
|
|
182
|
+
row[headers[c]] = (cells[c] ?? "").trim();
|
|
183
|
+
}
|
|
184
|
+
rows.push(row);
|
|
185
|
+
}
|
|
186
|
+
if (rows.length === 0) {
|
|
187
|
+
throw new DataLoaderError("CSV had a header row but no non-empty data rows", fileLabel);
|
|
188
|
+
}
|
|
189
|
+
return rows;
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* JSON parser — accepts either:
|
|
193
|
+
* 1. Array of objects: `[{a:1,b:2}, {a:3,b:4}]` — most natural shape.
|
|
194
|
+
* 2. Object with `rows`: `{rows: [{...}, {...}]}` — useful for files
|
|
195
|
+
* that also carry metadata (generator seed, source URL, etc).
|
|
196
|
+
*
|
|
197
|
+
* Numbers and booleans are stringified for Gherkin Examples compatibility.
|
|
198
|
+
* Null values become empty strings.
|
|
199
|
+
*/
|
|
200
|
+
function parseJson(text, fileLabel) {
|
|
201
|
+
// Strip UTF-8 BOM (see parseCsv for rationale).
|
|
202
|
+
if (text.charCodeAt(0) === 0xfeff)
|
|
203
|
+
text = text.slice(1);
|
|
204
|
+
let parsed;
|
|
205
|
+
try {
|
|
206
|
+
parsed = JSON.parse(text);
|
|
207
|
+
}
|
|
208
|
+
catch (err) {
|
|
209
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
210
|
+
throw new DataLoaderError(`JSON parse error: ${msg}`, fileLabel);
|
|
211
|
+
}
|
|
212
|
+
let raw;
|
|
213
|
+
if (Array.isArray(parsed)) {
|
|
214
|
+
raw = parsed;
|
|
215
|
+
}
|
|
216
|
+
else if (parsed &&
|
|
217
|
+
typeof parsed === "object" &&
|
|
218
|
+
Array.isArray(parsed.rows)) {
|
|
219
|
+
raw = parsed.rows;
|
|
220
|
+
}
|
|
221
|
+
else {
|
|
222
|
+
throw new DataLoaderError('JSON must be an array of objects OR an object with a "rows" array', fileLabel);
|
|
223
|
+
}
|
|
224
|
+
if (raw.length === 0) {
|
|
225
|
+
throw new DataLoaderError("JSON contained zero rows", fileLabel);
|
|
226
|
+
}
|
|
227
|
+
const rows = [];
|
|
228
|
+
let headersSeen = null;
|
|
229
|
+
for (const item of raw) {
|
|
230
|
+
if (!item || typeof item !== "object" || Array.isArray(item)) {
|
|
231
|
+
throw new DataLoaderError(`Every JSON row must be a plain object; got: ${JSON.stringify(item)?.slice(0, 80)}`, fileLabel);
|
|
232
|
+
}
|
|
233
|
+
const obj = item;
|
|
234
|
+
const row = {};
|
|
235
|
+
for (const [k, v] of Object.entries(obj)) {
|
|
236
|
+
row[k] = v === null || v === undefined ? "" : String(v);
|
|
237
|
+
}
|
|
238
|
+
if (!headersSeen) {
|
|
239
|
+
headersSeen = new Set(Object.keys(row));
|
|
240
|
+
validateHeaders(Array.from(headersSeen), fileLabel);
|
|
241
|
+
}
|
|
242
|
+
rows.push(row);
|
|
243
|
+
}
|
|
244
|
+
return rows;
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* XLSX parser — uses the optional `xlsx` dep. Reads the FIRST sheet
|
|
248
|
+
* only; users with multi-sheet workbooks should export the sheet they
|
|
249
|
+
* want to CSV.
|
|
250
|
+
*
|
|
251
|
+
* Lazy-loaded so users who never use XLSX don't need the dep installed.
|
|
252
|
+
*/
|
|
253
|
+
function parseXlsx(absPath, fileLabel) {
|
|
254
|
+
let xlsx;
|
|
255
|
+
try {
|
|
256
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
257
|
+
xlsx = require("xlsx");
|
|
258
|
+
}
|
|
259
|
+
catch {
|
|
260
|
+
throw new DataLoaderError("xlsx package not installed. Run: npm install xlsx", fileLabel);
|
|
261
|
+
}
|
|
262
|
+
const wb = xlsx.readFile(absPath);
|
|
263
|
+
const firstSheetName = wb.SheetNames[0];
|
|
264
|
+
if (!firstSheetName) {
|
|
265
|
+
throw new DataLoaderError("XLSX has no sheets", fileLabel);
|
|
266
|
+
}
|
|
267
|
+
const sheet = wb.Sheets[firstSheetName];
|
|
268
|
+
// sheet_to_json with header:1 returns array of arrays — we then
|
|
269
|
+
// promote first row to headers ourselves so the validation flow
|
|
270
|
+
// matches the CSV path exactly.
|
|
271
|
+
const aoa = xlsx.utils.sheet_to_json(sheet, {
|
|
272
|
+
header: 1,
|
|
273
|
+
defval: "",
|
|
274
|
+
blankrows: false,
|
|
275
|
+
raw: false,
|
|
276
|
+
});
|
|
277
|
+
if (aoa.length < 2) {
|
|
278
|
+
throw new DataLoaderError("XLSX must have at least a header row and one data row", fileLabel);
|
|
279
|
+
}
|
|
280
|
+
const headers = aoa[0].map((h) => String(h ?? "").trim());
|
|
281
|
+
validateHeaders(headers, fileLabel);
|
|
282
|
+
const rows = [];
|
|
283
|
+
for (let r = 1; r < aoa.length; r++) {
|
|
284
|
+
const cells = aoa[r];
|
|
285
|
+
if (isBlankRow(cells.map((c) => String(c ?? ""))))
|
|
286
|
+
continue;
|
|
287
|
+
const row = {};
|
|
288
|
+
for (let c = 0; c < headers.length; c++) {
|
|
289
|
+
row[headers[c]] = String(cells[c] ?? "").trim();
|
|
290
|
+
}
|
|
291
|
+
rows.push(row);
|
|
292
|
+
}
|
|
293
|
+
if (rows.length === 0) {
|
|
294
|
+
throw new DataLoaderError("XLSX had a header row but no non-empty data rows", fileLabel);
|
|
295
|
+
}
|
|
296
|
+
return rows;
|
|
297
|
+
}
|
|
298
|
+
// --- helpers ----------------------------------------------------------------
|
|
299
|
+
function validateHeaders(headers, fileLabel) {
|
|
300
|
+
if (headers.length === 0) {
|
|
301
|
+
throw new DataLoaderError("No headers found", fileLabel);
|
|
302
|
+
}
|
|
303
|
+
const blanks = headers
|
|
304
|
+
.map((h, i) => (h.length === 0 ? i : -1))
|
|
305
|
+
.filter((i) => i >= 0);
|
|
306
|
+
if (blanks.length > 0) {
|
|
307
|
+
throw new DataLoaderError(`Header row has blank column(s) at index: ${blanks.join(", ")}`, fileLabel);
|
|
308
|
+
}
|
|
309
|
+
const seen = new Set();
|
|
310
|
+
const dupes = [];
|
|
311
|
+
for (const h of headers) {
|
|
312
|
+
if (seen.has(h))
|
|
313
|
+
dupes.push(h);
|
|
314
|
+
seen.add(h);
|
|
315
|
+
}
|
|
316
|
+
if (dupes.length > 0) {
|
|
317
|
+
throw new DataLoaderError(`Duplicate header(s): ${[...new Set(dupes)].join(", ")}`, fileLabel);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
function isBlankRow(cells) {
|
|
321
|
+
return cells.every((c) => (c ?? "").toString().trim() === "");
|
|
322
|
+
}
|
|
323
|
+
/**
|
|
324
|
+
* Validates that the loaded data rows have every column the Scenario
|
|
325
|
+
* Outline placeholders reference. Returns the list of missing column
|
|
326
|
+
* names; empty array means OK.
|
|
327
|
+
*
|
|
328
|
+
* Called by the scaffold orchestrator after parsing the .feature file.
|
|
329
|
+
* If anything is missing, the orchestrator surfaces it as a
|
|
330
|
+
* `ReviewItem` and skips Examples injection for that scenario rather
|
|
331
|
+
* than producing a broken spec.
|
|
332
|
+
*/
|
|
333
|
+
function validateColumnsForPlaceholders(rows, placeholders) {
|
|
334
|
+
if (rows.length === 0)
|
|
335
|
+
return placeholders;
|
|
336
|
+
const presentColumns = new Set(Object.keys(rows[0]));
|
|
337
|
+
return placeholders.filter((p) => !presentColumns.has(p));
|
|
338
|
+
}
|
|
339
|
+
//# sourceMappingURL=dataLoader.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dataLoader.js","sourceRoot":"","sources":["../../src/data/dataLoader.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* v4.0.0 — ExamplesInjector.
|
|
3
|
+
*
|
|
4
|
+
* Mutates a parsed `FeatureIR` in place by replacing the `examples`
|
|
5
|
+
* array on every Scenario Outline with externally-sourced rows
|
|
6
|
+
* (from a CSV/JSON/XLSX file via DataLoader, or from a synthetic
|
|
7
|
+
* generator via SynthGenerator).
|
|
8
|
+
*
|
|
9
|
+
* Design decisions (locked in during v4.0.0 planning):
|
|
10
|
+
*
|
|
11
|
+
* 1. External rows OVERRIDE inline Examples entirely. Inline Examples
|
|
12
|
+
* in the .feature file become the no-data fallback. When --data is
|
|
13
|
+
* passed, every Scenario Outline gets its examples swapped.
|
|
14
|
+
*
|
|
15
|
+
* 2. Scenarios without Outline syntax (no <placeholder>) are skipped.
|
|
16
|
+
* A vanilla Scenario doesn't care about data injection.
|
|
17
|
+
*
|
|
18
|
+
* 3. If a scenario's placeholders reference columns NOT present in
|
|
19
|
+
* the data, that scenario is LEFT ALONE (keeps inline Examples)
|
|
20
|
+
* and the gap is logged as a `ReviewItem` so BDD_REVIEW.md surfaces
|
|
21
|
+
* the mismatch. Partial column overrides are not supported on
|
|
22
|
+
* purpose — they produce confusing test output.
|
|
23
|
+
*
|
|
24
|
+
* 4. On synth-generator failure (LLM down, schema invalid, Faker
|
|
25
|
+
* missing), we leave inline Examples in place and surface a
|
|
26
|
+
* `ReviewItem` warning. Same graceful-degradation philosophy as
|
|
27
|
+
* the LLM-fallback failure mode.
|
|
28
|
+
*/
|
|
29
|
+
import type { FeatureIR, ReviewItem, ScenarioIR } from "../types";
|
|
30
|
+
import type { DataRow } from "./dataLoader";
|
|
31
|
+
/**
|
|
32
|
+
* Discover the placeholder names referenced anywhere in a scenario's
|
|
33
|
+
* step text. Gherkin syntax: `<name>` substitutes a value from the
|
|
34
|
+
* row keyed by `name`.
|
|
35
|
+
*
|
|
36
|
+
* Returns a deduped, sorted list. Empty list = vanilla Scenario, not
|
|
37
|
+
* an Outline.
|
|
38
|
+
*/
|
|
39
|
+
export declare function extractPlaceholders(scenario: ScenarioIR): string[];
|
|
40
|
+
/**
|
|
41
|
+
* Inject `rows` into every Scenario Outline of `feature`. Returns
|
|
42
|
+
* a report describing what was swapped and what was skipped (so the
|
|
43
|
+
* caller can emit `ReviewItem`s and pino logs).
|
|
44
|
+
*
|
|
45
|
+
* Mutates `feature` in place — the caller passes the already-parsed
|
|
46
|
+
* IR and gets back a mutation summary.
|
|
47
|
+
*/
|
|
48
|
+
export interface InjectionReport {
|
|
49
|
+
totalScenarios: number;
|
|
50
|
+
swappedScenarios: number;
|
|
51
|
+
skippedScenarios: Array<{
|
|
52
|
+
name: string;
|
|
53
|
+
reason: string;
|
|
54
|
+
}>;
|
|
55
|
+
rowsPerSwap: number;
|
|
56
|
+
}
|
|
57
|
+
export declare function injectExamples(feature: FeatureIR, rows: DataRow[]): InjectionReport;
|
|
58
|
+
/**
|
|
59
|
+
* Convert an `InjectionReport` into `ReviewItem`s ready to push onto
|
|
60
|
+
* the scaffold's review queue. One info line per swap, one warning
|
|
61
|
+
* per skip whose reason indicates a real misconfiguration (missing
|
|
62
|
+
* columns) — skips for "vanilla Scenario" or "zero rows" are info-only.
|
|
63
|
+
*/
|
|
64
|
+
export declare function reportToReviewItems(report: InjectionReport, source: string): ReviewItem[];
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* v4.0.0 — ExamplesInjector.
|
|
4
|
+
*
|
|
5
|
+
* Mutates a parsed `FeatureIR` in place by replacing the `examples`
|
|
6
|
+
* array on every Scenario Outline with externally-sourced rows
|
|
7
|
+
* (from a CSV/JSON/XLSX file via DataLoader, or from a synthetic
|
|
8
|
+
* generator via SynthGenerator).
|
|
9
|
+
*
|
|
10
|
+
* Design decisions (locked in during v4.0.0 planning):
|
|
11
|
+
*
|
|
12
|
+
* 1. External rows OVERRIDE inline Examples entirely. Inline Examples
|
|
13
|
+
* in the .feature file become the no-data fallback. When --data is
|
|
14
|
+
* passed, every Scenario Outline gets its examples swapped.
|
|
15
|
+
*
|
|
16
|
+
* 2. Scenarios without Outline syntax (no <placeholder>) are skipped.
|
|
17
|
+
* A vanilla Scenario doesn't care about data injection.
|
|
18
|
+
*
|
|
19
|
+
* 3. If a scenario's placeholders reference columns NOT present in
|
|
20
|
+
* the data, that scenario is LEFT ALONE (keeps inline Examples)
|
|
21
|
+
* and the gap is logged as a `ReviewItem` so BDD_REVIEW.md surfaces
|
|
22
|
+
* the mismatch. Partial column overrides are not supported on
|
|
23
|
+
* purpose — they produce confusing test output.
|
|
24
|
+
*
|
|
25
|
+
* 4. On synth-generator failure (LLM down, schema invalid, Faker
|
|
26
|
+
* missing), we leave inline Examples in place and surface a
|
|
27
|
+
* `ReviewItem` warning. Same graceful-degradation philosophy as
|
|
28
|
+
* the LLM-fallback failure mode.
|
|
29
|
+
*/
|
|
30
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
31
|
+
exports.extractPlaceholders = extractPlaceholders;
|
|
32
|
+
exports.injectExamples = injectExamples;
|
|
33
|
+
exports.reportToReviewItems = reportToReviewItems;
|
|
34
|
+
/**
|
|
35
|
+
* Discover the placeholder names referenced anywhere in a scenario's
|
|
36
|
+
* step text. Gherkin syntax: `<name>` substitutes a value from the
|
|
37
|
+
* row keyed by `name`.
|
|
38
|
+
*
|
|
39
|
+
* Returns a deduped, sorted list. Empty list = vanilla Scenario, not
|
|
40
|
+
* an Outline.
|
|
41
|
+
*/
|
|
42
|
+
function extractPlaceholders(scenario) {
|
|
43
|
+
const found = new Set();
|
|
44
|
+
const re = /<([A-Za-z_][\w-]*)>/g;
|
|
45
|
+
for (const step of scenario.steps) {
|
|
46
|
+
let m;
|
|
47
|
+
while ((m = re.exec(step.text)) !== null)
|
|
48
|
+
found.add(m[1]);
|
|
49
|
+
// Also scan doc-string and data-table arguments.
|
|
50
|
+
if (typeof step.argument === "string") {
|
|
51
|
+
while ((m = re.exec(step.argument)) !== null)
|
|
52
|
+
found.add(m[1]);
|
|
53
|
+
}
|
|
54
|
+
else if (Array.isArray(step.argument)) {
|
|
55
|
+
for (const row of step.argument) {
|
|
56
|
+
for (const cell of row) {
|
|
57
|
+
while ((m = re.exec(cell)) !== null)
|
|
58
|
+
found.add(m[1]);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
// Also walk the existing examples table headers, since some
|
|
64
|
+
// scenarios reference a placeholder only via the row title and not
|
|
65
|
+
// in step text. (Rare, but legal Gherkin.)
|
|
66
|
+
if (scenario.examples && scenario.examples.length > 0) {
|
|
67
|
+
for (const k of Object.keys(scenario.examples[0])) {
|
|
68
|
+
// Headers don't need a `< >` wrapper in the parsed IR — we
|
|
69
|
+
// include them so column matching is symmetric with whatever
|
|
70
|
+
// the .feature file declared.
|
|
71
|
+
found.add(k);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return Array.from(found).sort();
|
|
75
|
+
}
|
|
76
|
+
function injectExamples(feature, rows) {
|
|
77
|
+
const report = {
|
|
78
|
+
totalScenarios: feature.scenarios.length,
|
|
79
|
+
swappedScenarios: 0,
|
|
80
|
+
skippedScenarios: [],
|
|
81
|
+
rowsPerSwap: rows.length,
|
|
82
|
+
};
|
|
83
|
+
if (rows.length === 0) {
|
|
84
|
+
// Nothing to inject — leave inline Examples untouched.
|
|
85
|
+
for (const s of feature.scenarios) {
|
|
86
|
+
report.skippedScenarios.push({
|
|
87
|
+
name: s.name,
|
|
88
|
+
reason: "data file had zero rows",
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
return report;
|
|
92
|
+
}
|
|
93
|
+
const dataColumns = new Set(Object.keys(rows[0]));
|
|
94
|
+
for (const scenario of feature.scenarios) {
|
|
95
|
+
const placeholders = extractPlaceholders(scenario);
|
|
96
|
+
if (placeholders.length === 0) {
|
|
97
|
+
// Vanilla Scenario — no placeholders, no injection needed.
|
|
98
|
+
report.skippedScenarios.push({
|
|
99
|
+
name: scenario.name,
|
|
100
|
+
reason: "vanilla Scenario (no <placeholders>)",
|
|
101
|
+
});
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
const missing = placeholders.filter((p) => !dataColumns.has(p));
|
|
105
|
+
if (missing.length > 0) {
|
|
106
|
+
report.skippedScenarios.push({
|
|
107
|
+
name: scenario.name,
|
|
108
|
+
reason: `data file is missing column(s): ${missing.join(", ")}`,
|
|
109
|
+
});
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
// Swap. Keep only the columns the scenario actually references —
|
|
113
|
+
// extra columns in the data file are silently dropped per
|
|
114
|
+
// scenario, so a single 50-column data file can feed many narrow
|
|
115
|
+
// scenarios without each one pulling in noise.
|
|
116
|
+
scenario.examples = rows.map((row) => {
|
|
117
|
+
const filtered = {};
|
|
118
|
+
for (const p of placeholders)
|
|
119
|
+
filtered[p] = row[p];
|
|
120
|
+
return filtered;
|
|
121
|
+
});
|
|
122
|
+
report.swappedScenarios += 1;
|
|
123
|
+
}
|
|
124
|
+
return report;
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Convert an `InjectionReport` into `ReviewItem`s ready to push onto
|
|
128
|
+
* the scaffold's review queue. One info line per swap, one warning
|
|
129
|
+
* per skip whose reason indicates a real misconfiguration (missing
|
|
130
|
+
* columns) — skips for "vanilla Scenario" or "zero rows" are info-only.
|
|
131
|
+
*/
|
|
132
|
+
function reportToReviewItems(report, source) {
|
|
133
|
+
const items = [];
|
|
134
|
+
if (report.swappedScenarios > 0) {
|
|
135
|
+
items.push({
|
|
136
|
+
severity: "info",
|
|
137
|
+
message: `Data injection from ${source}: swapped Examples in ${report.swappedScenarios}/${report.totalScenarios} scenario(s), ${report.rowsPerSwap} rows each.`,
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
for (const skip of report.skippedScenarios) {
|
|
141
|
+
const isMisconfig = skip.reason.startsWith("data file is missing");
|
|
142
|
+
items.push({
|
|
143
|
+
severity: isMisconfig ? "warn" : "info",
|
|
144
|
+
message: `Data injection skipped scenario "${skip.name}": ${skip.reason}`,
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
return items;
|
|
148
|
+
}
|
|
149
|
+
//# sourceMappingURL=examplesInjector.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"examplesInjector.js","sourceRoot":"","sources":["../../src/data/examplesInjector.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;;AAaH,kDA6BC;AAoBD,wCAuDC;AAQD,kDAmBC;AA3ID;;;;;;;GAOG;AACH,SAAgB,mBAAmB,CAAC,QAAoB;IACtD,MAAM,KAAK,GAAG,IAAI,GAAG,EAAU,CAAC;IAChC,MAAM,EAAE,GAAG,sBAAsB,CAAC;IAClC,KAAK,MAAM,IAAI,IAAI,QAAQ,CAAC,KAAK,EAAE,CAAC;QAClC,IAAI,CAAyB,CAAC;QAC9B,OAAO,CAAC,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,KAAK,IAAI;YAAE,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QAC1D,iDAAiD;QACjD,IAAI,OAAO,IAAI,CAAC,QAAQ,KAAK,QAAQ,EAAE,CAAC;YACtC,OAAO,CAAC,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,KAAK,IAAI;gBAAE,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QAChE,CAAC;aAAM,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC;YACxC,KAAK,MAAM,GAAG,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;gBAChC,KAAK,MAAM,IAAI,IAAI,GAAG,EAAE,CAAC;oBACvB,OAAO,CAAC,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,KAAK,IAAI;wBAAE,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;gBACvD,CAAC;YACH,CAAC;QACH,CAAC;IACH,CAAC;IACD,4DAA4D;IAC5D,mEAAmE;IACnE,2CAA2C;IAC3C,IAAI,QAAQ,CAAC,QAAQ,IAAI,QAAQ,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACtD,KAAK,MAAM,CAAC,IAAI,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;YAClD,2DAA2D;YAC3D,6DAA6D;YAC7D,8BAA8B;YAC9B,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;QACf,CAAC;IACH,CAAC;IACD,OAAO,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE,CAAC;AAClC,CAAC;AAoBD,SAAgB,cAAc,CAC5B,OAAkB,EAClB,IAAe;IAEf,MAAM,MAAM,GAAoB;QAC9B,cAAc,EAAE,OAAO,CAAC,SAAS,CAAC,MAAM;QACxC,gBAAgB,EAAE,CAAC;QACnB,gBAAgB,EAAE,EAAE;QACpB,WAAW,EAAE,IAAI,CAAC,MAAM;KACzB,CAAC;IAEF,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACtB,uDAAuD;QACvD,KAAK,MAAM,CAAC,IAAI,OAAO,CAAC,SAAS,EAAE,CAAC;YAClC,MAAM,CAAC,gBAAgB,CAAC,IAAI,CAAC;gBAC3B,IAAI,EAAE,CAAC,CAAC,IAAI;gBACZ,MAAM,EAAE,yBAAyB;aAClC,CAAC,CAAC;QACL,CAAC;QACD,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,MAAM,WAAW,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAElD,KAAK,MAAM,QAAQ,IAAI,OAAO,CAAC,SAAS,EAAE,CAAC;QACzC,MAAM,YAAY,GAAG,mBAAmB,CAAC,QAAQ,CAAC,CAAC;QACnD,IAAI,YAAY,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC9B,2DAA2D;YAC3D,MAAM,CAAC,gBAAgB,CAAC,IAAI,CAAC;gBAC3B,IAAI,EAAE,QAAQ,CAAC,IAAI;gBACnB,MAAM,EAAE,sCAAsC;aAC/C,CAAC,CAAC;YACH,SAAS;QACX,CAAC;QACD,MAAM,OAAO,GAAG,YAAY,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;QAChE,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACvB,MAAM,CAAC,gBAAgB,CAAC,IAAI,CAAC;gBAC3B,IAAI,EAAE,QAAQ,CAAC,IAAI;gBACnB,MAAM,EAAE,mCAAmC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE;aAChE,CAAC,CAAC;YACH,SAAS;QACX,CAAC;QACD,iEAAiE;QACjE,0DAA0D;QAC1D,iEAAiE;QACjE,+CAA+C;QAC/C,QAAQ,CAAC,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE;YACnC,MAAM,QAAQ,GAAY,EAAE,CAAC;YAC7B,KAAK,MAAM,CAAC,IAAI,YAAY;gBAAE,QAAQ,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC;YACnD,OAAO,QAAQ,CAAC;QAClB,CAAC,CAAC,CAAC;QACH,MAAM,CAAC,gBAAgB,IAAI,CAAC,CAAC;IAC/B,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;;;;GAKG;AACH,SAAgB,mBAAmB,CACjC,MAAuB,EACvB,MAAc;IAEd,MAAM,KAAK,GAAiB,EAAE,CAAC;IAC/B,IAAI,MAAM,CAAC,gBAAgB,GAAG,CAAC,EAAE,CAAC;QAChC,KAAK,CAAC,IAAI,CAAC;YACT,QAAQ,EAAE,MAAM;YAChB,OAAO,EAAE,uBAAuB,MAAM,yBAAyB,MAAM,CAAC,gBAAgB,IAAI,MAAM,CAAC,cAAc,iBAAiB,MAAM,CAAC,WAAW,aAAa;SAChK,CAAC,CAAC;IACL,CAAC;IACD,KAAK,MAAM,IAAI,IAAI,MAAM,CAAC,gBAAgB,EAAE,CAAC;QAC3C,MAAM,WAAW,GAAG,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,sBAAsB,CAAC,CAAC;QACnE,KAAK,CAAC,IAAI,CAAC;YACT,QAAQ,EAAE,WAAW,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM;YACvC,OAAO,EAAE,oCAAoC,IAAI,CAAC,IAAI,MAAM,IAAI,CAAC,MAAM,EAAE;SAC1E,CAAC,CAAC;IACL,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* v4.0.0 — Synthetic data generator.
|
|
3
|
+
*
|
|
4
|
+
* Reads a schema like:
|
|
5
|
+
*
|
|
6
|
+
* {
|
|
7
|
+
* "email": "faker.internet.email",
|
|
8
|
+
* "password": "faker.internet.password",
|
|
9
|
+
* "url_fragment": "inventory.html", // literal
|
|
10
|
+
* "claim_desc": "llm:auto insurance claim, one sentence"
|
|
11
|
+
* }
|
|
12
|
+
*
|
|
13
|
+
* Produces N `DataRow`s. Each row's fields are filled by routing the
|
|
14
|
+
* source string:
|
|
15
|
+
*
|
|
16
|
+
* - "faker.X.Y[.Z]..." → dynamic dotted-path lookup on faker, called
|
|
17
|
+
* with no args. Standard faker fields cover
|
|
18
|
+
* >95% of test data needs.
|
|
19
|
+
* - "llm:<prompt>" → ONE batched LLM call generates an array of
|
|
20
|
+
* N values for the whole column. Cost-optimal:
|
|
21
|
+
* three LLM-driven fields = three batched
|
|
22
|
+
* calls regardless of row count.
|
|
23
|
+
* - anything else → literal string, same value in every row.
|
|
24
|
+
*
|
|
25
|
+
* Determinism: faker is seeded with `opts.seed` (default 42). Same
|
|
26
|
+
* seed + same schema + same row count = byte-identical output across
|
|
27
|
+
* runs. LLM fields are NOT deterministic in the same way (temperature
|
|
28
|
+
* is fixed at 0 but provider sampling has residual nondeterminism),
|
|
29
|
+
* which is intentional — LLM-generated domain data should look
|
|
30
|
+
* varied each time you regenerate.
|
|
31
|
+
*
|
|
32
|
+
* Why batched LLM calls per column instead of one big call for the
|
|
33
|
+
* whole table: the prompt stays simple ("generate N car insurance
|
|
34
|
+
* claim descriptions"), the response stays a flat JSON array (easy
|
|
35
|
+
* to parse), and a column failure doesn't poison the other columns.
|
|
36
|
+
*/
|
|
37
|
+
import type { DataRow } from "./dataLoader";
|
|
38
|
+
import type { LLMClient } from "../llm/types";
|
|
39
|
+
export interface SynthSchema {
|
|
40
|
+
/** field name → source spec. See module doc for syntax. */
|
|
41
|
+
[columnName: string]: string;
|
|
42
|
+
}
|
|
43
|
+
export interface SynthOptions {
|
|
44
|
+
/** How many rows to generate. */
|
|
45
|
+
rows: number;
|
|
46
|
+
/** Faker RNG seed for reproducibility. Default 42. */
|
|
47
|
+
seed?: number;
|
|
48
|
+
/**
|
|
49
|
+
* LLM client to use for "llm:..." fields. Optional — if a schema
|
|
50
|
+
* has no LLM fields you can omit this. If a schema HAS LLM fields
|
|
51
|
+
* and this is undefined, those fields will be filled with the
|
|
52
|
+
* source-string literal as a fallback (visible in output so the
|
|
53
|
+
* user notices the gap).
|
|
54
|
+
*/
|
|
55
|
+
llm?: LLMClient;
|
|
56
|
+
/** Optional schema-relative model override for LLM calls. */
|
|
57
|
+
llmModel?: string;
|
|
58
|
+
}
|
|
59
|
+
export declare class SynthGeneratorError extends Error {
|
|
60
|
+
constructor(message: string);
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Generate N rows. Returns `DataRow[]` with the same shape as
|
|
64
|
+
* `loadDataFile` so downstream Examples injection works identically.
|
|
65
|
+
*/
|
|
66
|
+
export declare function generateSyntheticRows(schema: SynthSchema, opts: SynthOptions): Promise<DataRow[]>;
|
|
67
|
+
/**
|
|
68
|
+
* Convenience: load a schema JSON from disk and validate it has at
|
|
69
|
+
* least one column. Throws SynthGeneratorError on any read/parse error.
|
|
70
|
+
*/
|
|
71
|
+
export declare function loadSchema(schemaPath: string): SynthSchema;
|