apply-ddl.test.mjs 4.67 KB
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))
})