release: WRNexusJS 0.8.0
This commit is contained in:
@@ -0,0 +1,127 @@
|
||||
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<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",
|
||||
);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user