A refused store closes its pool; pin the key's columns and uniqueness

The startup check's refusal left the store's pool open: the process exits
on it in production, but the connection outlived the refusal. createStore
now closes a store it refuses.

Two live cases the first run showed undefended (mutants D6 and D9): a
unique key replaced by a plain index on the same columns, and by a unique
index on other columns of the same count. The read-only-role case also
checks that no session of the role outlives the refusal. The fixture's
owner pool ignores the connection ends its own forced drop causes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Meta Vens 2026-09-30 17:29:19 +02:00
commit b5d7b6b0cc
2 changed files with 31 additions and 0 deletions

View file

@ -311,6 +311,9 @@ describe.skipIf(!adminUrl)("startup against a live Postgres (HEIMDALL_TEST_PG_AD
await admin.end();
});
const pool = new Pool({ connectionString: urlFor(name) });
// The forced drop ends this fixture pool's connections, possibly while
// they are still closing; that is cleanup, not a finding.
pool.on("error", () => undefined);
cleanup.push(async () => {
await pool.end();
});
@ -347,6 +350,15 @@ describe.skipIf(!adminUrl)("startup against a live Postgres (HEIMDALL_TEST_PG_AD
const message = await start(urlFor(name, role, "CANARYrolepw"), false);
expect(message).toBe(refusal("SCHEMA_PRIVILEGES"));
expect(message).not.toContain("CANARY");
// The refused store closed its pool: no session of the role outlives the refusal.
let sessions = -1;
for (let attempt = 0; attempt < 20 && sessions !== 0; attempt += 1) {
if (attempt > 0) await new Promise((resolve) => setTimeout(resolve, 100));
const result = await pool.query<{ n: number }>("SELECT count(*)::int AS n FROM pg_stat_activity WHERE usename = $1", [role]);
sessions = result.rows[0]!.n;
}
expect(sessions).toBe(0);
});
it("refuses a table whose primary key was dropped, even after applying the schema", async () => {
@ -367,6 +379,22 @@ describe.skipIf(!adminUrl)("startup against a live Postgres (HEIMDALL_TEST_PG_AD
expect(await start(urlFor(name), false)).toBe(refusal("SCHEMA_MISSING"));
});
it("refuses a unique key replaced by a plain index on the same columns", async () => {
const { name, pool } = await appliedDatabase();
await pool.query("ALTER TABLE linked_identities DROP CONSTRAINT linked_identities_provider_provider_user_id_key");
await pool.query("CREATE INDEX linked_identities_plain ON linked_identities(provider, provider_user_id)");
expect(await start(urlFor(name), false)).toBe(refusal("SCHEMA_MISSING"));
});
it("refuses a unique key replaced by one on other columns of the same count", async () => {
const { name, pool } = await appliedDatabase();
await pool.query("ALTER TABLE linked_identities DROP CONSTRAINT linked_identities_provider_provider_user_id_key");
await pool.query("CREATE UNIQUE INDEX linked_identities_other ON linked_identities(provider, username)");
expect(await start(urlFor(name), false)).toBe(refusal("SCHEMA_MISSING"));
});
it("refuses a created table's dropped column", async () => {
const { name, pool } = await appliedDatabase();
await pool.query("ALTER TABLE auth_completions DROP COLUMN mode");