test(M01-B): 验证脱敏旧库迁移兼容
This commit is contained in:
@@ -11,6 +11,7 @@ const upSql = read('database/migrations/2026061601_m01b_core_schema.up.sql');
|
||||
const downSql = read('database/migrations/2026061601_m01b_core_schema.down.sql');
|
||||
const verifySql = read('database/migrations/2026061601_m01b_core_schema.verify.sql');
|
||||
const seedSql = read('database/seeds/2026061601_m01b_minimal_seed.sql');
|
||||
const legacyFixtureSql = read('database/fixtures/2026061801_m01b_legacy_schema.sql');
|
||||
|
||||
const coreTables = [
|
||||
'qipai_schema_migrations',
|
||||
@@ -60,5 +61,16 @@ assert.match(upSql, /legacy_store_id/);
|
||||
assert.match(upSql, /legacy_order_id/);
|
||||
assert.match(seedSql, /INSERT IGNORE INTO qipai_legacy_table_mappings/);
|
||||
assert.match(seedSql, /member_order_info/);
|
||||
for (const table of [
|
||||
'member_store_info',
|
||||
'member_room_info',
|
||||
'member_order_info',
|
||||
'member_device_info'
|
||||
]) {
|
||||
assert.match(legacyFixtureSql, new RegExp(`CREATE TABLE ${table}`));
|
||||
}
|
||||
assert.match(legacyFixtureSql, /DECIMAL\(10,\s*2\)/);
|
||||
assert.match(legacyFixtureSql, /LEGACY-SANITIZED-001/);
|
||||
assert.doesNotMatch(legacyFixtureSql, /(?:https?:\/\/|@|password|secret|token)/i);
|
||||
|
||||
console.log('PASS: M01-B migration contract is present.');
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import { readFile } from 'node:fs/promises';
|
||||
import { dirname, resolve } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { loadConfig } from '../dist/config.js';
|
||||
import { closeMySqlPool, createMySqlPool } from '../dist/db/mysql.js';
|
||||
import { LegacyReadRepository } from '../dist/db/legacy-read-repository.js';
|
||||
import {
|
||||
executeMigrationPlan,
|
||||
loadMigrationPlan
|
||||
loadMigrationPlan,
|
||||
splitSqlStatements
|
||||
} from '../dist/db/migration-runner.js';
|
||||
|
||||
const expectedTables = [
|
||||
@@ -18,6 +23,7 @@ const expectedTables = [
|
||||
'qipai_stores',
|
||||
'qipai_tenants'
|
||||
];
|
||||
const repoRoot = resolve(dirname(fileURLToPath(import.meta.url)), '../..');
|
||||
|
||||
async function readCoreTables(pool) {
|
||||
const placeholders = expectedTables.map(() => '?').join(', ');
|
||||
@@ -42,6 +48,37 @@ async function readMigrationVersion(pool) {
|
||||
return rows;
|
||||
}
|
||||
|
||||
async function loadLegacyFixture(pool) {
|
||||
const fixtureSql = await readFile(
|
||||
resolve(repoRoot, 'database/fixtures/2026061801_m01b_legacy_schema.sql'),
|
||||
'utf8'
|
||||
);
|
||||
const statements = splitSqlStatements(fixtureSql);
|
||||
for (const statement of statements) {
|
||||
await pool.query(statement);
|
||||
}
|
||||
return statements.length;
|
||||
}
|
||||
|
||||
async function assertLegacyCompatibility(pool) {
|
||||
const repository = new LegacyReadRepository(pool);
|
||||
const stores = await repository.listStores({ tenantId: 1 });
|
||||
const rooms = await repository.listRooms({ tenantId: 1, parentId: 101 });
|
||||
const orders = await repository.listOrders({ tenantId: 1, parentId: 1001 });
|
||||
const devices = await repository.listDevices({ tenantId: 1, parentId: 1001 });
|
||||
|
||||
assert.deepEqual(stores.map((record) => record.legacyId), [101]);
|
||||
assert.deepEqual(rooms.map((record) => record.legacyId), [1001, 1002]);
|
||||
assert.deepEqual(devices.map((record) => record.code), ['SANITIZED-DEVICE-001']);
|
||||
assert.equal(orders.length, 1);
|
||||
assert.equal(orders[0].code, 'LEGACY-SANITIZED-001');
|
||||
assert.equal(orders[0].totalAmountCents, 2580);
|
||||
assert.equal(orders[0].paidAmountCents, 2000);
|
||||
assert.equal(orders[0].renewalAmountCents, 580);
|
||||
assert.equal(orders[0].groupAmountCents, 0);
|
||||
assert.equal(orders[0].refundAmountCents, null);
|
||||
}
|
||||
|
||||
const config = loadConfig();
|
||||
assert.equal(config.mysql.passwordConfigured, true, 'Live migration test requires a temporary password.');
|
||||
assert.match(
|
||||
@@ -58,6 +95,10 @@ const plans = {
|
||||
const pool = createMySqlPool(config);
|
||||
|
||||
try {
|
||||
const legacyFixtureStatements = await loadLegacyFixture(pool);
|
||||
await assertLegacyCompatibility(pool);
|
||||
console.log('PASS: sanitized legacy fixture is readable before migration.');
|
||||
|
||||
await executeMigrationPlan(pool, plans.up);
|
||||
await executeMigrationPlan(pool, plans.verify);
|
||||
assert.deepEqual(await readCoreTables(pool), expectedTables);
|
||||
@@ -65,10 +106,12 @@ try {
|
||||
version: '2026061601',
|
||||
name: 'm01b_core_schema'
|
||||
}]);
|
||||
await assertLegacyCompatibility(pool);
|
||||
console.log('PASS: first up and verify completed.');
|
||||
|
||||
await executeMigrationPlan(pool, plans.down);
|
||||
assert.deepEqual(await readCoreTables(pool), []);
|
||||
await assertLegacyCompatibility(pool);
|
||||
console.log('PASS: down removed all M01-B core tables.');
|
||||
|
||||
await executeMigrationPlan(pool, plans.up);
|
||||
@@ -78,6 +121,7 @@ try {
|
||||
version: '2026061601',
|
||||
name: 'm01b_core_schema'
|
||||
}]);
|
||||
await assertLegacyCompatibility(pool);
|
||||
console.log('PASS: second up and verify restored the schema.');
|
||||
|
||||
console.log(JSON.stringify({
|
||||
@@ -93,7 +137,16 @@ try {
|
||||
up: plans.up.statements.length,
|
||||
verify: plans.verify.statements.length,
|
||||
down: plans.down.statements.length
|
||||
}
|
||||
},
|
||||
legacyFixtureStatements,
|
||||
legacyCompatibilityChecks: [
|
||||
'stores',
|
||||
'rooms',
|
||||
'orders',
|
||||
'devices',
|
||||
'tenant isolation',
|
||||
'decimal cents'
|
||||
]
|
||||
}, null, 2));
|
||||
} finally {
|
||||
await closeMySqlPool(pool);
|
||||
|
||||
Reference in New Issue
Block a user