154 lines
4.9 KiB
JavaScript
154 lines
4.9 KiB
JavaScript
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,
|
|
splitSqlStatements
|
|
} from '../dist/db/migration-runner.js';
|
|
|
|
const expectedTables = [
|
|
'qipai_audit_logs',
|
|
'qipai_devices',
|
|
'qipai_legacy_table_mappings',
|
|
'qipai_members',
|
|
'qipai_orders',
|
|
'qipai_payments',
|
|
'qipai_rooms',
|
|
'qipai_schema_migrations',
|
|
'qipai_stores',
|
|
'qipai_tenants'
|
|
];
|
|
const repoRoot = resolve(dirname(fileURLToPath(import.meta.url)), '../..');
|
|
|
|
async function readCoreTables(pool) {
|
|
const placeholders = expectedTables.map(() => '?').join(', ');
|
|
const [rows] = await pool.query(
|
|
`SELECT table_name AS tableName
|
|
FROM information_schema.tables
|
|
WHERE table_schema = DATABASE()
|
|
AND table_name IN (${placeholders})
|
|
ORDER BY table_name`,
|
|
expectedTables
|
|
);
|
|
return rows.map((row) => row.tableName);
|
|
}
|
|
|
|
async function readMigrationVersion(pool) {
|
|
const [rows] = await pool.query(
|
|
`SELECT version, name
|
|
FROM qipai_schema_migrations
|
|
WHERE version = ?`,
|
|
['2026061601']
|
|
);
|
|
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(
|
|
config.mysql.database,
|
|
/^qipai_m01b_test_[a-z0-9_]+$/,
|
|
'Live migration test refuses to use a non-temporary database.'
|
|
);
|
|
|
|
const plans = {
|
|
up: await loadMigrationPlan('up'),
|
|
verify: await loadMigrationPlan('verify'),
|
|
down: await loadMigrationPlan('down')
|
|
};
|
|
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);
|
|
assert.deepEqual(await readMigrationVersion(pool), [{
|
|
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);
|
|
await executeMigrationPlan(pool, plans.verify);
|
|
assert.deepEqual(await readCoreTables(pool), expectedTables);
|
|
assert.deepEqual(await readMigrationVersion(pool), [{
|
|
version: '2026061601',
|
|
name: 'm01b_core_schema'
|
|
}]);
|
|
await assertLegacyCompatibility(pool);
|
|
console.log('PASS: second up and verify restored the schema.');
|
|
|
|
console.log(JSON.stringify({
|
|
mysqlHost: config.mysql.host,
|
|
databaseClass: 'temporary',
|
|
sequence: ['up', 'verify', 'down', 'up', 'verify'],
|
|
checksums: {
|
|
up: plans.up.checksum,
|
|
verify: plans.verify.checksum,
|
|
down: plans.down.checksum
|
|
},
|
|
statementCounts: {
|
|
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);
|
|
}
|