import { beforeEach, describe, expect, it } from "@suaveplan/testing/runner"; import { BatchMigrator } from "../src/batch/batch-migrator/batch-migrator.js"; import { MemoryMigrationLock } from "../src/lock/memory-lock/memory-lock.js"; import { MigrationRunner } from "../src/runner/migration-runner/migration-runner.js"; import { MigrationStatusDashboard } from "../src/status/migration-status/migration-status.js"; import { MemoryMigrationStore } from "../src/store/memory-store/memory-store.js"; import type { Migration } from "../src/types/migration.types.js"; const migrations: Migration[] = [ { id: "001_create_users", name: "Create users table", async up(_ctx) {}, async down(_ctx) {}, }, { id: "002_add_email", name: "Add email column", async up(_ctx) {}, async down(_ctx) {}, }, { id: "003_create_posts", name: "Create posts table", async up(_ctx) {}, async down(_ctx) {}, }, ]; describe("MigrationRunner integration", () => { let store: MemoryMigrationStore; beforeEach(() => { store = new MemoryMigrationStore(); }); it("full up/down cycle", { timeout: 30000 }, async () => { const runner = new MigrationRunner({ migrations, store }); const upResult = await runner.up(); expect(upResult.applied).toHaveLength(3); expect(upResult.direction).toBe("up"); const statusAfterUp = await runner.status(); expect(statusAfterUp.every((s) => s.status === "applied")).toBe(true); const downResult = await runner.down({ steps: 1 }); expect(downResult.applied).toHaveLength(1); expect(downResult.applied[0]).toBe("003_create_posts"); // Up again applies only the rolled-back one const upResult2 = await runner.up(); expect(upResult2.applied).toHaveLength(1); }); it("idempotent up", { timeout: 30000 }, async () => { const runner = new MigrationRunner({ migrations, store }); await runner.up(); const second = await runner.up(); expect(second.applied).toHaveLength(0); }); it("with lock — runner acquires and releases", { timeout: 30000, }, async () => { const lock = new MemoryMigrationLock(); const runner = new MigrationRunner({ migrations, store, lock }); const result = await runner.up(); expect(result.applied).toHaveLength(3); expect(await lock.isLocked()).toBe(false); }); it("dry-run does not write records", { timeout: 30000 }, async () => { const runner = new MigrationRunner({ migrations, store, dryRun: true }); const result = await runner.up(); expect(result.dryRun).toBe(true); expect(await store.getApplied()).toHaveLength(0); }); it("status dashboard reflects runner results", { timeout: 30000, }, async () => { const runner = new MigrationRunner({ migrations, store }); await runner.up(); const dashboard = new MigrationStatusDashboard({ store, migrations }); const health = await dashboard.healthCheck(); expect(health.healthy).toBe(true); }); it("batch migrator runs multiple targets", { timeout: 30000 }, async () => { const store2 = new MemoryMigrationStore(); const runner1 = new MigrationRunner({ migrations: migrations.slice(0, 2), store, }); const runner2 = new MigrationRunner({ migrations: migrations.slice(0, 2), store: store2, }); const batch = new BatchMigrator({ targets: [ { name: "db1", runner: runner1 }, { name: "db2", runner: runner2 }, ], }); const result = await batch.run(); expect(result.succeeded).toEqual(["db1", "db2"]); expect(result.failed).toHaveLength(0); }); });