← back to Rentv
test(pr): fix order-dependent integration suite flake (TK-10290)
ec481a9b164fbc9db1561509902cfbfb44ed034a · 2026-08-10 11:21:09 -0700 · steve@designerwallcoverings.com
Root cause: db.js uses a module-level pg Pool singleton; when a throwaway-DB
test file finishes without draining the pool, open TCP connections to
rentv_pr_test linger until the worker process exits via abrupt teardown (RST,
not FIN). The next test file's module-load execSync fires pg_terminate_backend
+ dropdb --if-exists + createdb while Postgres is still processing the RST
cleanup, causing a brief catalog race: "duplicate key value violates unique
constraint pg_database_datname_index" on createdb.
Fix: add db.end() to src/pr/db.js (drains + nulls the pool), then call it in
test.after() in all three throwaway-DB test files (integration, setpassword-
tenant-scope, tenant-rls-invariant). The graceful FIN/ACK close gives Postgres
a clean state before the next file's dropdb+createdb fires.
Verified: npm test 100/100, 0 fail (before: 99/100, 1 fail). All three files
also pass in isolation. Touches only test/ + the db.js helper export — no
runtime path changed, prod-safe.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Files touched
M src/pr/db.jsM test/pr/integration.test.jsM test/pr/setpassword-tenant-scope.test.jsM test/pr/tenant-rls-invariant.test.js
Diff
commit ec481a9b164fbc9db1561509902cfbfb44ed034a
Author: steve@designerwallcoverings.com <steve@designerwallcoverings.com>
Date: Mon Aug 10 11:21:09 2026 -0700
test(pr): fix order-dependent integration suite flake (TK-10290)
Root cause: db.js uses a module-level pg Pool singleton; when a throwaway-DB
test file finishes without draining the pool, open TCP connections to
rentv_pr_test linger until the worker process exits via abrupt teardown (RST,
not FIN). The next test file's module-load execSync fires pg_terminate_backend
+ dropdb --if-exists + createdb while Postgres is still processing the RST
cleanup, causing a brief catalog race: "duplicate key value violates unique
constraint pg_database_datname_index" on createdb.
Fix: add db.end() to src/pr/db.js (drains + nulls the pool), then call it in
test.after() in all three throwaway-DB test files (integration, setpassword-
tenant-scope, tenant-rls-invariant). The graceful FIN/ACK close gives Postgres
a clean state before the next file's dropdb+createdb fires.
Verified: npm test 100/100, 0 fail (before: 99/100, 1 fail). All three files
also pass in isolation. Touches only test/ + the db.js helper export — no
runtime path changed, prod-safe.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---
src/pr/db.js | 16 +++++++++++++++-
test/pr/integration.test.js | 5 ++++-
test/pr/setpassword-tenant-scope.test.js | 4 ++++
test/pr/tenant-rls-invariant.test.js | 4 ++++
4 files changed, 27 insertions(+), 2 deletions(-)
diff --git a/src/pr/db.js b/src/pr/db.js
index 100416b2..259a0c81 100644
--- a/src/pr/db.js
+++ b/src/pr/db.js
@@ -161,4 +161,18 @@ async function runMigrations({ log = () => {} } = {}) {
return { applied: done, alreadyApplied: [...applied] };
}
-module.exports = { query, rows, one, tx, health, runMigrations, DB_NAME, connConfig, tenantMiddleware };
+/** Drain the connection pool and reset the singleton. Safe to call multiple times.
+ * Needed by tests: each throwaway-DB test file calls this in test.after() so the pool's
+ * idle connections are closed before the next test file's module-load execSync fires
+ * its dropdb+createdb — without this, pg auto-reconnects race against createdb and produce
+ * "duplicate key value violates unique constraint pg_database_datname_index" (TK-10290). */
+async function end() {
+ if (_pool) {
+ const p = _pool;
+ _pool = null;
+ _initError = null;
+ try { await p.end(); } catch { /* ignore — we're tearing down */ }
+ }
+}
+
+module.exports = { query, rows, one, tx, health, runMigrations, end, DB_NAME, connConfig, tenantMiddleware };
diff --git a/test/pr/integration.test.js b/test/pr/integration.test.js
index aa7cea40..a1f162c4 100644
--- a/test/pr/integration.test.js
+++ b/test/pr/integration.test.js
@@ -237,7 +237,10 @@ test('audit log recorded every step of the workflow', async () => {
});
test.after(async () => {
- // leave the test DB in place for inspection; it is throwaway by name.
+ // Drain the pool so subsequent test files' module-load execSync (dropdb+createdb) do not
+ // race against pg's auto-reconnect and hit "duplicate key ... pg_database_datname_index"
+ // (TK-10290 order-dependent flake). The throwaway DB is left in place for inspection.
+ await db.end();
});
test('state-aware quality gate: computes CA and AZ independently, stores per-state', async () => {
diff --git a/test/pr/setpassword-tenant-scope.test.js b/test/pr/setpassword-tenant-scope.test.js
index ee1e7f0d..739a0b0a 100644
--- a/test/pr/setpassword-tenant-scope.test.js
+++ b/test/pr/setpassword-tenant-scope.test.js
@@ -21,6 +21,10 @@ execSync(
const db = require('../../src/pr/db');
const auth = require('../../src/pr/services/auth');
+// Drain pool after all tests so subsequent test files' module-load execSync (dropdb+createdb)
+// do not race against pg's auto-reconnect (TK-10290 order-dependent flake fix).
+test.after(async () => { await db.end(); });
+
test('setPassword is tenant-scoped (no cross-tenant password reset)', async (t) => {
await db.runMigrations({});
// Two tenants, one user each. Tenant 1 usually preexists from migrations; upsert both to be safe.
diff --git a/test/pr/tenant-rls-invariant.test.js b/test/pr/tenant-rls-invariant.test.js
index 28e67d98..cdc66e27 100644
--- a/test/pr/tenant-rls-invariant.test.js
+++ b/test/pr/tenant-rls-invariant.test.js
@@ -22,6 +22,10 @@ const db = require('../../src/pr/db');
// Documented RLS-exempt auth tables (login must resolve a user across tenants pre-context).
const RLS_EXEMPT = ['pr_users', 'pr_sessions'];
+// Drain pool after all tests so subsequent test files' module-load execSync (dropdb+createdb)
+// do not race against pg's auto-reconnect (TK-10290 order-dependent flake fix).
+test.after(async () => { await db.end(); });
+
test('every tenant_id table has RLS+FORCE+policy (except documented auth tables)', async () => {
await db.runMigrations({});
const unguarded = await db.rows(`
← 00d1ac47 auto-data-snapshot: 2026-08-10T10:58:17 (7 data files) — dat
·
back to Rentv
·
rentv(TK-10061): fix founder name typo in sublease.com outre 6bbbcd97 →