// A withdrawn consumer keeps its database, and retiring one renames it — nothing here ever drops // (novox/hq issue 241). psql is a fake on PATH that records every statement and answers the lookups // these acts make; that the server keeps the data is proven against a real server, not here. // Run against the compiled module (npm test builds first), the way the runtime loads it. import { test, before, after } from "node:test"; import assert from "node:assert/strict"; import { chmod, mkdtemp, readFile, rm, writeFile } from "node:fs/promises"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { PostgresClient, retiredName } from "../dist/client.js"; let dir: string; let log: string; const originalPath = process.env.PATH; before(async () => { dir = await mkdtemp(join(tmpdir(), "postgres-withdraw-")); log = join(dir, "calls.jsonl"); await writeFile(join(dir, "psql"), `#!/usr/bin/env node const fs = require("node:fs"); const args = process.argv.slice(2); const sql = args[args.indexOf("-c") + 1]; fs.appendFileSync(${JSON.stringify(log)}, JSON.stringify({ sql }) + "\\n"); if (/FROM pg_roles/.test(sql)) process.stdout.write("?column?\\n1\\n"); else if (/pg_get_userbyid/.test(sql)) process.stdout.write("owner\\nmesh_anchor_mail\\n"); else if (/FROM pg_database WHERE datname = '.*_deleted_/.test(sql)) process.stdout.write("?column?\\n"); else process.stdout.write(""); `); await chmod(join(dir, "psql"), 0o755); process.env.PATH = `${dir}:${originalPath}`; }); after(async () => { process.env.PATH = originalPath; await rm(dir, { recursive: true, force: true }); }); const statements = async (): Promise => (await readFile(log, "utf8")).trim().split("\n").map((l) => (JSON.parse(l) as { sql: string }).sql); const client = (): PostgresClient => new PostgresClient({ host: "127.0.0.1", port: 5432, user: "postgres", password: "admin-secret" }); test("withdrawing a consumer locks its login and keeps its database", async () => { await writeFile(log, ""); await client().lockRole("mesh_anchor_mail"); const sql = await statements(); assert.ok(sql.some((s) => /ALTER ROLE "mesh_anchor_mail" NOLOGIN/.test(s)), sql.join("\n")); assert.ok(!sql.some((s) => /\bDROP\b/i.test(s)), `a withdrawal dropped something:\n${sql.join("\n")}`); }); test("retiring a database renames it aside and locks its owner, and drops nothing", async () => { await writeFile(log, ""); const now = new Date("2026-10-05T09:00:00Z"); const aside = await client().retireDatabase("mesh_anchor_mail", now); assert.equal(aside, "mesh_anchor_mail_deleted_20261005"); const sql = await statements(); assert.ok(sql.some((s) => /ALTER DATABASE "mesh_anchor_mail" RENAME TO "mesh_anchor_mail_deleted_20261005"/.test(s)), sql.join("\n")); assert.ok(sql.some((s) => /ALTER ROLE "mesh_anchor_mail" NOLOGIN/.test(s))); assert.ok(!sql.some((s) => /\bDROP\b/i.test(s)), `retiring dropped something:\n${sql.join("\n")}`); }); test("a retired name fits postgres's 63 bytes", () => { const long = "x".repeat(70); const name = retiredName(long, new Date("2026-10-05T00:00:00Z")); assert.ok(name.length <= 63 && name.endsWith("_deleted_20261005"), name); });