Files
Clintchiz a3ddd39b7b
Quality / quality (ubuntu-latest) (push) Failing after 14m38s
Quality / quality (windows-latest) (push) Canceled after 0s
feat: centralize application framework primitives
2026-08-22 23:07:46 +05:30

140 lines
4.5 KiB
TypeScript

import { describe, expect, test } from "bun:test";
import {
batch,
createDb,
createRepository,
databaseHealth,
retryTransaction,
compareAndSet,
} 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("compareAndSet reports whether the guarded transition won", async () => {
const db = createDb(memoryDriver());
expect(
await compareAndSet(db, {
table: "messages",
id: 1,
from: "pending",
to: "sending",
}),
).toBe(true);
});
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<RecordRow>(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",
);
});
});