mirror of
https://github.com/whyour/qinglong.git
synced 2026-09-20 16:07:11 +08:00
feat(ql3): establish 3.0 incubation baseline
This commit is contained in:
@@ -0,0 +1,207 @@
|
||||
const assert = require('node:assert/strict');
|
||||
const { test } = require('node:test');
|
||||
const {
|
||||
auditMigrationStreamHistory,
|
||||
InvalidMigrationStreamError,
|
||||
MigrationStreamAheadOfCodeError,
|
||||
MigrationStreamChecksumMismatchError,
|
||||
MigrationStreamHistoryCorruptionError,
|
||||
runMigrationStream,
|
||||
} = require('@qinglong/runtime-core/migration-stream');
|
||||
|
||||
const CHECKSUM_A = 'a'.repeat(64);
|
||||
const CHECKSUM_B = 'b'.repeat(64);
|
||||
|
||||
function stream(migrations) {
|
||||
return {
|
||||
id: 'postgresql-main',
|
||||
dialect: 'postgresql',
|
||||
migrationIdScheme: 'postgres-prefixed',
|
||||
checksumScheme: 'sha256',
|
||||
migrations,
|
||||
};
|
||||
}
|
||||
|
||||
function memoryStore(initial = []) {
|
||||
const records = new Map(
|
||||
initial.map((record) => [record.migrationId, { ...record }]),
|
||||
);
|
||||
let ensured = 0;
|
||||
return {
|
||||
records,
|
||||
get ensured() {
|
||||
return ensured;
|
||||
},
|
||||
store: {
|
||||
async ensureHistory() {
|
||||
ensured += 1;
|
||||
},
|
||||
async listAll() {
|
||||
return [...records.values()].map((record) => ({ ...record }));
|
||||
},
|
||||
async findById(id) {
|
||||
const record = records.get(id);
|
||||
return record ? { ...record } : null;
|
||||
},
|
||||
async transaction(work) {
|
||||
const staged = new Map(
|
||||
[...records].map(([id, record]) => [id, { ...record }]),
|
||||
);
|
||||
const result = await work({
|
||||
context: { statements: [] },
|
||||
async findById(id) {
|
||||
const record = staged.get(id);
|
||||
return record ? { ...record } : null;
|
||||
},
|
||||
async insert(record) {
|
||||
if (staged.has(record.migrationId)) throw new Error('duplicate');
|
||||
staged.set(record.migrationId, { ...record });
|
||||
},
|
||||
});
|
||||
records.clear();
|
||||
for (const [id, record] of staged) records.set(id, record);
|
||||
return result;
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
test('applies one prefixed migration atomically and replays it exactly once', async () => {
|
||||
const state = memoryStore();
|
||||
const logs = [];
|
||||
let calls = 0;
|
||||
const definition = stream([
|
||||
{
|
||||
id: 'pg-0001-schema-history',
|
||||
checksum: CHECKSUM_A,
|
||||
async up(context) {
|
||||
calls += 1;
|
||||
context.statements.push('create schema metadata');
|
||||
},
|
||||
},
|
||||
]);
|
||||
await runMigrationStream({
|
||||
stream: definition,
|
||||
store: state.store,
|
||||
clock: () => 100,
|
||||
logger: { info: (message) => logs.push(message) },
|
||||
});
|
||||
await runMigrationStream({ stream: definition, store: state.store });
|
||||
assert.equal(calls, 1);
|
||||
assert.equal(state.ensured, 2);
|
||||
assert.deepEqual(
|
||||
[...state.records.values()],
|
||||
[
|
||||
{
|
||||
streamId: 'postgresql-main',
|
||||
dialect: 'postgresql',
|
||||
migrationId: 'pg-0001-schema-history',
|
||||
checksum: CHECKSUM_A,
|
||||
appliedAtMs: 100,
|
||||
},
|
||||
],
|
||||
);
|
||||
assert.deepEqual(logs, [
|
||||
'[migration:postgresql-main] Applied pg-0001-schema-history',
|
||||
]);
|
||||
});
|
||||
|
||||
test('rejects checksum drift, ahead history and non-prefix gaps', async () => {
|
||||
const first = {
|
||||
id: 'pg-0001-schema-history',
|
||||
checksum: CHECKSUM_A,
|
||||
async up() {},
|
||||
};
|
||||
const second = {
|
||||
id: 'pg-0002-run-core',
|
||||
checksum: CHECKSUM_B,
|
||||
async up() {},
|
||||
};
|
||||
const record = (migrationId, checksum = CHECKSUM_A) => ({
|
||||
streamId: 'postgresql-main',
|
||||
dialect: 'postgresql',
|
||||
migrationId,
|
||||
checksum,
|
||||
appliedAtMs: 1,
|
||||
});
|
||||
await assert.rejects(
|
||||
runMigrationStream({
|
||||
stream: stream([{ ...first, checksum: CHECKSUM_B }]),
|
||||
store: memoryStore([record(first.id)]).store,
|
||||
}),
|
||||
MigrationStreamChecksumMismatchError,
|
||||
);
|
||||
await assert.rejects(
|
||||
runMigrationStream({
|
||||
stream: stream([first, second]),
|
||||
store: memoryStore([record('pg-0003-ahead')]).store,
|
||||
}),
|
||||
MigrationStreamAheadOfCodeError,
|
||||
);
|
||||
await assert.rejects(
|
||||
runMigrationStream({
|
||||
stream: stream([first, second]),
|
||||
store: memoryStore([record(second.id, second.checksum)]).store,
|
||||
}),
|
||||
MigrationStreamHistoryCorruptionError,
|
||||
);
|
||||
});
|
||||
|
||||
test('rolls migration work and history back together', async () => {
|
||||
const state = memoryStore();
|
||||
await assert.rejects(
|
||||
runMigrationStream({
|
||||
stream: stream([
|
||||
{
|
||||
id: 'pg-0001-schema-history',
|
||||
checksum: CHECKSUM_A,
|
||||
async up() {
|
||||
throw new Error('ddl failed');
|
||||
},
|
||||
},
|
||||
]),
|
||||
store: state.store,
|
||||
}),
|
||||
/ddl failed/,
|
||||
);
|
||||
assert.equal(state.records.size, 0);
|
||||
});
|
||||
|
||||
test('validates stream identity and immutable migration shape before storage', async () => {
|
||||
const state = memoryStore();
|
||||
for (const definition of [
|
||||
{ ...stream([]), id: 'PostgreSQL' },
|
||||
{ ...stream([]), migrationIdScheme: 'sqlite-numbered' },
|
||||
stream([{ id: '0001', checksum: CHECKSUM_A, async up() {} }]),
|
||||
stream([{ id: 'pg-0001', checksum: 'short', async up() {} }]),
|
||||
]) {
|
||||
await assert.rejects(
|
||||
runMigrationStream({ stream: definition, store: state.store }),
|
||||
InvalidMigrationStreamError,
|
||||
);
|
||||
}
|
||||
assert.equal(state.ensured, 0);
|
||||
});
|
||||
|
||||
test('audits a metadata-only manifest but requires executable steps to migrate', async () => {
|
||||
const manifest = stream([
|
||||
{ id: 'pg-0001-schema-history', checksum: CHECKSUM_A },
|
||||
]);
|
||||
const history = [
|
||||
{
|
||||
streamId: manifest.id,
|
||||
dialect: manifest.dialect,
|
||||
migrationId: manifest.migrations[0].id,
|
||||
checksum: manifest.migrations[0].checksum,
|
||||
appliedAtMs: 1,
|
||||
},
|
||||
];
|
||||
assert.deepEqual(
|
||||
[...auditMigrationStreamHistory(history, manifest)],
|
||||
['pg-0001-schema-history'],
|
||||
);
|
||||
await assert.rejects(
|
||||
runMigrationStream({ stream: manifest, store: memoryStore().store }),
|
||||
InvalidMigrationStreamError,
|
||||
);
|
||||
});
|
||||
Reference in New Issue
Block a user