/** * Transaction-based test isolation helper * * Wraps test code in a transaction that auto-rollbacks, providing * fast test isolation without truncating tables between tests. */ import type { Database } from "@reviq/db-schema"; import type { Kysely } from "kysely"; /** * Signal used to trigger transaction rollback after test completes */ class RollbackSignal extends Error { constructor() { super("RollbackSignal"); this.name = "RollbackSignal"; } } /** * Runs a test function inside a transaction that auto-rollbacks. * * The transaction implements the same interface as Kysely, * so it can be passed to context builders and used for all queries. * After the test completes, the transaction is rolled back, providing * instant cleanup without truncating tables. * * @example * ```typescript * test("creates user", async () => { * await withTestTransaction(getSharedDb(), async (db) => { * const user = await createTestUser(db, { email: "test@example.com" }); * const ctx = createAPIContext({ db }); * // ... test code * }); // Auto-rollback here * }); * ``` */ export async function withTestTransaction( db: Kysely, testFn: (trx: Kysely) => Promise, ): Promise { let result: T | undefined; try { await db.transaction().execute(async (trx) => { result = await testFn(trx); // Force rollback by throwing after test completes successfully throw new RollbackSignal(); }); } catch (e) { // Swallow the rollback signal - this is expected behavior if (!(e instanceof RollbackSignal)) { throw e; } } return result; }