import { createHash } from 'node:crypto'; import type { Pool, PoolClient, QueryResult } from 'pg'; import { describe, expect, it, vi } from 'vitest'; import { runDatabaseMigrations } from '../../server/postgres/migration-runner'; function result(rows: unknown[] = []): QueryResult { return { command: '', rowCount: rows.length, oid: 0, fields: [], rows } as QueryResult; } function fakePool(existing: Array<{ version: string; checksum: string }> = []) { const query = vi.fn(async (text: string) => text.includes('SELECT version, checksum') ? result(existing) : result()); const release = vi.fn(); const client = { query, release } as unknown as PoolClient; const pool = { connect: vi.fn(async () => client) } as unknown as Pool; return { pool, query, release }; } describe('PostgreSQL migration runner', () => { it('serializes, applies and records a pending migration transactionally', async () => { const runtime = fakePool(); const applied = await runDatabaseMigrations(runtime.pool, [{ version: '900', name: 'test', sql: 'SELECT 900' }]); expect(applied).toEqual([{ version: '900', name: 'test', checksum: createHash('sha256').update('SELECT 900').digest('hex') }]); const statements = runtime.query.mock.calls.map(call => call[0]); expect(statements).toContain('BEGIN'); expect(statements).toContain('SELECT 900'); expect(statements).toContain('COMMIT'); expect(statements.some(statement => statement.includes('INSERT INTO assessor_schema_migrations'))).toBe(true); expect(statements.some(statement => statement.includes('pg_advisory_lock'))).toBe(true); expect(statements.some(statement => statement.includes('pg_advisory_unlock'))).toBe(true); expect(runtime.release).toHaveBeenCalledOnce(); }); it('rejects a changed migration checksum without executing its SQL', async () => { const runtime = fakePool([{ version: '900', checksum: 'different' }]); await expect(runDatabaseMigrations(runtime.pool, [{ version: '900', name: 'test', sql: 'SELECT 900' }])) .rejects.toThrow('nach ihrer Anwendung verändert'); expect(runtime.query.mock.calls.map(call => call[0])).not.toContain('SELECT 900'); expect(runtime.release).toHaveBeenCalledOnce(); }); });