setup-test-db-template.test.mjs 4.01 KB
// lib/setup-test-db-template.test.mjs — 校验生成模板 scripts/setup-test-db.mjs 的 schema 守卫。
// 跑的是真实模板产物:复制到临时 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, env = {}) {
  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: 127.0.0.1', '  port: 1', '  user: root', '  password: x', '  ' + schemaLine, ''].join('\n'),
  )
  return spawnSync('node', [join(dir, 'scripts', 'setup-test-db.mjs')], { encoding: 'utf8', env: { ...process.env, ...env } })
}

// ROBUST-3:空 schema 不应进到 DROP DATABASE `` —— 守卫应先拦下。
test('setup-test-db: empty schema fails closed with a schema message (ROBUST-3)', () => {
  const r = runWithSchema('schema:')
  assert.equal(r.status, 1)
  assert.match(r.stderr, /schema/, '应是 schema 守卫报错而非连库失败 — stderr: ' + r.stderr)
})

// ROBUST-3:未填的 【人工填写】 占位不应被当库名。
test('setup-test-db: 【人工填写】 placeholder schema fails closed (ROBUST-3)', () => {
  const r = runWithSchema('schema: 【人工填写:schema 名】')
  assert.equal(r.status, 1)
  assert.match(r.stderr, /schema/, 'stderr: ' + r.stderr)
})

// DDL-8:含反引号的 schema(标识符注入)应被拒,而不是拼进 DROP/CREATE 语句。
test('setup-test-db: schema with a backtick is rejected (DDL-8 injection guard)', () => {
  const r = runWithSchema('schema: ev`il')
  assert.equal(r.status, 1)
  assert.match(r.stderr, /schema/, 'stderr: ' + r.stderr)
})

// 正例:合法标识符 schema 应通过守卫并继续到连库阶段(此处连 127.0.0.1:1 必失败,
// 但 stderr 应是连库/mysql 错误,而非 schema 守卫错误)——证明守卫不误伤合法名。
test('setup-test-db: a valid identifier schema passes the guard (no false positive)', () => {
  const r = runWithSchema('schema: erp_dev')
  // 连不上 127.0.0.1:1 → 非零退出;关键是错误不来自 schema 守卫。
  assert.doesNotMatch(r.stderr, /database\.schema 非法|schema 非法或未填/, 'stderr: ' + r.stderr)
})

// 前置依赖 D(安全):测试库命名护栏——非测试库名默认 fail-closed,防误删开发/生产库。
test('setup-test-db: a non-test schema fails closed by default (D non-test guard)', () => {
  const r = runWithSchema('schema: erp_prod')
  assert.equal(r.status, 1)
  assert.match(r.stderr, /不像测试库|ALLOW_NONTEST_DROP/, '应是测试库命名护栏报错 — stderr: ' + r.stderr)
})

// D:测试库名(含 test / _test / _dev / _local)应通过命名护栏,错误不来自该护栏。
test('setup-test-db: test-like schema names pass the naming guard (erp_test / erp_dev)', () => {
  for (const name of ['erp_test', 'erp_dev', 'erp_local', 'test_db']) {
    const r = runWithSchema('schema: ' + name)
    assert.doesNotMatch(r.stderr, /不像测试库/, `${name} 不应被命名护栏拒绝 — stderr: ` + r.stderr)
  }
})

// D:ALLOW_NONTEST_DROP=1 显式放行非测试库名(错误不再来自命名护栏)。
test('setup-test-db: ALLOW_NONTEST_DROP=1 explicitly bypasses the naming guard', () => {
  const r = runWithSchema('schema: erp_prod', { ALLOW_NONTEST_DROP: '1' })
  assert.doesNotMatch(r.stderr, /不像测试库/, '显式放行后不应再被命名护栏拒绝 — stderr: ' + r.stderr)
})