test-template.test.mjs 4.26 KB
// lib/test-template.test.mjs — 校验生成模板 scripts/test.mjs 的测试 DB lane 化贯通逻辑。
// D5 的要害是 env 必须真正打穿到后端测试子进程(gradle fork / e2e globalSetup→bootRun 均靠继承 env,
// 由 application*.yml 的 ${ERP_TEST_DB_SCHEMA:...} 占位消费)——故用探针命令替换 {{...}} 占位
// (模拟 skeleton-gen 的 Plan 期填充),直接断言子进程眼中的 ERP_TEST_DB_SCHEMA。
import { test } from 'node:test'
import assert from 'node:assert/strict'
import { spawnSync } from 'node:child_process'
import { mkdtempSync, mkdirSync, readFileSync, 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-test-template.mjs', import.meta.url))

// 探针脚本:以子进程身份打印自己继承到的 ERP_TEST_DB_SCHEMA(unset 显式可断言)。
// 占位填充值不能带引号(模板里占位嵌在 JS 单引号字符串内),故用脚本文件而非内联 node -e。
const PROBE_SOURCE = `console.log('PROBE_SCHEMA=' + (process.env.ERP_TEST_DB_SCHEMA ?? '(unset)'))\n`

// env:传给闸门进程的增量环境变量;laneMarker:非 null 时写 .tmp/erp-lane-db 兜底标记。
// 基础环境强制剔除 ERP_TEST_DB_SCHEMA——外层 shell 若恰好设了该变量,"缺省行为"用例会被污染。
function runGate({ env = {}, laneMarker = null } = {}) {
  const dir = mkdtempSync(join(tmpdir(), 'erp-gate-'))
  mkdirSync(join(dir, 'scripts'))
  mkdirSync(join(dir, 'backend'))
  // backend_* 在 backend/ 子目录下执行、e2e 在仓库根执行,相对路径相应不同(frontend_* 因无 frontend/ 被跳过)。
  const filled = readFileSync(TEMPLATE, 'utf8')
    .replace(/\{\{(backend|frontend)_[a-z_]+\}\}/g, 'node ../scripts/probe.mjs')
    .replace(/\{\{e2e_cmd\}\}/g, 'node scripts/probe.mjs')
  writeFileSync(join(dir, 'scripts', 'test.mjs'), filled)
  writeFileSync(join(dir, 'scripts', 'probe.mjs'), PROBE_SOURCE)
  // setup-test-db stub:真模板会连库失败中断闸门、且自身也读 marker,无法隔离 test.mjs 的回填路径。
  writeFileSync(join(dir, 'scripts', 'setup-test-db.mjs'), PROBE_SOURCE)
  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', 'test.mjs')], { encoding: 'utf8', env: childEnv })
}

test('test.mjs: ERP_TEST_DB_SCHEMA env reaches every child process', () => {
  const r = runGate({ env: { ERP_TEST_DB_SCHEMA: 'erp_dev_lane2' } })
  assert.equal(r.status, 0, 'stdout: ' + r.stdout + ' stderr: ' + r.stderr)
  // setup-db / build / lint / unit / e2e 五步各打印一次。
  assert.equal((r.stdout.match(/PROBE_SCHEMA=erp_dev_lane2/g) || []).length, 5, 'stdout: ' + r.stdout)
  assert.match(r.stdout, /GREEN/, 'stdout: ' + r.stdout)
})

test('test.mjs: .tmp/erp-lane-db marker backfills env for children when env is absent', () => {
  const r = runGate({ laneMarker: 'erp_dev_lane3\n' })
  assert.equal(r.status, 0, 'stdout: ' + r.stdout + ' stderr: ' + r.stderr)
  assert.match(r.stdout, /lane DB marker 生效: ERP_TEST_DB_SCHEMA=erp_dev_lane3/, 'stdout: ' + r.stdout)
  assert.equal((r.stdout.match(/PROBE_SCHEMA=erp_dev_lane3/g) || []).length, 5, 'stdout: ' + r.stdout)
})

test('test.mjs: env wins over marker (marker log absent)', () => {
  const r = runGate({ env: { ERP_TEST_DB_SCHEMA: 'erp_dev_lane2' }, laneMarker: 'erp_dev_lane3\n' })
  assert.equal(r.status, 0, 'stdout: ' + r.stdout + ' stderr: ' + r.stderr)
  assert.equal((r.stdout.match(/PROBE_SCHEMA=erp_dev_lane2/g) || []).length, 5, 'stdout: ' + r.stdout)
  assert.doesNotMatch(r.stdout, /lane DB marker 生效/, 'stdout: ' + r.stdout)
})

test('test.mjs: no env and no marker leaves children unset (serial behavior unchanged)', () => {
  const r = runGate()
  assert.equal(r.status, 0, 'stdout: ' + r.stdout + ' stderr: ' + r.stderr)
  assert.equal((r.stdout.match(/PROBE_SCHEMA=\(unset\)/g) || []).length, 5, 'stdout: ' + r.stdout)
  assert.doesNotMatch(r.stdout, /lane DB marker 生效/, 'stdout: ' + r.stdout)
})