Files
mesh-catalog/modules/postgres/test/withdraw.test.ts
T

70 lines
3.1 KiB
TypeScript

// 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<string[]> =>
(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);
});