import { mkdtempSync, rmSync } from 'node:fs'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; import { sql } from 'drizzle-orm'; import { afterEach, beforeEach, describe, expect, it } from 'vitest'; import { createPgliteDb } from './client-pglite.js'; import { runPgliteMigrations } from './migrate.js'; import type { DbHandle } from './client.js'; interface PgliteExec { exec(query: string): Promise; } describe('runPgliteMigrations', () => { let dataDir: string; let handle: DbHandle; beforeEach(() => { dataDir = mkdtempSync(join(tmpdir(), 'mosaic-db-migrate-test-')); handle = createPgliteDb(dataDir); }); afterEach(async () => { await handle.close(); rmSync(dataDir, { recursive: true, force: true }); }); it('creates the BetterAuth tables required by the gateway', async () => { await runPgliteMigrations(handle); const result = (await handle.db.execute(sql` SELECT table_name FROM information_schema.tables WHERE table_schema = 'public' ORDER BY table_name `)) as unknown as { rows: Array<{ table_name: string }> }; const tables = result.rows.map((r) => r.table_name); // Auth tables — required for sign-in / bootstrap to function. expect(tables).toContain('users'); expect(tables).toContain('sessions'); expect(tables).toContain('accounts'); expect(tables).toContain('verifications'); // Schema sanity check — admin token table consumed by mosaic gateway config. expect(tables).toContain('admin_tokens'); }); it('is idempotent — running twice does not error', async () => { await runPgliteMigrations(handle); await expect(runPgliteMigrations(handle)).resolves.toBeUndefined(); }); it('surfaces statement-level error context on failure and leaves no ledger row', async () => { // Pre-create a `users` table that conflicts with migration 0000's CREATE TABLE, // forcing it to fail without IF NOT EXISTS. const client = (handle.db as unknown as { $client: PgliteExec }).$client; await client.exec('CREATE TABLE users (sentinel text)'); await expect(runPgliteMigrations(handle)).rejects.toThrow( /migration hash=[a-f0-9]+ statement #\d+ failed/, ); // Ledger should be empty — partial application must not pretend to be complete. const ledger = (await handle.db.execute( sql`SELECT count(*)::int AS count FROM drizzle.__drizzle_migrations`, )) as unknown as { rows: Array<{ count: number }> }; expect(ledger.rows[0]?.count).toBe(0); }); });