@sun-asterisk/sungen 3.1.2-beta.99 → 3.2.0-beta.141
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/capabilities/context.d.ts +17 -0
- package/dist/capabilities/context.d.ts.map +1 -1
- package/dist/capabilities/discover.d.ts.map +1 -1
- package/dist/capabilities/discover.js +70 -9
- package/dist/capabilities/discover.js.map +1 -1
- package/dist/capabilities/registry.d.ts +3 -1
- package/dist/capabilities/registry.d.ts.map +1 -1
- package/dist/capabilities/registry.js.map +1 -1
- package/dist/capabilities/sensor.d.ts +3 -0
- package/dist/capabilities/sensor.d.ts.map +1 -1
- package/dist/cli/commands/audit.d.ts.map +1 -1
- package/dist/cli/commands/audit.js +17 -11
- package/dist/cli/commands/audit.js.map +1 -1
- package/dist/cli/commands/capability.d.ts.map +1 -1
- package/dist/cli/commands/capability.js +57 -5
- package/dist/cli/commands/capability.js.map +1 -1
- package/dist/cli/commands/context.d.ts +9 -0
- package/dist/cli/commands/context.d.ts.map +1 -0
- package/dist/cli/commands/context.js +91 -0
- package/dist/cli/commands/context.js.map +1 -0
- package/dist/cli/commands/delivery.d.ts.map +1 -1
- package/dist/cli/commands/delivery.js +42 -30
- package/dist/cli/commands/delivery.js.map +1 -1
- package/dist/cli/commands/generate.d.ts.map +1 -1
- package/dist/cli/commands/generate.js +28 -5
- package/dist/cli/commands/generate.js.map +1 -1
- package/dist/cli/commands/ledger.d.ts.map +1 -1
- package/dist/cli/commands/ledger.js +15 -5
- package/dist/cli/commands/ledger.js.map +1 -1
- package/dist/cli/commands/manifest.d.ts.map +1 -1
- package/dist/cli/commands/manifest.js +10 -9
- package/dist/cli/commands/manifest.js.map +1 -1
- package/dist/cli/commands/repair.d.ts +8 -0
- package/dist/cli/commands/repair.d.ts.map +1 -0
- package/dist/cli/commands/repair.js +97 -0
- package/dist/cli/commands/repair.js.map +1 -0
- package/dist/cli/commands/script-check.d.ts.map +1 -1
- package/dist/cli/commands/script-check.js +13 -9
- package/dist/cli/commands/script-check.js.map +1 -1
- package/dist/cli/commands/trace.d.ts.map +1 -1
- package/dist/cli/commands/trace.js +7 -4
- package/dist/cli/commands/trace.js.map +1 -1
- package/dist/cli/index.js +4 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/generators/test-generator/code-generator.d.ts +7 -0
- package/dist/generators/test-generator/code-generator.d.ts.map +1 -1
- package/dist/generators/test-generator/code-generator.js +112 -42
- package/dist/generators/test-generator/code-generator.js.map +1 -1
- package/dist/harness/annotation-overrides.d.ts +3 -1
- package/dist/harness/annotation-overrides.d.ts.map +1 -1
- package/dist/harness/annotation-overrides.js +3 -1
- package/dist/harness/annotation-overrides.js.map +1 -1
- package/dist/harness/audit.d.ts +9 -1
- package/dist/harness/audit.d.ts.map +1 -1
- package/dist/harness/audit.js +114 -12
- package/dist/harness/audit.js.map +1 -1
- package/dist/harness/capability-plan.d.ts +14 -0
- package/dist/harness/capability-plan.d.ts.map +1 -1
- package/dist/harness/capability-plan.js +63 -1
- package/dist/harness/capability-plan.js.map +1 -1
- package/dist/harness/data-driven-lint.d.ts.map +1 -1
- package/dist/harness/data-driven-lint.js +23 -0
- package/dist/harness/data-driven-lint.js.map +1 -1
- package/dist/harness/flow-check.d.ts +9 -0
- package/dist/harness/flow-check.d.ts.map +1 -1
- package/dist/harness/flow-check.js +13 -6
- package/dist/harness/flow-check.js.map +1 -1
- package/dist/harness/intent.d.ts +6 -0
- package/dist/harness/intent.d.ts.map +1 -1
- package/dist/harness/intent.js +20 -4
- package/dist/harness/intent.js.map +1 -1
- package/dist/harness/ledger.d.ts.map +1 -1
- package/dist/harness/ledger.js +3 -2
- package/dist/harness/ledger.js.map +1 -1
- package/dist/harness/manifest.d.ts.map +1 -1
- package/dist/harness/manifest.js +3 -2
- package/dist/harness/manifest.js.map +1 -1
- package/dist/harness/parse.d.ts +1 -0
- package/dist/harness/parse.d.ts.map +1 -1
- package/dist/harness/parse.js +3 -0
- package/dist/harness/parse.js.map +1 -1
- package/dist/harness/quality-gates.js +1 -1
- package/dist/harness/quality-gates.js.map +1 -1
- package/dist/harness/query-catalog.d.ts.map +1 -1
- package/dist/harness/query-catalog.js +0 -0
- package/dist/harness/query-catalog.js.map +1 -1
- package/dist/harness/repair.d.ts +20 -0
- package/dist/harness/repair.d.ts.map +1 -0
- package/dist/harness/repair.js +111 -0
- package/dist/harness/repair.js.map +1 -0
- package/dist/harness/script-check.d.ts +3 -1
- package/dist/harness/script-check.d.ts.map +1 -1
- package/dist/harness/script-check.js +22 -8
- package/dist/harness/script-check.js.map +1 -1
- package/dist/harness/sensors.d.ts +40 -0
- package/dist/harness/sensors.d.ts.map +1 -1
- package/dist/harness/sensors.js +54 -2
- package/dist/harness/sensors.js.map +1 -1
- package/dist/harness/trace.d.ts.map +1 -1
- package/dist/harness/trace.js +4 -3
- package/dist/harness/trace.js.map +1 -1
- package/dist/harness/unit-paths.d.ts +3 -0
- package/dist/harness/unit-paths.d.ts.map +1 -0
- package/dist/harness/unit-paths.js +52 -0
- package/dist/harness/unit-paths.js.map +1 -0
- package/dist/index.d.ts +4 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -1
- package/dist/index.js.map +1 -1
- package/dist/orchestrator/ai-rules-updater.d.ts.map +1 -1
- package/dist/orchestrator/ai-rules-updater.js +2 -0
- package/dist/orchestrator/ai-rules-updater.js.map +1 -1
- package/dist/orchestrator/context-discovery.d.ts +12 -0
- package/dist/orchestrator/context-discovery.d.ts.map +1 -0
- package/dist/orchestrator/context-discovery.js +46 -0
- package/dist/orchestrator/context-discovery.js.map +1 -0
- package/dist/orchestrator/templates/ai-instructions/claude-agent-reviewer.md +7 -1
- package/dist/orchestrator/templates/ai-instructions/claude-cmd-create-test.md +6 -2
- package/dist/orchestrator/templates/ai-instructions/claude-cmd-run-test.md +18 -1
- package/dist/orchestrator/templates/ai-instructions/claude-skill-api-design.md +62 -0
- package/dist/orchestrator/templates/ai-instructions/claude-skill-harness-audit.md +2 -1
- package/dist/orchestrator/templates/ai-instructions/claude-skill-tc-generation.md +16 -2
- package/dist/orchestrator/templates/ai-instructions/claude-skill-viewpoint.md +14 -0
- package/dist/orchestrator/templates/ai-instructions/copilot-cmd-create-test.md +6 -2
- package/dist/orchestrator/templates/ai-instructions/copilot-cmd-run-test.md +11 -1
- package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-api-design.md +62 -0
- package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-harness-audit.md +2 -1
- package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-tc-generation.md +16 -2
- package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-viewpoint.md +14 -0
- package/dist/orchestrator/templates/specs-api.d.ts +38 -2
- package/dist/orchestrator/templates/specs-api.d.ts.map +1 -1
- package/dist/orchestrator/templates/specs-api.js +65 -22
- package/dist/orchestrator/templates/specs-api.js.map +1 -1
- package/dist/orchestrator/templates/specs-api.ts +71 -18
- package/dist/orchestrator/templates/specs-db.d.ts +3 -0
- package/dist/orchestrator/templates/specs-db.d.ts.map +1 -1
- package/dist/orchestrator/templates/specs-db.js +78 -1
- package/dist/orchestrator/templates/specs-db.js.map +1 -1
- package/dist/orchestrator/templates/specs-db.ts +78 -1
- package/dist/orchestrator/templates/specs-test-data.ts +2 -1
- package/package.json +2 -2
- package/src/capabilities/context.ts +19 -0
- package/src/capabilities/discover.ts +27 -7
- package/src/capabilities/registry.ts +3 -1
- package/src/capabilities/sensor.ts +1 -1
- package/src/cli/commands/audit.ts +15 -9
- package/src/cli/commands/capability.ts +53 -5
- package/src/cli/commands/context.ts +52 -0
- package/src/cli/commands/delivery.ts +40 -31
- package/src/cli/commands/generate.ts +30 -5
- package/src/cli/commands/ledger.ts +13 -5
- package/src/cli/commands/manifest.ts +9 -7
- package/src/cli/commands/repair.ts +57 -0
- package/src/cli/commands/script-check.ts +12 -8
- package/src/cli/commands/trace.ts +7 -4
- package/src/cli/index.ts +4 -0
- package/src/generators/test-generator/code-generator.ts +115 -40
- package/src/harness/annotation-overrides.ts +3 -1
- package/src/harness/audit.ts +115 -15
- package/src/harness/capability-plan.ts +51 -1
- package/src/harness/data-driven-lint.ts +20 -0
- package/src/harness/flow-check.ts +15 -6
- package/src/harness/intent.ts +25 -4
- package/src/harness/ledger.ts +3 -2
- package/src/harness/manifest.ts +3 -2
- package/src/harness/parse.ts +4 -0
- package/src/harness/quality-gates.ts +1 -1
- package/src/harness/query-catalog.ts +0 -0
- package/src/harness/repair.ts +75 -0
- package/src/harness/script-check.ts +25 -8
- package/src/harness/sensors.ts +71 -2
- package/src/harness/trace.ts +4 -3
- package/src/harness/unit-paths.ts +14 -0
- package/src/index.ts +4 -2
- package/src/orchestrator/ai-rules-updater.ts +2 -0
- package/src/orchestrator/context-discovery.ts +50 -0
- package/src/orchestrator/templates/ai-instructions/claude-agent-reviewer.md +7 -1
- package/src/orchestrator/templates/ai-instructions/claude-cmd-create-test.md +6 -2
- package/src/orchestrator/templates/ai-instructions/claude-cmd-run-test.md +18 -1
- package/src/orchestrator/templates/ai-instructions/claude-skill-api-design.md +62 -0
- package/src/orchestrator/templates/ai-instructions/claude-skill-harness-audit.md +2 -1
- package/src/orchestrator/templates/ai-instructions/claude-skill-tc-generation.md +16 -2
- package/src/orchestrator/templates/ai-instructions/claude-skill-viewpoint.md +14 -0
- package/src/orchestrator/templates/ai-instructions/copilot-cmd-create-test.md +6 -2
- package/src/orchestrator/templates/ai-instructions/copilot-cmd-run-test.md +11 -1
- package/src/orchestrator/templates/ai-instructions/github-skill-sungen-api-design.md +62 -0
- package/src/orchestrator/templates/ai-instructions/github-skill-sungen-harness-audit.md +2 -1
- package/src/orchestrator/templates/ai-instructions/github-skill-sungen-tc-generation.md +16 -2
- package/src/orchestrator/templates/ai-instructions/github-skill-sungen-viewpoint.md +14 -0
- package/src/orchestrator/templates/specs-api.ts +71 -18
- package/src/orchestrator/templates/specs-db.ts +78 -1
- package/src/orchestrator/templates/specs-test-data.ts +2 -1
|
@@ -48,6 +48,7 @@ exports.api = void 0;
|
|
|
48
48
|
*/
|
|
49
49
|
const fs = __importStar(require("fs"));
|
|
50
50
|
const path = __importStar(require("path"));
|
|
51
|
+
const test_1 = require("@playwright/test");
|
|
51
52
|
function loadEnvQa() {
|
|
52
53
|
for (const name of ['.env.qa', `.env.qa.${process.env.SUNGEN_ENV || ''}`]) {
|
|
53
54
|
const p = path.join(process.cwd(), name);
|
|
@@ -89,39 +90,81 @@ class ApiClient {
|
|
|
89
90
|
}
|
|
90
91
|
return { key, conf };
|
|
91
92
|
}
|
|
92
|
-
/**
|
|
93
|
-
|
|
93
|
+
/**
|
|
94
|
+
* Run a catalog request and return the response. `req` is embedded at compile time; `params` (path
|
|
95
|
+
* `:id`, JSON body `:fields`, and header `:tokens`) bind at runtime. Catalog `headers` layer over the
|
|
96
|
+
* datasource headers and may carry `:param` placeholders — e.g. `authorization: "Bearer :token"` with
|
|
97
|
+
* the dynamic token threaded from a prior response (flow chaining).
|
|
98
|
+
*/
|
|
99
|
+
async call(label, req, params = {}, opts = {}) {
|
|
94
100
|
const { conf } = this.cfg(req.datasource);
|
|
95
101
|
const base = (conf.base_url || conf.baseUrl || '').replace(/\/$/, '');
|
|
96
102
|
if (!base)
|
|
97
103
|
throw new Error(`API Driver: ${label} — datasource has no base_url (set it in .env.qa).`);
|
|
98
|
-
const
|
|
99
|
-
let body;
|
|
104
|
+
const urlPath = substitute(req.path, params); // path params (:id) bind at runtime
|
|
100
105
|
const headers = { ...(conf.headers || {}) };
|
|
106
|
+
// catalog headers; :param tokens bind at runtime — raw (no URL-encoding, unlike the path)
|
|
107
|
+
for (const [k, v] of Object.entries(req.headers || {}))
|
|
108
|
+
headers[k] = String(v).replace(/:([A-Za-z_][A-Za-z0-9_]*)/g, (_m, p) => String(params[p] ?? ''));
|
|
109
|
+
// Body: substitute `:param` into the body template (object values), then encode per `encoding`.
|
|
110
|
+
let body;
|
|
101
111
|
if (req.body !== undefined && req.body !== null) {
|
|
102
|
-
|
|
103
|
-
body = JSON.stringify(resolved);
|
|
104
|
-
if (!headers['content-type'] && !headers['Content-Type'])
|
|
105
|
-
headers['content-type'] = 'application/json';
|
|
112
|
+
body = JSON.parse(JSON.stringify(req.body).replace(/":([A-Za-z_][A-Za-z0-9_]*)"/g, (_m, p) => JSON.stringify(params[p] ?? null)));
|
|
106
113
|
}
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
114
|
+
// Map the wire format to the right Playwright option (#345): json → data (application/json,
|
|
115
|
+
// default), form → form (application/x-www-form-urlencoded), multipart → multipart (form-data).
|
|
116
|
+
const bodyOpt = {};
|
|
117
|
+
if (body !== undefined) {
|
|
118
|
+
const enc = req.encoding ?? 'json';
|
|
119
|
+
if (enc === 'form')
|
|
120
|
+
bodyOpt.form = body;
|
|
121
|
+
else if (enc === 'multipart')
|
|
122
|
+
bodyOpt.multipart = body;
|
|
123
|
+
else
|
|
124
|
+
bodyOpt.data = body;
|
|
125
|
+
}
|
|
126
|
+
// Playwright APIRequestContext: same runner/report/retries as UI tests. @hybrid passes
|
|
127
|
+
// `storageState` (the @auth role's saved session) so the request shares the browser's
|
|
128
|
+
// authenticated cookies. Disposed per call so no request context lingers and hangs the process.
|
|
129
|
+
const ctx = await test_1.request.newContext({
|
|
130
|
+
baseURL: base,
|
|
131
|
+
extraHTTPHeaders: headers,
|
|
132
|
+
timeout: conf.timeout_ms ?? 15000,
|
|
133
|
+
...(opts.storageState ? { storageState: opts.storageState } : {}),
|
|
134
|
+
});
|
|
110
135
|
try {
|
|
111
|
-
res = await fetch(
|
|
136
|
+
const res = await ctx.fetch(urlPath, { method: req.method, ...bodyOpt });
|
|
137
|
+
const text = await res.text();
|
|
138
|
+
let parsed = text;
|
|
139
|
+
try {
|
|
140
|
+
parsed = text ? JSON.parse(text) : null;
|
|
141
|
+
}
|
|
142
|
+
catch { /* non-JSON → keep text */ }
|
|
143
|
+
return { status: res.status(), ok: res.ok(), body: parsed, headers: res.headers() };
|
|
112
144
|
}
|
|
113
145
|
finally {
|
|
114
|
-
|
|
146
|
+
await ctx.dispose();
|
|
115
147
|
}
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Fire the same request N times in parallel (the `@concurrent:N` primitive) and bind aggregates —
|
|
151
|
+
* the idempotency/race oracle. Returns the full `responses` array plus `ok_count`, `status_counts`,
|
|
152
|
+
* and `statuses`, asserted with `expect {{name.ok_count}} is 1` (and cross-checked against the DB via
|
|
153
|
+
* `@query` to prove "exactly one charge"). Path access works on the bound value: `{{name.ok_count}}`,
|
|
154
|
+
* `{{name.status_counts.409}}`, `{{name.responses.count}}`, `{{name.responses[0].body.id}}`.
|
|
155
|
+
*/
|
|
156
|
+
async callN(label, req, params = {}, n = 1, opts = {}) {
|
|
157
|
+
const count = Math.max(1, Math.floor(n));
|
|
158
|
+
const responses = await Promise.all(Array.from({ length: count }, () => this.call(label, req, params, opts)));
|
|
159
|
+
const status_counts = {};
|
|
160
|
+
for (const r of responses)
|
|
161
|
+
status_counts[String(r.status)] = (status_counts[String(r.status)] || 0) + 1;
|
|
162
|
+
return {
|
|
163
|
+
responses,
|
|
164
|
+
ok_count: responses.filter((r) => r.ok).length,
|
|
165
|
+
status_counts,
|
|
166
|
+
statuses: responses.map((r) => r.status),
|
|
167
|
+
};
|
|
125
168
|
}
|
|
126
169
|
}
|
|
127
170
|
exports.api = new ApiClient();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"specs-api.js","sourceRoot":"","sources":["../../../src/orchestrator/templates/specs-api.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,oBAAoB;AACpB;;;;;;;;;;GAUG;AACH,uCAAyB;AACzB,2CAA6B;
|
|
1
|
+
{"version":3,"file":"specs-api.js","sourceRoot":"","sources":["../../../src/orchestrator/templates/specs-api.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,oBAAoB;AACpB;;;;;;;;;;GAUG;AACH,uCAAyB;AACzB,2CAA6B;AAC7B,2CAAmE;AAWnE,SAAS,SAAS;IAChB,KAAK,MAAM,IAAI,IAAI,CAAC,SAAS,EAAE,WAAW,OAAO,CAAC,GAAG,CAAC,UAAU,IAAI,EAAE,EAAE,CAAC,EAAE,CAAC;QAC1E,MAAM,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,IAAI,CAAC,CAAC;QACzC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,CAAC;YAC5C,KAAK,MAAM,IAAI,IAAI,EAAE,CAAC,YAAY,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;gBAC1D,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,8CAA8C,CAAC,CAAC;gBACrE,IAAI,CAAC,IAAI,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,SAAS;oBAAE,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,cAAc,EAAE,EAAE,CAAC,CAAC;YACjG,CAAC;QACH,CAAC;IACH,CAAC;AACH,CAAC;AAED,SAAS,UAAU;IACjB,SAAS,EAAE,CAAC;IACZ,MAAM,IAAI,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,kBAAkB,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,IAAI,EAAE,kBAAkB,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC;IAC9I,IAAI,CAAC,IAAI;QAAE,MAAM,IAAI,KAAK,CAAC,8DAA8D,CAAC,CAAC;IAC3F,MAAM,GAAG,GAAG,EAAE,CAAC,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,OAAO,CAAC,iCAAiC,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;IACrH,MAAM,EAAE,KAAK,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAClC,MAAM,GAAG,GAAG,KAAK,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC;IAC7B,OAAO,GAAG,CAAC,WAAW,IAAI,EAAE,CAAC;AAC/B,CAAC;AAED,SAAS,UAAU,CAAC,IAAY,EAAE,MAA2B;IAC3D,OAAO,IAAI,CAAC,OAAO,CAAC,4BAA4B,EAAE,CAAC,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC,kBAAkB,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;AAC5G,CAAC;AAED,MAAM,SAAS;IAAf;QACU,YAAO,GAAyC,IAAI,CAAC;IAmG/D,CAAC;IAjGS,GAAG,CAAC,IAAa;QACvB,IAAI,CAAC,IAAI,CAAC,OAAO;YAAE,IAAI,CAAC,OAAO,GAAG,UAAU,EAAE,CAAC;QAC/C,MAAM,GAAG,GAAG,IAAI,IAAI,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,OAAQ,CAAC,CAAC,CAAC,CAAC,IAAI,IAAI,KAAK,CAAC,KAAK,KAAK,CAAC,IAAI,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC;QACtI,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QAC/B,IAAI,CAAC,IAAI;YAAE,MAAM,IAAI,KAAK,CAAC,2BAA2B,GAAG,iCAAiC,CAAC,CAAC;QAC5F,IAAI,IAAI,CAAC,GAAG,KAAK,YAAY,IAAI,OAAO,CAAC,GAAG,CAAC,iBAAiB,KAAK,GAAG,EAAE,CAAC;YACvE,MAAM,IAAI,KAAK,CAAC,2BAA2B,GAAG,uEAAuE,CAAC,CAAC;QACzH,CAAC;QACD,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC;IACvB,CAAC;IAED;;;;;OAKG;IACH,KAAK,CAAC,IAAI,CACR,KAAa,EACb,GAAsJ,EACtJ,SAA8B,EAAE,EAChC,OAAkC,EAAE;QAEpC,MAAM,EAAE,IAAI,EAAE,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;QAC1C,MAAM,IAAI,GAAG,CAAC,IAAI,CAAC,QAAQ,IAAI,IAAI,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;QACtE,IAAI,CAAC,IAAI;YAAE,MAAM,IAAI,KAAK,CAAC,eAAe,KAAK,oDAAoD,CAAC,CAAC;QACrG,MAAM,OAAO,GAAG,UAAU,CAAC,GAAG,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAG,oCAAoC;QAEpF,MAAM,OAAO,GAA2B,EAAE,GAAG,CAAC,IAAI,CAAC,OAAO,IAAI,EAAE,CAAC,EAAE,CAAC;QACpE,0FAA0F;QAC1F,KAAK,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,OAAO,IAAI,EAAE,CAAC;YACpD,OAAO,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,4BAA4B,EAAE,CAAC,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;QACnG,gGAAgG;QAChG,IAAI,IAAS,CAAC;QACd,IAAI,GAAG,CAAC,IAAI,KAAK,SAAS,IAAI,GAAG,CAAC,IAAI,KAAK,IAAI,EAAE,CAAC;YAChD,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,8BAA8B,EAAE,CAAC,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC;QACpI,CAAC;QACD,4FAA4F;QAC5F,gGAAgG;QAChG,MAAM,OAAO,GAA4B,EAAE,CAAC;QAC5C,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;YACvB,MAAM,GAAG,GAAG,GAAG,CAAC,QAAQ,IAAI,MAAM,CAAC;YACnC,IAAI,GAAG,KAAK,MAAM;gBAAE,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC;iBACnC,IAAI,GAAG,KAAK,WAAW;gBAAE,OAAO,CAAC,SAAS,GAAG,IAAI,CAAC;;gBAClD,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC;QAC3B,CAAC;QAED,uFAAuF;QACvF,sFAAsF;QACtF,gGAAgG;QAChG,MAAM,GAAG,GAAsB,MAAM,cAAO,CAAC,UAAU,CAAC;YACtD,OAAO,EAAE,IAAI;YACb,gBAAgB,EAAE,OAAO;YACzB,OAAO,EAAE,IAAI,CAAC,UAAU,IAAI,KAAK;YACjC,GAAG,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC,CAAC,EAAE,YAAY,EAAE,IAAI,CAAC,YAAY,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SAClE,CAAC,CAAC;QACH,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,MAAM,GAAG,CAAC,KAAK,CAAC,OAAO,EAAE,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,GAAG,OAAO,EAAE,CAAC,CAAC;YACzE,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC;YAC9B,IAAI,MAAM,GAAQ,IAAI,CAAC;YACvB,IAAI,CAAC;gBAAC,MAAM,GAAG,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;YAAC,CAAC;YAAC,MAAM,CAAC,CAAC,0BAA0B,CAAC,CAAC;YACrF,OAAO,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,EAAE,EAAE,EAAE,GAAG,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,CAAC,OAAO,EAAE,EAAE,CAAC;QACtF,CAAC;gBAAS,CAAC;YACT,MAAM,GAAG,CAAC,OAAO,EAAE,CAAC;QACtB,CAAC;IACH,CAAC;IAED;;;;;;OAMG;IACH,KAAK,CAAC,KAAK,CACT,KAAa,EACb,GAAsJ,EACtJ,SAA8B,EAAE,EAChC,CAAC,GAAG,CAAC,EACL,OAAkC,EAAE;QAOpC,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;QACzC,MAAM,SAAS,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC;QAC9G,MAAM,aAAa,GAA2B,EAAE,CAAC;QACjD,KAAK,MAAM,CAAC,IAAI,SAAS;YAAE,aAAa,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC;QACxG,OAAO;YACL,SAAS;YACT,QAAQ,EAAE,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,MAAM;YAC9C,aAAa;YACb,QAAQ,EAAE,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC;SACzC,CAAC;IACJ,CAAC;CACF;AAEY,QAAA,GAAG,GAAG,IAAI,SAAS,EAAE,CAAC"}
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
*/
|
|
13
13
|
import * as fs from 'fs';
|
|
14
14
|
import * as path from 'path';
|
|
15
|
+
import { request, type APIRequestContext } from '@playwright/test';
|
|
15
16
|
|
|
16
17
|
interface ApiDataSource {
|
|
17
18
|
kind?: string;
|
|
@@ -62,39 +63,91 @@ class ApiClient {
|
|
|
62
63
|
return { key, conf };
|
|
63
64
|
}
|
|
64
65
|
|
|
65
|
-
/**
|
|
66
|
+
/**
|
|
67
|
+
* Run a catalog request and return the response. `req` is embedded at compile time; `params` (path
|
|
68
|
+
* `:id`, JSON body `:fields`, and header `:tokens`) bind at runtime. Catalog `headers` layer over the
|
|
69
|
+
* datasource headers and may carry `:param` placeholders — e.g. `authorization: "Bearer :token"` with
|
|
70
|
+
* the dynamic token threaded from a prior response (flow chaining).
|
|
71
|
+
*/
|
|
66
72
|
async call(
|
|
67
73
|
label: string,
|
|
68
|
-
req: { method: string; path: string; body?: unknown; datasource?: string },
|
|
74
|
+
req: { method: string; path: string; body?: unknown; encoding?: 'json' | 'form' | 'multipart'; headers?: Record<string, string>; datasource?: string },
|
|
69
75
|
params: Record<string, any> = {},
|
|
76
|
+
opts: { storageState?: string } = {},
|
|
70
77
|
): Promise<{ status: number; ok: boolean; body: any; headers: Record<string, string> }> {
|
|
71
78
|
const { conf } = this.cfg(req.datasource);
|
|
72
79
|
const base = (conf.base_url || conf.baseUrl || '').replace(/\/$/, '');
|
|
73
80
|
if (!base) throw new Error(`API Driver: ${label} — datasource has no base_url (set it in .env.qa).`);
|
|
74
|
-
const
|
|
81
|
+
const urlPath = substitute(req.path, params); // path params (:id) bind at runtime
|
|
75
82
|
|
|
76
|
-
let body: string | undefined;
|
|
77
83
|
const headers: Record<string, string> = { ...(conf.headers || {}) };
|
|
84
|
+
// catalog headers; :param tokens bind at runtime — raw (no URL-encoding, unlike the path)
|
|
85
|
+
for (const [k, v] of Object.entries(req.headers || {}))
|
|
86
|
+
headers[k] = String(v).replace(/:([A-Za-z_][A-Za-z0-9_]*)/g, (_m, p) => String(params[p] ?? ''));
|
|
87
|
+
// Body: substitute `:param` into the body template (object values), then encode per `encoding`.
|
|
88
|
+
let body: any;
|
|
78
89
|
if (req.body !== undefined && req.body !== null) {
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
90
|
+
body = JSON.parse(JSON.stringify(req.body).replace(/":([A-Za-z_][A-Za-z0-9_]*)"/g, (_m, p) => JSON.stringify(params[p] ?? null)));
|
|
91
|
+
}
|
|
92
|
+
// Map the wire format to the right Playwright option (#345): json → data (application/json,
|
|
93
|
+
// default), form → form (application/x-www-form-urlencoded), multipart → multipart (form-data).
|
|
94
|
+
const bodyOpt: Record<string, unknown> = {};
|
|
95
|
+
if (body !== undefined) {
|
|
96
|
+
const enc = req.encoding ?? 'json';
|
|
97
|
+
if (enc === 'form') bodyOpt.form = body;
|
|
98
|
+
else if (enc === 'multipart') bodyOpt.multipart = body;
|
|
99
|
+
else bodyOpt.data = body;
|
|
82
100
|
}
|
|
83
101
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
102
|
+
// Playwright APIRequestContext: same runner/report/retries as UI tests. @hybrid passes
|
|
103
|
+
// `storageState` (the @auth role's saved session) so the request shares the browser's
|
|
104
|
+
// authenticated cookies. Disposed per call so no request context lingers and hangs the process.
|
|
105
|
+
const ctx: APIRequestContext = await request.newContext({
|
|
106
|
+
baseURL: base,
|
|
107
|
+
extraHTTPHeaders: headers,
|
|
108
|
+
timeout: conf.timeout_ms ?? 15000,
|
|
109
|
+
...(opts.storageState ? { storageState: opts.storageState } : {}),
|
|
110
|
+
});
|
|
87
111
|
try {
|
|
88
|
-
res = await fetch(
|
|
112
|
+
const res = await ctx.fetch(urlPath, { method: req.method, ...bodyOpt });
|
|
113
|
+
const text = await res.text();
|
|
114
|
+
let parsed: any = text;
|
|
115
|
+
try { parsed = text ? JSON.parse(text) : null; } catch { /* non-JSON → keep text */ }
|
|
116
|
+
return { status: res.status(), ok: res.ok(), body: parsed, headers: res.headers() };
|
|
89
117
|
} finally {
|
|
90
|
-
|
|
118
|
+
await ctx.dispose();
|
|
91
119
|
}
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Fire the same request N times in parallel (the `@concurrent:N` primitive) and bind aggregates —
|
|
124
|
+
* the idempotency/race oracle. Returns the full `responses` array plus `ok_count`, `status_counts`,
|
|
125
|
+
* and `statuses`, asserted with `expect {{name.ok_count}} is 1` (and cross-checked against the DB via
|
|
126
|
+
* `@query` to prove "exactly one charge"). Path access works on the bound value: `{{name.ok_count}}`,
|
|
127
|
+
* `{{name.status_counts.409}}`, `{{name.responses.count}}`, `{{name.responses[0].body.id}}`.
|
|
128
|
+
*/
|
|
129
|
+
async callN(
|
|
130
|
+
label: string,
|
|
131
|
+
req: { method: string; path: string; body?: unknown; encoding?: 'json' | 'form' | 'multipart'; headers?: Record<string, string>; datasource?: string },
|
|
132
|
+
params: Record<string, any> = {},
|
|
133
|
+
n = 1,
|
|
134
|
+
opts: { storageState?: string } = {},
|
|
135
|
+
): Promise<{
|
|
136
|
+
responses: Array<{ status: number; ok: boolean; body: any; headers: Record<string, string> }>;
|
|
137
|
+
ok_count: number;
|
|
138
|
+
status_counts: Record<string, number>;
|
|
139
|
+
statuses: number[];
|
|
140
|
+
}> {
|
|
141
|
+
const count = Math.max(1, Math.floor(n));
|
|
142
|
+
const responses = await Promise.all(Array.from({ length: count }, () => this.call(label, req, params, opts)));
|
|
143
|
+
const status_counts: Record<string, number> = {};
|
|
144
|
+
for (const r of responses) status_counts[String(r.status)] = (status_counts[String(r.status)] || 0) + 1;
|
|
145
|
+
return {
|
|
146
|
+
responses,
|
|
147
|
+
ok_count: responses.filter((r) => r.ok).length,
|
|
148
|
+
status_counts,
|
|
149
|
+
statuses: responses.map((r) => r.status),
|
|
150
|
+
};
|
|
98
151
|
}
|
|
99
152
|
}
|
|
100
153
|
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
declare class DataSource {
|
|
2
2
|
private configs;
|
|
3
3
|
private engines;
|
|
4
|
+
private tunnels;
|
|
4
5
|
private cfg;
|
|
5
6
|
private engine;
|
|
7
|
+
/** Close any open SSH tunnels (optional explicit teardown; tunnels are unref'd so the process exits regardless). */
|
|
8
|
+
close(): void;
|
|
6
9
|
private build;
|
|
7
10
|
/** A row matching `filter` must exist; if `expected` given, assert those columns on the first match. */
|
|
8
11
|
assertRow(table: string, filter: Record<string, any>, expected?: Record<string, any>, datasource?: string): Promise<void>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"specs-db.d.ts","sourceRoot":"","sources":["../../../src/orchestrator/templates/specs-db.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"specs-db.d.ts","sourceRoot":"","sources":["../../../src/orchestrator/templates/specs-db.ts"],"names":[],"mappings":"AA4HA,cAAM,UAAU;IACd,OAAO,CAAC,OAAO,CAAiD;IAChE,OAAO,CAAC,OAAO,CAA6B;IAC5C,OAAO,CAAC,OAAO,CAAoC;IAEnD,OAAO,CAAC,GAAG;YAQG,MAAM;IA6BpB,oHAAoH;IACpH,KAAK,IAAI,IAAI;IAKb,OAAO,CAAC,KAAK;IAOb,wGAAwG;IAClG,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAc/H,0CAA0C;IACpC,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAOjG,gDAAgD;IAC1C,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAUhH,qDAAqD;IACrD,OAAO,CAAC,MAAM;IAKd,4FAA4F;IAC5F,OAAO,CAAC,gBAAgB;IASxB;;;;OAIG;IACG,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC;CAKjG;AAMD,eAAO,MAAM,EAAE,YAAmB,CAAC"}
|
|
@@ -56,6 +56,65 @@ const ident = (s) => {
|
|
|
56
56
|
throw new Error(`Unsafe identifier: ${JSON.stringify(s)} (allowed: [A-Za-z_][A-Za-z0-9_]*)`);
|
|
57
57
|
return s;
|
|
58
58
|
};
|
|
59
|
+
/**
|
|
60
|
+
* Open a local TCP forward (127.0.0.1:<ephemeral> → ssh bastion → dstHost:dstPort) for a DB socket.
|
|
61
|
+
* Sockets are unref()'d so a dangling tunnel never keeps the test process alive after the run.
|
|
62
|
+
*/
|
|
63
|
+
async function openSshTunnel(ssh, dstHost, dstPort) {
|
|
64
|
+
const { Client } = require('ssh2');
|
|
65
|
+
const net = require('net');
|
|
66
|
+
const privateKey = ssh.private_key
|
|
67
|
+
? ssh.private_key
|
|
68
|
+
: ssh.private_key_path
|
|
69
|
+
? fs.readFileSync(ssh.private_key_path.replace(/^~(?=\/)/, process.env.HOME || ''), 'utf8')
|
|
70
|
+
: undefined;
|
|
71
|
+
if (!privateKey)
|
|
72
|
+
throw new Error('Data Driver: datasource `ssh` requires `private_key` or `private_key_path`.');
|
|
73
|
+
const conn = new Client();
|
|
74
|
+
await new Promise((resolve, reject) => {
|
|
75
|
+
conn.on('ready', resolve).on('error', reject).connect({
|
|
76
|
+
host: ssh.host,
|
|
77
|
+
port: ssh.port ?? 22,
|
|
78
|
+
username: ssh.user,
|
|
79
|
+
privateKey,
|
|
80
|
+
passphrase: ssh.passphrase,
|
|
81
|
+
hostVerifier: (key) => {
|
|
82
|
+
const got = Buffer.isBuffer(key) ? key.toString('base64') : String(key);
|
|
83
|
+
if (ssh.known_host) {
|
|
84
|
+
if (got === ssh.known_host.trim())
|
|
85
|
+
return true;
|
|
86
|
+
throw new Error(`Data Driver: SSH host-key mismatch for ${ssh.host} — refused (known_host pin).`);
|
|
87
|
+
}
|
|
88
|
+
console.warn(`Data Driver: SSH host key for ${ssh.host} is not pinned (set datasource ssh.known_host to verify). Proceeding (TOFU).`);
|
|
89
|
+
return true;
|
|
90
|
+
},
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
const server = net.createServer((sock) => {
|
|
94
|
+
conn.forwardOut(sock.remoteAddress || '127.0.0.1', sock.remotePort || 0, dstHost, dstPort, (err, stream) => {
|
|
95
|
+
if (err) {
|
|
96
|
+
sock.destroy();
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
sock.pipe(stream).pipe(sock);
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
await new Promise((resolve, reject) => server.on('error', reject).listen(0, '127.0.0.1', () => resolve()));
|
|
103
|
+
const addr = server.address();
|
|
104
|
+
const port = addr && typeof addr === 'object' ? addr.port : 0;
|
|
105
|
+
server.unref(); // don't keep the event loop alive after tests
|
|
106
|
+
try {
|
|
107
|
+
conn._sock?.unref?.();
|
|
108
|
+
}
|
|
109
|
+
catch { /* best-effort */ }
|
|
110
|
+
return { host: '127.0.0.1', port, close: () => { try {
|
|
111
|
+
server.close();
|
|
112
|
+
}
|
|
113
|
+
catch { } try {
|
|
114
|
+
conn.end();
|
|
115
|
+
}
|
|
116
|
+
catch { } } };
|
|
117
|
+
}
|
|
59
118
|
function loadEnvQa() {
|
|
60
119
|
for (const name of ['.env.qa', `.env.qa.${process.env.SUNGEN_ENV || ''}`]) {
|
|
61
120
|
const p = path.join(process.cwd(), name);
|
|
@@ -89,6 +148,7 @@ class DataSource {
|
|
|
89
148
|
constructor() {
|
|
90
149
|
this.configs = null;
|
|
91
150
|
this.engines = new Map();
|
|
151
|
+
this.tunnels = [];
|
|
92
152
|
}
|
|
93
153
|
cfg(name) {
|
|
94
154
|
if (!this.configs)
|
|
@@ -107,11 +167,22 @@ class DataSource {
|
|
|
107
167
|
throw new Error(`Data Driver: datasource "${key}" has no url (set it in .env.qa).`);
|
|
108
168
|
let engine;
|
|
109
169
|
if (conf.engine === 'postgres') {
|
|
170
|
+
let connectionString = conf.url;
|
|
171
|
+
if (conf.ssh) { // Cách B: tunnel the DB socket through a bastion
|
|
172
|
+
const u = new URL(conf.url);
|
|
173
|
+
const t = await openSshTunnel(conf.ssh, u.hostname, Number(u.port || 5432));
|
|
174
|
+
this.tunnels.push(t);
|
|
175
|
+
u.hostname = t.host;
|
|
176
|
+
u.port = String(t.port); // rewrite host:port → 127.0.0.1:<tunnel> (keep user/pass/db/query)
|
|
177
|
+
connectionString = u.toString();
|
|
178
|
+
}
|
|
110
179
|
const { Pool } = require('pg');
|
|
111
|
-
const pool = new Pool({ connectionString
|
|
180
|
+
const pool = new Pool({ connectionString, max: 2, statement_timeout: conf.statement_timeout_ms ?? 4000 });
|
|
112
181
|
engine = { query: async (sql, params) => (await pool.query(sql, params)).rows };
|
|
113
182
|
}
|
|
114
183
|
else if (conf.engine === 'sqlite') {
|
|
184
|
+
if (conf.ssh)
|
|
185
|
+
console.warn(`Data Driver: datasource "${key}" sets ssh: but engine is sqlite (file-based) — ssh ignored.`);
|
|
115
186
|
const Database = require('better-sqlite3');
|
|
116
187
|
const db = new Database(conf.url.replace(/^sqlite:/, ''), { readonly: conf.readonly !== false });
|
|
117
188
|
engine = { query: async (sql, params) => db.prepare(sql).all(...params) };
|
|
@@ -122,6 +193,12 @@ class DataSource {
|
|
|
122
193
|
this.engines.set(key, engine);
|
|
123
194
|
return { engine, conf };
|
|
124
195
|
}
|
|
196
|
+
/** Close any open SSH tunnels (optional explicit teardown; tunnels are unref'd so the process exits regardless). */
|
|
197
|
+
close() {
|
|
198
|
+
for (const t of this.tunnels)
|
|
199
|
+
t.close();
|
|
200
|
+
this.tunnels = [];
|
|
201
|
+
}
|
|
125
202
|
build(table, filter) {
|
|
126
203
|
const cols = Object.keys(filter);
|
|
127
204
|
const where = cols.map((c, i) => `${ident(c)} = $${i + 1}`).join(' AND ');
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"specs-db.js","sourceRoot":"","sources":["../../../src/orchestrator/templates/specs-db.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"specs-db.js","sourceRoot":"","sources":["../../../src/orchestrator/templates/specs-db.ts"],"names":[],"mappings":"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"}
|
|
@@ -21,12 +21,73 @@ const ident = (s: string): string => {
|
|
|
21
21
|
return s;
|
|
22
22
|
};
|
|
23
23
|
|
|
24
|
+
interface SshConfig {
|
|
25
|
+
host: string; // jump host reachable from the runner
|
|
26
|
+
port?: number; // default 22
|
|
27
|
+
user: string;
|
|
28
|
+
private_key?: string; // PEM contents (from ${VAR} in .env.qa) — preferred for CI
|
|
29
|
+
private_key_path?: string; // or a filesystem path (local dev)
|
|
30
|
+
passphrase?: string; // for an encrypted key
|
|
31
|
+
known_host?: string; // base64 of the server's host key to pin (optional; else warn-and-proceed)
|
|
32
|
+
}
|
|
33
|
+
|
|
24
34
|
interface DataSourceConfig {
|
|
25
35
|
engine: 'postgres' | 'mysql' | 'sqlite';
|
|
26
36
|
url: string;
|
|
27
37
|
readonly?: boolean;
|
|
28
38
|
statement_timeout_ms?: number;
|
|
29
39
|
max_rows?: number;
|
|
40
|
+
// Cách B (fallback): tunnel the DB SOCKET through an SSH bastion. DB-only — the browser/E2E
|
|
41
|
+
// still run on the runner; only PG traffic crosses. See docs/spec/sungen_data_driver_ssh_tunnel_spec.md.
|
|
42
|
+
ssh?: SshConfig;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Open a local TCP forward (127.0.0.1:<ephemeral> → ssh bastion → dstHost:dstPort) for a DB socket.
|
|
47
|
+
* Sockets are unref()'d so a dangling tunnel never keeps the test process alive after the run.
|
|
48
|
+
*/
|
|
49
|
+
async function openSshTunnel(ssh: SshConfig, dstHost: string, dstPort: number): Promise<{ host: string; port: number; close: () => void }> {
|
|
50
|
+
const { Client } = require('ssh2');
|
|
51
|
+
const net = require('net');
|
|
52
|
+
const privateKey = ssh.private_key
|
|
53
|
+
? ssh.private_key
|
|
54
|
+
: ssh.private_key_path
|
|
55
|
+
? fs.readFileSync(ssh.private_key_path.replace(/^~(?=\/)/, process.env.HOME || ''), 'utf8')
|
|
56
|
+
: undefined;
|
|
57
|
+
if (!privateKey) throw new Error('Data Driver: datasource `ssh` requires `private_key` or `private_key_path`.');
|
|
58
|
+
|
|
59
|
+
const conn = new Client();
|
|
60
|
+
await new Promise<void>((resolve, reject) => {
|
|
61
|
+
conn.on('ready', resolve).on('error', reject).connect({
|
|
62
|
+
host: ssh.host,
|
|
63
|
+
port: ssh.port ?? 22,
|
|
64
|
+
username: ssh.user,
|
|
65
|
+
privateKey,
|
|
66
|
+
passphrase: ssh.passphrase,
|
|
67
|
+
hostVerifier: (key: Buffer) => {
|
|
68
|
+
const got = Buffer.isBuffer(key) ? key.toString('base64') : String(key);
|
|
69
|
+
if (ssh.known_host) {
|
|
70
|
+
if (got === ssh.known_host.trim()) return true;
|
|
71
|
+
throw new Error(`Data Driver: SSH host-key mismatch for ${ssh.host} — refused (known_host pin).`);
|
|
72
|
+
}
|
|
73
|
+
console.warn(`Data Driver: SSH host key for ${ssh.host} is not pinned (set datasource ssh.known_host to verify). Proceeding (TOFU).`);
|
|
74
|
+
return true;
|
|
75
|
+
},
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
const server = net.createServer((sock: any) => {
|
|
80
|
+
conn.forwardOut(sock.remoteAddress || '127.0.0.1', sock.remotePort || 0, dstHost, dstPort, (err: any, stream: any) => {
|
|
81
|
+
if (err) { sock.destroy(); return; }
|
|
82
|
+
sock.pipe(stream).pipe(sock);
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
await new Promise<void>((resolve, reject) => server.on('error', reject).listen(0, '127.0.0.1', () => resolve()));
|
|
86
|
+
const addr = server.address();
|
|
87
|
+
const port = addr && typeof addr === 'object' ? addr.port : 0;
|
|
88
|
+
server.unref(); // don't keep the event loop alive after tests
|
|
89
|
+
try { (conn as any)._sock?.unref?.(); } catch { /* best-effort */ }
|
|
90
|
+
return { host: '127.0.0.1', port, close: () => { try { server.close(); } catch {} try { conn.end(); } catch {} } };
|
|
30
91
|
}
|
|
31
92
|
|
|
32
93
|
function loadEnvQa(): void {
|
|
@@ -64,6 +125,7 @@ type Engine = { query(sql: string, params: any[]): Promise<any[]>; };
|
|
|
64
125
|
class DataSource {
|
|
65
126
|
private configs: Record<string, DataSourceConfig> | null = null;
|
|
66
127
|
private engines = new Map<string, Engine>();
|
|
128
|
+
private tunnels: Array<{ close: () => void }> = [];
|
|
67
129
|
|
|
68
130
|
private cfg(name?: string): { key: string; conf: DataSourceConfig } {
|
|
69
131
|
if (!this.configs) this.configs = loadConfig();
|
|
@@ -79,10 +141,19 @@ class DataSource {
|
|
|
79
141
|
if (!conf.url) throw new Error(`Data Driver: datasource "${key}" has no url (set it in .env.qa).`);
|
|
80
142
|
let engine: Engine;
|
|
81
143
|
if (conf.engine === 'postgres') {
|
|
144
|
+
let connectionString = conf.url;
|
|
145
|
+
if (conf.ssh) { // Cách B: tunnel the DB socket through a bastion
|
|
146
|
+
const u = new URL(conf.url);
|
|
147
|
+
const t = await openSshTunnel(conf.ssh, u.hostname, Number(u.port || 5432));
|
|
148
|
+
this.tunnels.push(t);
|
|
149
|
+
u.hostname = t.host; u.port = String(t.port); // rewrite host:port → 127.0.0.1:<tunnel> (keep user/pass/db/query)
|
|
150
|
+
connectionString = u.toString();
|
|
151
|
+
}
|
|
82
152
|
const { Pool } = require('pg');
|
|
83
|
-
const pool = new Pool({ connectionString
|
|
153
|
+
const pool = new Pool({ connectionString, max: 2, statement_timeout: conf.statement_timeout_ms ?? 4000 });
|
|
84
154
|
engine = { query: async (sql, params) => (await pool.query(sql, params)).rows };
|
|
85
155
|
} else if (conf.engine === 'sqlite') {
|
|
156
|
+
if (conf.ssh) console.warn(`Data Driver: datasource "${key}" sets ssh: but engine is sqlite (file-based) — ssh ignored.`);
|
|
86
157
|
const Database = require('better-sqlite3');
|
|
87
158
|
const db = new Database(conf.url.replace(/^sqlite:/, ''), { readonly: conf.readonly !== false });
|
|
88
159
|
engine = { query: async (sql, params) => db.prepare(sql).all(...params) };
|
|
@@ -93,6 +164,12 @@ class DataSource {
|
|
|
93
164
|
return { engine, conf };
|
|
94
165
|
}
|
|
95
166
|
|
|
167
|
+
/** Close any open SSH tunnels (optional explicit teardown; tunnels are unref'd so the process exits regardless). */
|
|
168
|
+
close(): void {
|
|
169
|
+
for (const t of this.tunnels) t.close();
|
|
170
|
+
this.tunnels = [];
|
|
171
|
+
}
|
|
172
|
+
|
|
96
173
|
private build(table: string, filter: Record<string, any>): { sql: string; params: any[] } {
|
|
97
174
|
const cols = Object.keys(filter);
|
|
98
175
|
const where = cols.map((c, i) => `${ident(c)} = $${i + 1}`).join(' AND ');
|
|
@@ -23,7 +23,8 @@ export class TestDataLoader {
|
|
|
23
23
|
*/
|
|
24
24
|
static load(screenName: string, featureName: string): TestDataLoader {
|
|
25
25
|
let baseDir: string;
|
|
26
|
-
if (screenName.startsWith('flows/')) {
|
|
26
|
+
if (screenName.startsWith('flows/') || screenName.startsWith('api/')) {
|
|
27
|
+
// flows/<flow> · api/<area> · api/flows/<flow> → qa/<screenName>/test-data
|
|
27
28
|
baseDir = path.join(process.cwd(), 'qa', screenName, 'test-data');
|
|
28
29
|
} else {
|
|
29
30
|
baseDir = path.join(process.cwd(), 'qa', 'screens', screenName, 'test-data');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sun-asterisk/sungen",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.2.0-beta.141",
|
|
4
4
|
"description": "Deterministic E2E Test Compiler - Gherkin + Selectors → Playwright tests",
|
|
5
5
|
"main": "src/index.ts",
|
|
6
6
|
"types": "src/index.ts",
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"node": ">=18.0.0"
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
|
-
"@sungen/driver-ui": "3.
|
|
36
|
+
"@sungen/driver-ui": "3.2.0-beta.141",
|
|
37
37
|
"@anthropic-ai/sdk": "^0.71.0",
|
|
38
38
|
"@babel/parser": "^7.28.5",
|
|
39
39
|
"@babel/traverse": "^7.28.5",
|
|
@@ -44,3 +44,22 @@ export interface ContextMapper {
|
|
|
44
44
|
/** Turn the Context into generation units (+ modes), honouring the kernel's band targets. */
|
|
45
45
|
decompose(ctx: Context, bands?: Record<string, number>): GenerationUnit[];
|
|
46
46
|
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Repair phase hook — a capability's deterministic fix catalog. Each rule matches a finding (audit
|
|
50
|
+
* gap) or a runtime failure message and proposes a concrete fix. `sungen repair` gathers the
|
|
51
|
+
* unit-capability's rules and turns the findings/failures into an actionable plan; the AI repair
|
|
52
|
+
* loop and a human get the same proposals. Drivers/projects extend the catalog by adding rules.
|
|
53
|
+
*/
|
|
54
|
+
export interface RepairRule {
|
|
55
|
+
/** Stable id, e.g. 'api-error' | 'api-auth'. */
|
|
56
|
+
id: string;
|
|
57
|
+
/** Matches a finding or failure message. */
|
|
58
|
+
match: RegExp;
|
|
59
|
+
/** The concrete repair instruction to apply. */
|
|
60
|
+
fix: string;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface RepairProvider {
|
|
64
|
+
rules: RepairRule[];
|
|
65
|
+
}
|