import { test } from 'node:test' import assert from 'node:assert/strict' import { spawnSync } from 'node:child_process' import { fileURLToPath } from 'node:url' import { mkdtempSync, mkdirSync, writeFileSync } from 'node:fs' import { tmpdir } from 'node:os' import { join } from 'node:path' import { resolveDbConfig, resolveMysql2Path } from './apply-ddl.mjs' const APPLY = fileURLToPath(new URL('./apply-ddl.mjs', import.meta.url)) // ── resolveDbConfig(直接读 config-vars.yaml 解析后的 database: 段)───────── test('resolveDbConfig maps the database section to mysql2 settings', () => { const c = resolveDbConfig({ database: { host: 'db.local', port: '3307', user: 'u', password: 'p@ss', schema: 'erp_test' }, }) assert.equal(c.host, 'db.local') assert.equal(c.port, 3307) assert.equal(c.user, 'u') assert.equal(c.password, 'p@ss') assert.equal(c.database, 'erp_test') }) test('resolveDbConfig fails closed when database.schema is missing (M1)', () => { assert.throws(() => resolveDbConfig({ database: { user: 'root' } }, 'config-vars.yaml'), /database\.schema/) assert.throws(() => resolveDbConfig({}), /database\.schema/) }) test('resolveDbConfig applies sane defaults for host/port/user/password', () => { const c = resolveDbConfig({ database: { schema: 's' } }) assert.equal(c.host, '127.0.0.1') assert.equal(c.port, 3306) assert.equal(c.user, 'root') assert.equal(c.password, '') }) test('resolveDbConfig rejects invalid ports', () => { assert.throws(() => resolveDbConfig({ database: { schema: 'erp_test', port: 'abc' } }), /database\.port/) assert.throws(() => resolveDbConfig({ database: { schema: 'erp_test', port: '70000' } }), /database\.port/) }) test('resolveDbConfig rejects unfilled 【人工填写】 placeholders as incomplete config', () => { const base = { host: 'h', port: '3306', user: 'u', password: 'p', schema: 's' } assert.throws(() => resolveDbConfig({ database: { ...base, schema: '【人工填写:schema 名】' } }), /仍是占位/) assert.throws(() => resolveDbConfig({ database: { ...base, host: '【人工填写:MySQL host】' } }), /仍是占位/) assert.throws(() => resolveDbConfig({ database: { ...base, user: '【人工填写:账号】' } }), /仍是占位/) assert.throws(() => resolveDbConfig({ database: { ...base, password: '【人工填写:密码】' } }), /仍是占位/) }) test('resolveDbConfig allows an explicit empty database.password', () => { const c = resolveDbConfig({ database: { host: '127.0.0.1', port: '3306', user: 'root', password: '', schema: 'erp_dev' } }) assert.equal(c.password, '') assert.equal(c.database, 'erp_dev') }) // ── DDL-6:缺文件应退出码 2(用法/路径错),与 db-init C.2 文档及 validate-ddl 对齐 ── test('apply-ddl CLI exits 2 when the config file does not exist (DDL-6)', () => { const r = spawnSync('node', [APPLY, '/no/such/config-vars.yaml', '/no/such/V1.sql'], { encoding: 'utf8' }) assert.equal(r.status, 2, 'missing config file should be exit 2, not 1 — stderr: ' + r.stderr) }) test('apply-ddl CLI exits 2 when the DDL file does not exist (DDL-6)', () => { // config exists (this very test file stands in as an existing path), DDL does not const r = spawnSync('node', [APPLY, APPLY, '/no/such/V1.sql'], { encoding: 'utf8' }) assert.equal(r.status, 2, 'missing DDL file should be exit 2 — stderr: ' + r.stderr) }) test('apply-ddl CLI exits 2 on missing arguments (existing behavior preserved)', () => { const r = spawnSync('node', [APPLY], { encoding: 'utf8' }) assert.equal(r.status, 2) }) // ── H2:mysql2 必须从「目标项目」解析,而非插件自身目录 ──────────────────── // ESM 裸说明符按 importer 解析;apply-ddl.mjs 住在插件目录,若直接 import('mysql2/promise') // 则永远看不到目标项目 `npm i mysql2` 的安装。resolveMysql2Path 用 createRequire(目标根) 修正。 test('resolveMysql2Path resolves mysql2/promise from the target project dir (H2)', () => { const dir = mkdtempSync(join(tmpdir(), 'erp-m2-')) mkdirSync(join(dir, 'node_modules', 'mysql2'), { recursive: true }) writeFileSync(join(dir, 'node_modules', 'mysql2', 'package.json'), JSON.stringify({ name: 'mysql2', version: '0.0.0', exports: { './promise': './promise.js' } })) writeFileSync(join(dir, 'node_modules', 'mysql2', 'promise.js'), 'export default {}') const p = resolveMysql2Path(dir) assert.match(p, /node_modules[\\/]mysql2[\\/]promise\.js$/, 'got: ' + p) }) test('resolveMysql2Path throws when mysql2 is absent in the target project (H2)', () => { const dir = mkdtempSync(join(tmpdir(), 'erp-m2-empty-')) assert.throws(() => resolveMysql2Path(dir)) })