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); }