Files
baiyue-firstleg/scripts/database-schema-alignment-migrations-contract.test.mjs
T

48 lines
2.3 KiB
JavaScript
Raw Normal View History

2026-09-20 19:08:42 +08:00
import assert from 'node:assert/strict'
import { readFile } from 'node:fs/promises'
import path from 'node:path'
import test from 'node:test'
import { fileURLToPath } from 'node:url'
const repositoryRoot = path.resolve(fileURLToPath(new URL('..', import.meta.url)))
const migrations = {
iam: 'deploy/sql/migrations/cn/baiyue_iam/V1__align_schema_baseline.sql',
firstleg: 'deploy/sql/migrations/cn/baiyue_firstleg/V1__align_schema_baseline.sql',
file: 'deploy/sql/migrations/cn/baiyue_file/V1__align_schema_baseline.sql',
lastleg: 'deploy/sql/migrations/overseas/baiyue_lastleg/V2__align_schema_baseline.sql',
}
async function sources() {
return Object.fromEntries(await Promise.all(Object.entries(migrations).map(async ([name, relative]) => (
[name, await readFile(path.join(repositoryRoot, relative), 'utf8')]
))))
}
test('schema alignment migrations contain no destructive statements', async () => {
for (const [name, source] of Object.entries(await sources())) {
assert.doesNotMatch(source, /^\s*(?:DROP|DELETE|UPDATE|TRUNCATE)\b/im, `${name} 迁移不得删除、清理或回填数据`)
assert.doesNotMatch(source, /\bSIGNAL\s+SQLSTATE\b/i, `${name} 迁移不得使用 MySQL 不支持的动态 SIGNAL`)
}
})
test('schema alignment migrations cover the verified drift', async () => {
const source = await sources()
for (const field of ['tenant_code', 'customer_nickname', 'original_file_name']) assert.match(source.iam, new RegExp(`\\b${field}\\b`))
assert.match(source.firstleg, /\bpallet_no\b/)
for (const table of ['file_pda_release_channel', 'file_pda_release']) assert.match(source.file, new RegExp(`\\b${table}\\b`))
for (const field of [
'actual_discard_packages', 'pda_status', 'pda_operator_id', 'pda_completed_at',
'quantity', 'pallet_code', 'discrepancy_reason', 'manually_completed_at',
]) assert.match(source.lastleg, new RegExp(`\\b${field}\\b`))
})
test('constraint tightening is guarded before any alter table', async () => {
for (const [name, source] of Object.entries(await sources())) {
const firstAlter = source.search(/\bALTER TABLE\b/i)
const firstGuard = source.search(/SET @guard\s*=\s*IF/i)
if (name === 'file' || name === 'iam' || name === 'firstleg' || name === 'lastleg') {
assert.ok(firstGuard >= 0 && firstGuard < firstAlter, `${name} 必须先验证存量数据再执行 DDL`)
}
}
})