feat(migration): add lock heartbeats, predictive dry-run planning, and stricter ledger option validation

This commit is contained in:
2026-04-14 12:31:34 +00:00
parent 19ebdee31a
commit 1b4358aca5
17 changed files with 695 additions and 180 deletions
+43
View File
@@ -29,6 +29,7 @@ tap.test('dryRun: returns plan without invoking handlers', async () => {
expect(r.stepsApplied).toHaveLength(0);
expect(r.stepsSkipped).toHaveLength(2);
expect(r.stepsSkipped.map((s) => s.id)).toEqual(['a', 'b']);
expect(r.currentVersionAfter).toEqual('2.0.0');
// The ledger should still be in its initial state (currentVersion = null).
const current = await m.getCurrentVersion();
@@ -45,12 +46,54 @@ tap.test('plan(): returns plan without writing or running', async () => {
expect(stepCalled).toBeFalse();
expect(planResult.stepsSkipped).toHaveLength(1);
expect(planResult.stepsApplied).toHaveLength(0);
expect(planResult.currentVersionAfter).toEqual('2.0.0');
// Plan does not modify the ledger.
const current = await m.getCurrentVersion();
expect(current).toBeNull();
});
tap.test('dryRun: models freshInstallVersion on an empty database', async () => {
const m = new SmartMigration({
targetVersion: '2.0.0',
db,
ledgerName: 'dryrun_fresh',
freshInstallVersion: '2.0.0',
dryRun: true,
});
let stepCalled = false;
m.step('a').from('1.0.0').to('2.0.0').up(async () => { stepCalled = true; });
const r = await m.run();
expect(stepCalled).toBeFalse();
expect(r.wasFreshInstall).toBeTrue();
expect(r.stepsSkipped).toHaveLength(0);
expect(r.currentVersionBefore).toBeNull();
expect(r.currentVersionAfter).toEqual('2.0.0');
expect(await m.getCurrentVersion()).toBeNull();
});
tap.test('plan(): does not use freshInstallVersion when user data already exists', async () => {
await db.mongoDb.collection('dryrun_preexisting_users').insertOne({ id: 'user-1' });
const m = new SmartMigration({
targetVersion: '2.0.0',
db,
ledgerName: 'plan_preexisting',
freshInstallVersion: '2.0.0',
});
let stepCalled = false;
m.step('a').from('1.0.0').to('2.0.0').up(async () => { stepCalled = true; });
const r = await m.plan();
expect(stepCalled).toBeFalse();
expect(r.wasFreshInstall).toBeFalse();
expect(r.stepsSkipped).toHaveLength(1);
expect(r.stepsSkipped[0].id).toEqual('a');
expect(r.currentVersionAfter).toEqual('2.0.0');
expect(await m.getCurrentVersion()).toBeNull();
});
tap.test('cleanup: close shared db', async () => {
await cleanup();
});