evOo-local/packages/db/src/client.test.ts
cynarAI 6c7eed7580 Initial clean evOo-local from f00577b
Fresh history cut from cynarAI/evoo antigravity-handoff/project-timeline
(Voice First + project timeline). Brand docs/titles as evOo; keep @rakazo/*
packages as tech debt. Excludes node_modules, caches, secrets, voice blobs.
2026-09-20 17:15:16 +02:00

116 lines
4.0 KiB
TypeScript

import { afterEach, describe, expect, it, vi } from "vitest";
import {
createDb,
createPool,
isTooManyDatabaseConnections,
parsePositiveInteger,
retryOnTooManyConnections,
} from "./client.js";
const pools: Array<{ end: () => Promise<void> }> = [];
afterEach(async () => {
await Promise.all(pools.splice(0).map((pool) => pool.end()));
});
describe("createDb", () => {
it("caps the pg pool and swallows idle-client errors so they cannot crash the process", () => {
const { pool } = createDb("postgres://rakazo:rakazo@127.0.0.1:9/rakazo", {
poolMax: 3,
applicationName: "rakazo-test",
});
pools.push(pool);
expect(pool.options.max).toBe(3);
expect(pool.options.connectionTimeoutMillis).toBe(10_000);
expect(pool.options.idleTimeoutMillis).toBe(0);
expect(pool.options.application_name).toBe("rakazo-test");
expect(pool.listenerCount("error")).toBeGreaterThan(0);
expect(pool.listenerCount("connect")).toBeGreaterThan(0);
expect(() => pool.emit("error", new Error("idle client lost"))).not.toThrow();
});
it("defaults to a four-connection pool", () => {
const { pool } = createDb("postgres://rakazo:rakazo@127.0.0.1:9/rakazo");
pools.push(pool);
expect(pool.options.max).toBe(4);
});
});
describe("parsePositiveInteger", () => {
it("falls back when the value is missing, zero, or not an integer", () => {
expect(parsePositiveInteger(undefined, 8)).toBe(8);
expect(parsePositiveInteger("0", 8)).toBe(8);
expect(parsePositiveInteger("nope", 8)).toBe(8);
expect(parsePositiveInteger("6", 8)).toBe(6);
});
});
describe("isTooManyDatabaseConnections", () => {
it("recognises Prisma P2037 and Postgres 53300", () => {
expect(isTooManyDatabaseConnections({ code: "P2037" })).toBe(true);
expect(isTooManyDatabaseConnections({ code: "53300" })).toBe(true);
expect(
isTooManyDatabaseConnections(
new Error("Too many database connections opened: sorry, too many clients already"),
),
).toBe(true);
expect(isTooManyDatabaseConnections(new Error("relation does not exist"))).toBe(false);
expect(isTooManyDatabaseConnections(undefined)).toBe(false);
});
it("walks a nested cause, matching the pg-pool unhandledRejection shape", () => {
const root = Object.assign(new Error("sorry, too many clients already"), { code: "53300" });
expect(isTooManyDatabaseConnections(new Error("pool.connect failed", { cause: root }))).toBe(
true,
);
});
});
describe("retryOnTooManyConnections", () => {
it("retries 53300 and returns the first success", async () => {
const sleep = vi.fn(async () => undefined);
let attempts = 0;
const result = await retryOnTooManyConnections(
async () => {
attempts += 1;
if (attempts < 3) {
throw Object.assign(new Error("sorry, too many clients already"), { code: "53300" });
}
return "ok";
},
{ sleep },
);
expect(result).toBe("ok");
expect(attempts).toBe(3);
expect(sleep).toHaveBeenCalledTimes(2);
});
it("does not retry unrelated failures", async () => {
const error = new Error("relation does not exist");
await expect(
retryOnTooManyConnections(async () => {
throw error;
}),
).rejects.toBe(error);
});
});
describe("createPool", () => {
it("builds a bounded pool with connect-retry and idle-error listeners", () => {
const pool = createPool("postgres://rakazo:rakazo@127.0.0.1:9/rakazo", {
poolMax: 2,
applicationName: "rakazo-pool-test",
});
pools.push(pool);
expect(pool.options.max).toBe(2);
expect(pool.options.connectionTimeoutMillis).toBe(10_000);
expect(pool.options.idleTimeoutMillis).toBe(0);
expect(pool.options.application_name).toBe("rakazo-pool-test");
expect(pool.listenerCount("error")).toBeGreaterThan(0);
expect(pool.listenerCount("connect")).toBeGreaterThan(0);
expect(() => pool.emit("error", new Error("idle client lost"))).not.toThrow();
});
});