100 lines
5.1 KiB
JavaScript
100 lines
5.1 KiB
JavaScript
import assert from "node:assert/strict";
|
|
import test from "node:test";
|
|
import {
|
|
COMMENT_PREFIX, DEFAULT_DATABASE_PREFIX, databaseName, provision, rolePassword, validPrefix
|
|
} from "../src/execution-database.js";
|
|
|
|
const SECRET = "0123456789abcdef0123456789abcdef";
|
|
|
|
test("the name and the password are what the workflow manager derives", () => {
|
|
// The same two values are asserted in the manager's PostgresExecutionDatabaseTest: the two sides meet only through them.
|
|
const name = databaseName("exec_", "00000000-0000-0000-0000-000000000001");
|
|
assert.equal(name, "exec_00000000_0000_0000_0000_000000000001");
|
|
assert.equal(rolePassword(SECRET, name), "42910f819b45a671f75c2a0d6eaa0ea112df2dd9577c3e35498157c7c673b9bc");
|
|
assert.equal(databaseName("exec_", "ABC"), "exec_abc");
|
|
assert.equal(DEFAULT_DATABASE_PREFIX, "exec_");
|
|
});
|
|
|
|
test("a name that is not a usable identifier is refused", () => {
|
|
assert.throws(() => databaseName("exec_", "has space"), /scope/);
|
|
assert.throws(() => databaseName("exec_", "x".repeat(60)), /at most 63/);
|
|
});
|
|
|
|
test("a prefix that cannot start an identifier is refused", () => {
|
|
for (const bad of ["", "Exec_", "1exec", "ex-ec", "x".repeat(27), undefined]) assert.throws(() => validPrefix(bad), /prefix/);
|
|
assert.equal(validPrefix("exec_"), "exec_");
|
|
});
|
|
|
|
class FakeAdmin {
|
|
constructor({ version = 170000, roles = [], databases = [], failures = [] } = {}) {
|
|
this.version = version;
|
|
this.roles = new Set(roles);
|
|
this.databases = new Set(databases);
|
|
this.failures = failures;
|
|
this.statements = [];
|
|
}
|
|
|
|
async query(sql, values) {
|
|
this.statements.push(sql);
|
|
if (this.failures.length > 0 && this.failures[0].on.test(sql)) throw Object.assign(new Error("boom"), { code: this.failures.shift().code });
|
|
if (sql.startsWith("SELECT 1 FROM pg_roles")) return { rowCount: this.roles.has(values[0]) ? 1 : 0 };
|
|
if (sql.startsWith("SELECT 1 FROM pg_database")) return { rowCount: this.databases.has(values[0]) ? 1 : 0 };
|
|
if (sql.startsWith("SHOW server_version_num")) return { rows: [{ server_version_num: String(this.version) }] };
|
|
if (sql.startsWith("CREATE ROLE")) this.roles.add(sql.match(/"([^"]+)"/)[1]);
|
|
if (sql.startsWith("CREATE DATABASE")) this.databases.add(sql.match(/"([^"]+)"/)[1]);
|
|
return { rowCount: 0, rows: [] };
|
|
}
|
|
}
|
|
|
|
test("makes the user, the database owned by it, closes it to everyone else and sets the password", async () => {
|
|
const admin = new FakeAdmin();
|
|
await provision(admin, "exec_a", "pw");
|
|
const sql = admin.statements.filter((s) => !s.startsWith("SELECT") && !s.startsWith("SHOW"));
|
|
assert.match(sql[0], /^CREATE ROLE "exec_a" NOLOGIN NOSUPERUSER NOCREATEDB NOCREATEROLE NOREPLICATION NOBYPASSRLS CONNECTION LIMIT 8$/);
|
|
assert.equal(sql[1], 'GRANT "exec_a" TO CURRENT_USER WITH INHERIT TRUE, SET TRUE');
|
|
assert.equal(sql[2], 'CREATE DATABASE "exec_a" OWNER "exec_a"');
|
|
assert.match(sql[3], new RegExp(`^COMMENT ON DATABASE "exec_a" IS '${COMMENT_PREFIX}\\d+'$`));
|
|
assert.equal(sql[4], 'REVOKE ALL ON DATABASE "exec_a" FROM PUBLIC');
|
|
assert.equal(sql[5], 'GRANT CONNECT ON DATABASE "exec_a" TO CURRENT_USER');
|
|
assert.equal(sql[6], `ALTER ROLE "exec_a" LOGIN PASSWORD 'pw'`);
|
|
});
|
|
|
|
test("before PostgreSQL 16 a plain GRANT does what two options do after", async () => {
|
|
const admin = new FakeAdmin({ version: 150004 });
|
|
await provision(admin, "exec_a", "pw");
|
|
assert.ok(admin.statements.includes('GRANT "exec_a" TO CURRENT_USER'));
|
|
});
|
|
|
|
test("leaves a database and user that are already there, and still closes them and sets the password", async () => {
|
|
const admin = new FakeAdmin({ roles: ["exec_a"], databases: ["exec_a"] });
|
|
await provision(admin, "exec_a", "pw");
|
|
assert.ok(!admin.statements.some((s) => s.startsWith("CREATE ")), "nothing is made twice");
|
|
assert.ok(!admin.statements.some((s) => s.startsWith("COMMENT")), "its age is that of the one who made it");
|
|
assert.ok(admin.statements.includes(`ALTER ROLE "exec_a" LOGIN PASSWORD 'pw'`));
|
|
});
|
|
|
|
test("a password with a quote cannot break out of its statement", async () => {
|
|
const admin = new FakeAdmin();
|
|
await provision(admin, "exec_a", "it's");
|
|
assert.ok(admin.statements.includes(`ALTER ROLE "exec_a" LOGIN PASSWORD 'it''s'`));
|
|
});
|
|
|
|
test("losing a race for the database is not an error", async () => {
|
|
const admin = new FakeAdmin({ failures: [{ on: /^CREATE DATABASE/, code: "42P04" }] });
|
|
await provision(admin, "exec_a", "pw");
|
|
assert.ok(admin.statements.includes(`ALTER ROLE "exec_a" LOGIN PASSWORD 'pw'`));
|
|
});
|
|
|
|
test("a transient clash starts the steps again, and a real error does not", async () => {
|
|
const transient = new FakeAdmin({ failures: [{ on: /^GRANT "exec_a" TO/, code: "XX000" }] });
|
|
await provision(transient, "exec_a", "pw");
|
|
assert.equal(transient.statements.filter((s) => s.startsWith("GRANT \"exec_a\" TO")).length, 2);
|
|
|
|
const denied = new FakeAdmin({ failures: [{ on: /^CREATE ROLE/, code: "42501" }] });
|
|
await assert.rejects(provision(denied, "exec_a", "pw"), (error) => error.code === "42501");
|
|
});
|
|
|
|
test("a name that is not an identifier never reaches a statement", async () => {
|
|
await assert.rejects(provision(new FakeAdmin(), 'x"; DROP DATABASE postgres; --', "pw"), /Not a usable name/);
|
|
});
|