// lib/setup-test-db-template.test.mjs — 校验生成模板 scripts/setup-test-db.mjs 的最小 DB reset 逻辑。 // 跑的是真实模板产物:复制到临时 scripts/ 下、写一个 ../config-vars.yaml、再 node 执行。 // 所有用例的 host/port 故意指向 127.0.0.1:1(必拒连),不会触碰真实库。 import { test } from 'node:test' import assert from 'node:assert/strict' import { spawnSync } from 'node:child_process' import { mkdtempSync, mkdirSync, copyFileSync, writeFileSync } from 'node:fs' import { tmpdir } from 'node:os' import { join } from 'node:path' import { fileURLToPath } from 'node:url' const TEMPLATE = fileURLToPath(new URL('../skills/plan/skeleton-gen/templates/scripts-setup-test-db-template.mjs', import.meta.url)) function runWithSchema(schemaLine) { return runWithDb({ schemaLine }) } // env:传给子进程的增量环境变量;laneMarker:非 null 时写 .tmp/erp-lane-db 兜底标记。 // 基础环境强制剔除 ERP_TEST_DB_SCHEMA——外层 shell 若恰好设了该变量,"缺省行为"用例会被污染。 function runWithDb({ host = '127.0.0.1', port = '1', user = 'root', password = 'x', schemaLine = 'schema: erp_dev', env = {}, laneMarker = null }) { const dir = mkdtempSync(join(tmpdir(), 'erp-stdb-')) mkdirSync(join(dir, 'scripts')) copyFileSync(TEMPLATE, join(dir, 'scripts', 'setup-test-db.mjs')) writeFileSync( join(dir, 'config-vars.yaml'), ['database:', ` host: ${host}`, ` port: ${port}`, ` user: ${user}`, ` password: ${password}`, ' ' + schemaLine, ''].join('\n'), ) if (laneMarker !== null) { mkdirSync(join(dir, '.tmp')) writeFileSync(join(dir, '.tmp', 'erp-lane-db'), laneMarker) } const childEnv = { ...process.env, ...env } if (!('ERP_TEST_DB_SCHEMA' in env)) delete childEnv.ERP_TEST_DB_SCHEMA return spawnSync('node', [join(dir, 'scripts', 'setup-test-db.mjs')], { encoding: 'utf8', env: childEnv }) } test('setup-test-db: empty schema fails before mysql is invoked', () => { const r = runWithSchema('schema:') assert.equal(r.status, 1) assert.match(r.stderr, /database\.schema/, '应是 schema 缺失报错而非连库失败 — stderr: ' + r.stderr) }) test('setup-test-db: unfilled 【人工填写】 config placeholders fail before mysql is invoked', () => { for (const cfg of [ { host: '【人工填写:MySQL host】' }, { port: '【人工填写:MySQL port】' }, { user: '【人工填写:账号】' }, { password: '【人工填写:密码】' }, { schemaLine: 'schema: 【人工填写:schema 名】' }, ]) { const r = runWithDb(cfg) assert.equal(r.status, 1) assert.match(r.stderr, /仍是占位/, 'stderr: ' + r.stderr) } }) test('setup-test-db: schema with a backtick is quoted for MySQL instead of rejected', () => { const r = runWithSchema('schema: ev`il') assert.match(r.stdout, /`ev``il`/, 'stdout: ' + r.stdout) assert.doesNotMatch(r.stderr, /database\.schema/, 'stderr: ' + r.stderr) }) test('setup-test-db: an ordinary dev schema proceeds to mysql', () => { const r = runWithSchema('schema: erp_dev') assert.match(r.stdout, /DROP \+ CREATE `erp_dev`/, 'stdout: ' + r.stdout) }) test('setup-test-db: non-test names are allowed in sandbox/dev workflows', () => { const r = runWithSchema('schema: erp_prod') assert.match(r.stdout, /DROP \+ CREATE `erp_prod`/, 'stdout: ' + r.stdout) }) // ── 测试 DB lane 化:取值顺序 env > .tmp/erp-lane-db marker > config ── test('setup-test-db: ERP_TEST_DB_SCHEMA env overrides config schema', () => { const r = runWithDb({ env: { ERP_TEST_DB_SCHEMA: 'erp_dev_lane2' } }) assert.match(r.stdout, /DROP \+ CREATE `erp_dev_lane2`/, 'stdout: ' + r.stdout) }) test('setup-test-db: empty ERP_TEST_DB_SCHEMA env falls through to config schema', () => { const r = runWithDb({ env: { ERP_TEST_DB_SCHEMA: '' } }) assert.match(r.stdout, /DROP \+ CREATE `erp_dev`/, 'stdout: ' + r.stdout) }) test('setup-test-db: .tmp/erp-lane-db marker overrides config when env is absent, with audit log', () => { const r = runWithDb({ laneMarker: 'erp_dev_lane3\n' }) assert.match(r.stdout, /lane DB marker 生效: erp_dev_lane3/, 'stdout: ' + r.stdout) assert.match(r.stdout, /DROP \+ CREATE `erp_dev_lane3`/, 'stdout: ' + r.stdout) }) test('setup-test-db: env wins over marker (marker log absent)', () => { const r = runWithDb({ env: { ERP_TEST_DB_SCHEMA: 'erp_dev_lane2' }, laneMarker: 'erp_dev_lane3\n' }) assert.match(r.stdout, /DROP \+ CREATE `erp_dev_lane2`/, 'stdout: ' + r.stdout) assert.doesNotMatch(r.stdout, /lane DB marker 生效/, 'stdout: ' + r.stdout) }) test('setup-test-db: blank marker falls through to config schema', () => { const r = runWithDb({ laneMarker: '\n' }) assert.match(r.stdout, /DROP \+ CREATE `erp_dev`/, 'stdout: ' + r.stdout) assert.doesNotMatch(r.stdout, /lane DB marker 生效/, 'stdout: ' + r.stdout) })