feat(M01-C): 建立MySQL异步任务基础
This commit is contained in:
@@ -5,6 +5,7 @@ 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 { TaskRepository } from '../dist/tasks/task-repository.js';
|
||||
import {
|
||||
executeMigrationPlan,
|
||||
loadMigrationPlan,
|
||||
@@ -12,11 +13,13 @@ import {
|
||||
} from '../dist/db/migration-runner.js';
|
||||
|
||||
const expectedTables = [
|
||||
'qipai_async_tasks',
|
||||
'qipai_audit_logs',
|
||||
'qipai_devices',
|
||||
'qipai_legacy_table_mappings',
|
||||
'qipai_members',
|
||||
'qipai_orders',
|
||||
'qipai_outbox_events',
|
||||
'qipai_payments',
|
||||
'qipai_rooms',
|
||||
'qipai_schema_migrations',
|
||||
@@ -38,12 +41,13 @@ async function readCoreTables(pool) {
|
||||
return rows.map((row) => row.tableName);
|
||||
}
|
||||
|
||||
async function readMigrationVersion(pool) {
|
||||
async function readMigrationVersions(pool) {
|
||||
const [rows] = await pool.query(
|
||||
`SELECT version, name
|
||||
FROM qipai_schema_migrations
|
||||
WHERE version = ?`,
|
||||
['2026061601']
|
||||
WHERE version IN (?, ?)
|
||||
ORDER BY version`,
|
||||
['2026061601', '2026061802']
|
||||
);
|
||||
return rows;
|
||||
}
|
||||
@@ -79,11 +83,48 @@ async function assertLegacyCompatibility(pool) {
|
||||
assert.equal(orders[0].refundAmountCents, null);
|
||||
}
|
||||
|
||||
async function assertTaskDurability(pool) {
|
||||
await pool.query(
|
||||
`INSERT INTO qipai_tenants (code, name)
|
||||
VALUES ('M01C-TEST', 'M01C sanitized test tenant')`
|
||||
);
|
||||
const firstRepository = new TaskRepository(pool);
|
||||
const firstEnqueue = await firstRepository.enqueue({
|
||||
tenantId: '1',
|
||||
taskType: 'order.advance',
|
||||
idempotencyKey: 'order:501:paid',
|
||||
payload: { orderId: '501' },
|
||||
maxAttempts: 3
|
||||
});
|
||||
const duplicateEnqueue = await firstRepository.enqueue({
|
||||
tenantId: '1',
|
||||
taskType: 'order.advance',
|
||||
idempotencyKey: 'order:501:paid',
|
||||
payload: { orderId: '501' },
|
||||
maxAttempts: 3
|
||||
});
|
||||
assert.equal(firstEnqueue.created, true);
|
||||
assert.equal(duplicateEnqueue.created, false);
|
||||
assert.equal(duplicateEnqueue.id, firstEnqueue.id);
|
||||
|
||||
const restartedRepository = new TaskRepository(pool);
|
||||
const claimed = await restartedRepository.claimNext('worker-after-restart', 30_000);
|
||||
assert.equal(claimed?.id, firstEnqueue.id);
|
||||
assert.equal(claimed?.attempts, 1);
|
||||
assert.equal(await restartedRepository.complete(claimed.id, 'worker-after-restart'), true);
|
||||
|
||||
const [rows] = await pool.query(
|
||||
`SELECT status, attempts FROM qipai_async_tasks WHERE id = ?`,
|
||||
[firstEnqueue.id]
|
||||
);
|
||||
assert.deepEqual(rows, [{ status: 'SUCCEEDED', attempts: 1 }]);
|
||||
}
|
||||
|
||||
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_]+$/,
|
||||
/^qipai_m01c_test_[a-z0-9_]+$/,
|
||||
'Live migration test refuses to use a non-temporary database.'
|
||||
);
|
||||
|
||||
@@ -102,25 +143,26 @@ try {
|
||||
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'
|
||||
}]);
|
||||
assert.deepEqual(await readMigrationVersions(pool), [
|
||||
{ version: '2026061601', name: 'm01b_core_schema' },
|
||||
{ version: '2026061802', name: 'm01c_async_tasks' }
|
||||
]);
|
||||
await assertTaskDurability(pool);
|
||||
await assertLegacyCompatibility(pool);
|
||||
console.log('PASS: first up and verify completed.');
|
||||
console.log('PASS: first up, verify and durable task restart check completed.');
|
||||
|
||||
await executeMigrationPlan(pool, plans.down);
|
||||
assert.deepEqual(await readCoreTables(pool), []);
|
||||
await assertLegacyCompatibility(pool);
|
||||
console.log('PASS: down removed all M01-B core tables.');
|
||||
console.log('PASS: down removed all M01-B/M01-C 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'
|
||||
}]);
|
||||
assert.deepEqual(await readMigrationVersions(pool), [
|
||||
{ version: '2026061601', name: 'm01b_core_schema' },
|
||||
{ version: '2026061802', name: 'm01c_async_tasks' }
|
||||
]);
|
||||
await assertLegacyCompatibility(pool);
|
||||
console.log('PASS: second up and verify restored the schema.');
|
||||
|
||||
|
||||
Reference in New Issue
Block a user