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', fileForwardFix: 'deploy/sql/migrations/cn/baiyue_file/V2__widen_business_type.sql', lastleg: 'deploy/sql/migrations/overseas/baiyue_lastleg/V2__align_schema_baseline.sql', lastlegForwardFix: 'deploy/sql/migrations/overseas/baiyue_lastleg/V3__relax_valid_nullable_fields.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`)) assert.match(source.fileForwardFix, /business_type\s+varchar\(64\)/i) 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`)) for (const field of ['inventory_position_id', 'new_sku_code', 'source_warehouse_id']) { assert.match(source.lastlegForwardFix, 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`) } } })