import { describe, expect, it } from "@effect/vitest";import { Effect } from "effect"; import { runCodeMigrations, type CodeMigration, type CodeMigrationSql,} from "../../scripts/code-migrations/runner"; const makeFakeSql = (options: { readonly ledgerExists?: boolean; readonly stamped?: string[] }) => { const log: { readonly query: string; readonly params?: readonly unknown[] }[] = []; const stamped = [...(options.stamped ?? [])]; const sql: CodeMigrationSql = { unsafe: (query, params) => { log.push({ query, params }); if (query.includes("to_regclass")) { const rows = [ { ledger_table: options.ledgerExists === false ? null : "cloud_code_migration" }, ]; return Promise.resolve(rows as never); } if (query.includes('SELECT "name" FROM "cloud_code_migration"')) { return Promise.resolve(stamped.map((name) => ({ name })) as never); } if (query.includes('INSERT INTO "cloud_code_migration"')) { stamped.push(String(params?.[0])); } return Promise.resolve([] as never); }, }; return { sql, log, stamped };}; const migrationSpy = (name: string): { migration: CodeMigration; calls: boolean[] } => { const calls: boolean[] = []; return { calls, migration: { name, run: async ({ dryRun }) => { calls.push(dryRun); return { summary: `${name} done` }; }, }, };}; describe("runCodeMigrations", () => { it.effect("runs pending migrations and stamps them", () => Effect.promise(async () => { const { sql, stamped } = makeFakeSql({}); const a = migrationSpy("2026-06-20-a"); const b = migrationSpy("2026-06-21-b"); const applied = await runCodeMigrations(sql, [a.migration, b.migration], { log: () => {} }); expect(applied).toEqual(["2026-06-20-a", "2026-06-21-b"]); expect(a.calls).toEqual([false]); expect(b.calls).toEqual([false]); expect(stamped).toEqual(["2026-06-20-a", "2026-06-21-b"]); }), ); it.effect("dry run plans pending migrations without creating or stamping the ledger", () => Effect.promise(async () => { const { sql, log, stamped } = makeFakeSql({ ledgerExists: false }); const migration = migrationSpy("2026-06-20-a"); const applied = await runCodeMigrations(sql, [migration.migration], { dryRun: true, log: () => {}, }); expect(applied).toEqual(["2026-06-20-a"]); expect(migration.calls).toEqual([true]); expect(stamped).toEqual([]); expect(log.some((entry) => entry.query.includes("CREATE TABLE"))).toBe(false); expect(log.some((entry) => entry.query.includes("INSERT INTO"))).toBe(false); }), ); it.effect("skips stamped migrations", () => Effect.promise(async () => { const { sql } = makeFakeSql({ stamped: ["2026-06-20-a"] }); const a = migrationSpy("2026-06-20-a"); const b = migrationSpy("2026-06-21-b"); const applied = await runCodeMigrations(sql, [a.migration, b.migration], { log: () => {} }); expect(applied).toEqual(["2026-06-21-b"]); expect(a.calls).toEqual([]); expect(b.calls).toEqual([false]); }), ); it.effect("rejects duplicate names before touching the database", () => Effect.promise(async () => { const { sql, log } = makeFakeSql({}); const a = migrationSpy("2026-06-20-a"); await expect(runCodeMigrations(sql, [a.migration, a.migration])).rejects.toThrow( "Duplicate code migration name", ); expect(log).toEqual([]); }), );});
import { describe, expect, it } from "@effect/vitest";
import { Effect } from "effect";
import {
runCodeMigrations,
type CodeMigration,
type CodeMigrationSql,
} from "../../scripts/code-migrations/runner";
const makeFakeSql = (options: { readonly ledgerExists?: boolean; readonly stamped?: string[] }) => {
const log: { readonly query: string; readonly params?: readonly unknown[] }[] = [];
const stamped = [...(options.stamped ?? [])];
const sql: CodeMigrationSql = {
unsafe: (query, params) => {
log.push({ query, params });
if (query.includes("to_regclass")) {
const rows = [
{ ledger_table: options.ledgerExists === false ? null : "cloud_code_migration" },
];
return Promise.resolve(rows as never);
}
if (query.includes('SELECT "name" FROM "cloud_code_migration"')) {
return Promise.resolve(stamped.map((name) => ({ name })) as never);
}
if (query.includes('INSERT INTO "cloud_code_migration"')) {
stamped.push(String(params?.[0]));
}
return Promise.resolve([] as never);
},
};
return { sql, log, stamped };
};
const migrationSpy = (name: string): { migration: CodeMigration; calls: boolean[] } => {
const calls: boolean[] = [];
return {
calls,
migration: {
name,
run: async ({ dryRun }) => {
calls.push(dryRun);
return { summary: `${name} done` };
},
},
};
};
describe("runCodeMigrations", () => {
it.effect("runs pending migrations and stamps them", () =>
Effect.promise(async () => {
const { sql, stamped } = makeFakeSql({});
const a = migrationSpy("2026-06-20-a");
const b = migrationSpy("2026-06-21-b");
const applied = await runCodeMigrations(sql, [a.migration, b.migration], { log: () => {} });
expect(applied).toEqual(["2026-06-20-a", "2026-06-21-b"]);
expect(a.calls).toEqual([false]);
expect(b.calls).toEqual([false]);
expect(stamped).toEqual(["2026-06-20-a", "2026-06-21-b"]);
}),
);
it.effect("dry run plans pending migrations without creating or stamping the ledger", () =>
Effect.promise(async () => {
const { sql, log, stamped } = makeFakeSql({ ledgerExists: false });
const migration = migrationSpy("2026-06-20-a");
const applied = await runCodeMigrations(sql, [migration.migration], {
dryRun: true,
log: () => {},
});
expect(applied).toEqual(["2026-06-20-a"]);
expect(migration.calls).toEqual([true]);
expect(stamped).toEqual([]);
expect(log.some((entry) => entry.query.includes("CREATE TABLE"))).toBe(false);
expect(log.some((entry) => entry.query.includes("INSERT INTO"))).toBe(false);
}),
);
it.effect("skips stamped migrations", () =>
Effect.promise(async () => {
const { sql } = makeFakeSql({ stamped: ["2026-06-20-a"] });
const a = migrationSpy("2026-06-20-a");
const b = migrationSpy("2026-06-21-b");
const applied = await runCodeMigrations(sql, [a.migration, b.migration], { log: () => {} });
expect(applied).toEqual(["2026-06-21-b"]);
expect(a.calls).toEqual([]);
expect(b.calls).toEqual([false]);
}),
);
it.effect("rejects duplicate names before touching the database", () =>
Effect.promise(async () => {
const { sql, log } = makeFakeSql({});
const a = migrationSpy("2026-06-20-a");
await expect(runCodeMigrations(sql, [a.migration, a.migration])).rejects.toThrow(
"Duplicate code migration name",
);
expect(log).toEqual([]);
}),
);
});