70 lines
3.1 KiB
TypeScript
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);
|
|
});
|