107 lines · 3.5 KB · TypeScript
RawDownload
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([]);    }),  );});