@sungen/driver-db 3.2.14 → 3.2.16-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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sungen/driver-db",
3
- "version": "3.2.14",
3
+ "version": "3.2.16-beta.1",
4
4
  "description": "Sungen DB capability (Data Driver) — the @query annotation, query catalog, declarative DB steps, and the specs/db.ts runtime template. Plugs into @sun-asterisk/sungen via the capability SPI.",
5
5
  "main": "src/index.ts",
6
6
  "types": "src/index.ts",
@@ -23,7 +23,7 @@
23
23
  "author": "eqe team (engineer & quality) — Sun Asterisk",
24
24
  "license": "MIT",
25
25
  "dependencies": {
26
- "@sun-asterisk/sungen": "3.2.14"
26
+ "@sun-asterisk/sungen": "3.2.16-beta.1"
27
27
  },
28
28
  "files": [
29
29
  "dist",
@@ -22,11 +22,28 @@ const reNoRow = new RegExp(`see\\s+${TABLE}\\s+no\\s+row\\s+where\\b`, 'i');
22
22
  const reCount = new RegExp(`see\\s+${TABLE}.*\\bcount\\s+is\\b`, 'i');
23
23
 
24
24
  /** True when a step is a declarative DB-verification step (used to wire the `db` import).
25
- * Named queries are invoked via the `@query:` annotation, not a step — see code-generator. */
25
+ * Named queries are invoked via the `@query:` annotation, not a step — see code-generator.
26
+ * Callers pass the ORIGINAL (unmasked) step text here — see the masked matcher pair below. */
26
27
  export function isDbStep(text: string): boolean {
27
28
  return reNoRow.test(text) || reRow.test(text) || reCount.test(text);
28
29
  }
29
30
 
31
+ /**
32
+ * Masked-text matcher pair: PatternRegistry.findPattern() matches against step text with every
33
+ * `[...]`/`{{...}}` ref CONTENT replaced by `~` (see maskRefsForMatching in core's patterns/index.ts),
34
+ * so a verb/keyword substring inside a selector or variable name can't hijack classification. The
35
+ * table identifier here is always a ref, so `[table]` becomes `[~]` at matching time — the RAW
36
+ * `reRow`/`reNoRow`/`reCount` above can never match masked text. These mirror them against the
37
+ * masked shape; `isDbStep()` and the generators below still use the raw regexes against the
38
+ * ORIGINAL step text supplied by the caller.
39
+ */
40
+ const reRowMasked = /see\s+\[~\]\s+row\s+where\b/i;
41
+ const reNoRowMasked = /see\s+\[~\]\s+no\s+row\s+where\b/i;
42
+ // Mirrors the generator's extraction shape (`[table] [where …] count is`) instead of `.*` — a lax
43
+ // gap would let a UI text assertion whose literal happens to contain "count is" (e.g.
44
+ // `see [Cart badge] contains "count is high"`) claim this priority-60 pattern away from the UI drivers.
45
+ const reCountMasked = /see\s+\[~\](?:\s+where\s+.+?)?\s+count\s+is\b/i;
46
+
30
47
  /** Render a value token (`{{var}}` | "literal" | 'literal' | number) as a JS expression. */
31
48
  function valueExpr(token: string): string {
32
49
  const t = token.trim();
@@ -62,9 +79,12 @@ export const databasePatterns: StepPattern[] = [
62
79
  {
63
80
  name: 'db-no-row',
64
81
  priority: 60, // above generic see-assertions
65
- matcher: (step: ParsedStep) => reNoRow.test(step.text),
82
+ matcher: (step: ParsedStep) => reNoRowMasked.test(step.text),
66
83
  generator: (step: ParsedStep, _ctx: PatternContext): MappedStep => {
67
- const m = step.text.match(new RegExp(`${TABLE}\\s+no\\s+row\\s+where\\s+(.+)$`, 'i'))!;
84
+ // The masked matcher is looser than this extraction regex, so a malformed step (e.g. an
85
+ // empty table/filter) can claim the pattern yet not re-match here — emit a TODO instead of crashing.
86
+ const m = step.text.match(new RegExp(`${TABLE}\\s+no\\s+row\\s+where\\s+(.+)$`, 'i'));
87
+ if (!m) return { code: `// TODO: malformed 'no row' step (expected: User see [table] no row where [col] is VALUE): ${step.text}`, comment: 'Unrecognized db-no-row step' };
68
88
  const table = m[1];
69
89
  const filter = parseFilter(m[2]);
70
90
  return { code: `await db.assertNoRow(${JSON.stringify(table)}, ${filter});`, comment: `DB: no row in ${table}` };
@@ -73,9 +93,10 @@ export const databasePatterns: StepPattern[] = [
73
93
  {
74
94
  name: 'db-count',
75
95
  priority: 60,
76
- matcher: (step: ParsedStep) => reCount.test(step.text) && !reRow.test(step.text),
96
+ matcher: (step: ParsedStep) => reCountMasked.test(step.text) && !reRowMasked.test(step.text),
77
97
  generator: (step: ParsedStep, _ctx: PatternContext): MappedStep => {
78
- const m = step.text.match(new RegExp(`${TABLE}(?:\\s+where\\s+(.+?))?\\s+count\\s+is\\s+(${VALUE})`, 'i'))!;
98
+ const m = step.text.match(new RegExp(`${TABLE}(?:\\s+where\\s+(.+?))?\\s+count\\s+is\\s+(${VALUE})`, 'i'));
99
+ if (!m) return { code: `// TODO: malformed 'count is' step (expected: User see [table] [where [col] is VALUE] count is VALUE): ${step.text}`, comment: 'Unrecognized db-count step' };
79
100
  const table = m[1];
80
101
  const filter = m[2] ? parseFilter(m[2]) : '{}';
81
102
  return { code: `await db.assertCount(${JSON.stringify(table)}, ${filter}, Number(${valueExpr(m[3])}));`, comment: `DB: count rows in ${table}` };
@@ -84,10 +105,11 @@ export const databasePatterns: StepPattern[] = [
84
105
  {
85
106
  name: 'db-row',
86
107
  priority: 60,
87
- matcher: (step: ParsedStep) => reRow.test(step.text),
108
+ matcher: (step: ParsedStep) => reRowMasked.test(step.text),
88
109
  generator: (step: ParsedStep, _ctx: PatternContext): MappedStep => {
89
110
  // [table] row where <filter> [has <expected>]
90
- const m = step.text.match(new RegExp(`${TABLE}\\s+row\\s+where\\s+(.+?)(?:\\s+has\\s+(.+))?$`, 'i'))!;
111
+ const m = step.text.match(new RegExp(`${TABLE}\\s+row\\s+where\\s+(.+?)(?:\\s+has\\s+(.+))?$`, 'i'));
112
+ if (!m) return { code: `// TODO: malformed 'row where' step (expected: User see [table] row where [col] is VALUE [has [col2] = VALUE2]): ${step.text}`, comment: 'Unrecognized db-row step' };
91
113
  const table = m[1];
92
114
  const filter = parseFilter(m[2]);
93
115
  const expected = m[3] ? parseExpected(m[3]) : '';