import { describe, expect, test } from "bun:test"; import { batch, createDb, createRepository, databaseHealth, retryTransaction, } from "../src/index.ts"; import type { Driver, Row } from "../src/index.ts"; function memoryDriver(): Driver { const rows: Row[] = [{ id: 1, name: "One" }]; return { dialect: "sqlite", async query(sql) { if (/COUNT/.test(sql)) return [{ count: rows.length }]; if (/SELECT 1 AS healthy/.test(sql)) return [{ healthy: 1 }]; return rows; }, async exec() { return { changes: 1, lastInsertId: 2 }; }, async transaction(callback) { return callback(this); }, close() {}, }; } describe("database helper kit", () => { test("provides repository CRUD helpers and health checks", async () => { const db = createDb(memoryDriver()); const repository = createRepository<{ id: number; name: string }>(db, { table: "items", allowedColumns: ["name"], }); expect((await repository.find(1))?.name).toBe("One"); expect(await repository.count()).toBe(1); expect((await databaseHealth(db)).ok).toBe(true); expect(batch([1, 2, 3], 2)).toEqual([[1, 2], [3]]); }); test("retries transaction callbacks with bounded attempts", async () => { const db = createDb(memoryDriver()); let calls = 0; const value = await retryTransaction( db, async () => { calls += 1; if (calls < 2) throw new Error("retry"); return "done"; }, { attempts: 2, baseDelayMs: 0, shouldRetry: () => true }, ); expect(value).toBe("done"); expect(calls).toBe(2); }); test("uses dialect-aware placeholders and bounded list options", async () => { const queries: string[] = []; const driver = memoryDriver(); driver.dialect = "postgres"; const originalQuery = driver.query; driver.query = async (sql, params) => { queries.push(sql); return originalQuery.call(driver, sql, params); }; const originalExec = driver.exec; driver.exec = async (sql, params) => { queries.push(sql); return originalExec.call(driver, sql, params); }; const repository = createRepository<{ id: number; name: string }>(createDb(driver), { table: "items", allowedColumns: ["name"], maxListLimit: 50, }); await repository.find(1); await repository.create({ name: "Two" }); await repository.update(1, { name: "Changed" }); await repository.all({ limit: 10, offset: 5, orderBy: "name", direction: "desc" }); expect(queries.some((sql) => sql.includes("id = $1"))).toBe(true); expect(queries.some((sql) => sql.includes("VALUES ($1)"))).toBe(true); expect(queries.some((sql) => sql.includes("LIMIT $1 OFFSET $2"))).toBe(true); }); test("automatically applies an immutable tenant scope to every repository operation", async () => { const calls: Array<{ sql: string; params: unknown[] }> = []; const driver: Driver = { dialect: "sqlite", async query(sql, params = []) { calls.push({ sql, params: [...params] }); return /COUNT/.test(sql) ? [{ count: 0 }] : []; }, async exec(sql, params = []) { calls.push({ sql, params: [...params] }); return { changes: 1 }; }, async transaction(callback) { return callback(this); }, close() {}, }; type RecordRow = { id: number; name: string; tenant_id: string }; const repository = createRepository(createDb(driver), { table: "records", allowedColumns: ["name"], scope: { column: "tenant_id", value: "acme" }, }); await repository.all(); await repository.find(1); await repository.create({ name: "A", tenant_id: "other" }); await repository.update(1, { name: "B" }); await repository.remove(1); await repository.count(); expect(calls.every((call) => call.sql.includes("tenant_id"))).toBe(true); expect(calls.every((call) => call.params.includes("acme"))).toBe(true); expect(calls.some((call) => call.params.includes("other"))).toBe(false); }); test("rejects unsafe repository identifiers", () => { const db = createDb(memoryDriver()); expect(() => createRepository(db, { table: "items; DROP TABLE items" })).toThrow( "Unsafe SQL identifier", ); }); });