@sungen/driver-data-factory 3.2.12-beta.5 → 3.2.12-beta.6

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sungen/driver-data-factory",
3
- "version": "3.2.12-beta.5",
3
+ "version": "3.2.12-beta.6",
4
4
  "description": "Sungen Data Factory capability — standardized test-data generation from a field-type + security knowledge catalog (valid/boundary/invalid + traceability), the .overwrite merge layer, and (opt-in) @dataFactory runtime seeding. Design-time knowledge is runtime-free. Plugs into @sun-asterisk/sungen via the capability SPI.",
5
5
  "main": "src/index.ts",
6
6
  "types": "src/index.ts",
@@ -14,7 +14,7 @@
14
14
  "author": "eqe team (engineer & quality) — Sun Asterisk",
15
15
  "license": "MIT",
16
16
  "dependencies": {
17
- "@sun-asterisk/sungen": "3.2.12-beta.5",
17
+ "@sun-asterisk/sungen": "3.2.12-beta.6",
18
18
  "yaml": "^2.4.0"
19
19
  },
20
20
  "files": [
package/src/cli.ts CHANGED
@@ -233,7 +233,7 @@ export function registerDataFactoryCommand(program: any): void {
233
233
  .description('Generate a CSV import fixture from the field-map (bulk-import / file-upload testing)')
234
234
  .requiredOption('--screen <name>', 'Unit whose field-map to build a fixture from')
235
235
  .option('--rows <n>', 'Number of valid rows', '3')
236
- .option('--invalid', 'Also write <name>.invalid.csv (one seeded-bad row per field + _violation column)')
236
+ .option('--invalid', 'Also write <name>.invalid.csv (one seeded-bad row per field) + <name>.invalid.expected.yaml (per-row expected rejection)')
237
237
  .option('--force', 'Overwrite existing fixture file(s) without asking')
238
238
  .action(async (opts: { screen: string; rows?: string; invalid?: boolean; force?: boolean }) => {
239
239
  const cwd = process.cwd();
@@ -247,12 +247,18 @@ export function registerDataFactoryCommand(program: any): void {
247
247
  const validPath = join(dir, `${opts.screen}.csv`);
248
248
  const writes: Array<[string, string]> = [[validPath, res.valid]];
249
249
  if (res.invalid) writes.push([join(dir, `${opts.screen}.invalid.csv`), res.invalid]);
250
+ // Sidecar: expected rejection per row — keeps the CSV payload clean (no _violation column).
251
+ if (res.expected) writes.push([join(dir, `${opts.screen}.invalid.expected.yaml`), stringifyYaml(res.expected)]);
250
252
  for (const [p] of writes) {
251
253
  if (existsSync(p) && !opts.force) {
252
254
  const a = (await ask(` ? ${p.replace(cwd + '/', '')} exists — overwrite? [y/N] `)).toLowerCase();
253
255
  if (a !== 'y' && a !== 'yes') { console.log(' · skipped'); return; }
254
256
  }
255
257
  }
256
- for (const [p, content] of writes) { writeFileSync(p, content, 'utf8'); console.log(` ✓ wrote ${p.replace(cwd + '/', '')} (${res.headers.length} columns)`); }
258
+ for (const [p, content] of writes) {
259
+ writeFileSync(p, content, 'utf8');
260
+ const label = p.endsWith('.expected.yaml') ? 'expected-rejection sidecar' : `${res.headers.length} columns`;
261
+ console.log(` ✓ wrote ${p.replace(cwd + '/', '')} (${label})`);
262
+ }
257
263
  });
258
264
  }
package/src/fixture.ts CHANGED
@@ -6,9 +6,11 @@
6
6
  * Export` checklist area. Two products:
7
7
  * • the VALID fixture — N rows of valid values (should import cleanly). Unique fields get a
8
8
  * per-row suffix so rows don't collide.
9
- * • the INVALID fixture — one row per field's first invalid case (all-valid except that one field),
10
- * plus a trailing `_violation` column naming the field + reason + expected error — so a row-level
11
- * import-rejection test knows which row should fail and why.
9
+ * • the INVALID fixture — one row per field's first invalid case (all-valid except that one field).
10
+ * It carries the SAME headers as the valid file (no extra column) so a schema-strict importer
11
+ * fails on the DATA, not on an unexpected column. The "which row is bad and why" lives in a
12
+ * SIDECAR (`<name>.invalid.expected.yaml`), keyed by 0-based data-row index — the expected result
13
+ * never alters the payload under test.
12
14
  *
13
15
  * Deterministic: same field-map + catalog → same CSV. Columns are the flattened leaf paths (nested
14
16
  * groups → dotted headers, e.g. `address.city`). Reference the file from an `@api` upload (`files:`)
@@ -31,7 +33,11 @@ function leaves(expanded: ExpandedField[], prefix = ''): Leaf[] {
31
33
  }
32
34
 
33
35
  const validOf = (f: ExpandedField): string => f.points.find((p) => p.group === 'valid' && p.value != null)?.value ?? '';
34
- const firstInvalid = (f: ExpandedField) => f.points.find((p) => p.group === 'invalid' && p.value != null);
36
+ // First concrete invalid, preferring a plain (non-adversarial) format/boundary invalid over a
37
+ // security payload — a bulk-import row-rejection test wants a data violation, not an injection string.
38
+ const firstInvalid = (f: ExpandedField) =>
39
+ f.points.find((p) => p.group === 'invalid' && p.category !== 'adversarial' && p.value != null) ??
40
+ f.points.find((p) => p.group === 'invalid' && p.value != null);
35
41
 
36
42
  /** RFC-4180-ish CSV cell quoting. */
37
43
  function cell(v: string): string {
@@ -41,9 +47,23 @@ function row(cells: string[]): string {
41
47
  return cells.map(cell).join(',');
42
48
  }
43
49
 
50
+ /** One expected-rejection entry in the sidecar, keyed to a 0-based data row of the invalid CSV. */
51
+ export interface ExpectedRow {
52
+ row: number;
53
+ field: string;
54
+ chk: string;
55
+ reason: string;
56
+ expected_error?: string;
57
+ }
58
+ export interface FixtureExpected {
59
+ _note: string;
60
+ rows: ExpectedRow[];
61
+ }
62
+
44
63
  export interface FixtureResult {
45
64
  valid: string; // CSV text (header + N valid rows)
46
- invalid?: string; // CSV text (header + _violation + one bad row per field)
65
+ invalid?: string; // CSV text (SAME headers as valid + one bad row per field — no extra column)
66
+ expected?: FixtureExpected; // sidecar: per-row expected rejection (0-based data-row index)
47
67
  headers: string[];
48
68
  }
49
69
 
@@ -62,18 +82,30 @@ export function generateFixture(map: FieldMap, catalog: Catalog, opts: { rows?:
62
82
  const valid = [row(headers), ...validRows].join('\n') + '\n';
63
83
 
64
84
  let invalid: string | undefined;
85
+ let expected: FixtureExpected | undefined;
65
86
  if (opts.includeInvalid) {
66
- const invHeaders = [...headers, '_violation'];
67
87
  const invRows: string[] = [];
88
+ const expectedRows: ExpectedRow[] = [];
68
89
  for (const target of ls) {
69
90
  const bad = firstInvalid(target.field);
70
91
  if (!bad) continue; // no concrete invalid value to seed
92
+ // Same columns as the valid file — only the target cell is bad. No `_violation` column: a
93
+ // schema-strict importer must fail on the data, not reject the file for an unexpected column.
71
94
  const cells = ls.map((l) => (l === target ? (bad.value as string) : validOf(l.field).replace(/\{\{\$timestamp\}\}/g, '1')));
72
- const why = `${target.path}: ${bad.reason || 'invalid'}${bad.expectedError ? ` (${bad.expectedError})` : ''}`;
73
- invRows.push(row([...cells, why]));
95
+ expectedRows.push(trimExpected({ row: invRows.length, field: target.path, chk: bad.id, reason: bad.reason || 'invalid', expected_error: bad.expectedError }));
96
+ invRows.push(row(cells));
74
97
  }
75
- invalid = [row(invHeaders), ...invRows].join('\n') + '\n';
98
+ invalid = [row(headers), ...invRows].join('\n') + '\n';
99
+ expected = {
100
+ _note: 'Each row of the .invalid.csv should be REJECTED. `row` is the 0-based data-row index (header excluded). The CSV carries no extra columns so a strict importer fails on the data, not the schema.',
101
+ rows: expectedRows,
102
+ };
76
103
  }
77
104
 
78
- return { valid, invalid, headers };
105
+ return { valid, invalid, expected, headers };
106
+ }
107
+
108
+ function trimExpected(e: ExpectedRow): ExpectedRow {
109
+ if (e.expected_error == null) { const { expected_error, ...rest } = e; return rest as ExpectedRow; }
110
+ return e;
79
111
  }
package/src/index.ts CHANGED
@@ -30,7 +30,7 @@ export type { Validity, BoundaryPoint } from './boundary';
30
30
  export { evaluateWhen, inProfile, fieldFacts } from './applicability';
31
31
  export { registerDataFactoryCommand } from './cli';
32
32
  export { generateFixture } from './fixture';
33
- export type { FixtureResult } from './fixture';
33
+ export type { FixtureResult, FixtureExpected, ExpectedRow } from './fixture';
34
34
  export { dataFactoryCoverageSensor } from './sensor';
35
35
  export { dfPreconditionCodegen } from './precondition';
36
36
  export type { Catalog, FieldType, SecurityBank, Technique, DataPoint, OverwriteWarning, CatalogProvenance, FieldTypeProvenance, SelectionContext, Profile } from './types';