test(db): resolve the integration DSN at run time instead of storing an IP (#161)
ci / lint-test (push) Successful in 1m5s
ci / lint-test (push) Successful in 1m5s
setup-test-db.sh wrote .env.test once with the Postgres container's bridge IP baked in, and Docker reassigns those on recreation. The file rotted silently: its stored 172.22.0.18 belongs to **gitea** as of today, and an earlier drift had the suite pointing its TRUNCATE at **postgres-pantry** — which it would have wiped had the credentials happened to match. Root-cause fix: stop persisting a host at all. - scripts/resolve-test-db.sh re-derives the DSN from the live container and rewrites .env.test. Resolution order is TEST_DATABASE_URL, then the container's published host port, then the bridge IP. `docker port postgres-personal` reports 127.0.0.1:5432 since the 2026-08-17 PG17 migration, so loopback is what gets used here — and a loopback address cannot be reassigned to another container, which is the whole point. (Note this supersedes the smarthome CLAUDE.md's "postgres-personal publishes no host port"; that is now stale.) The bridge-IP fallback is kept for a host where the port is not published, and the script never prints the password. - Wired as `pretest:integration`, so npm runs it before every integration run. .env.test becomes a generated artifact rather than stored configuration. - setup-test-db.sh now only creates the database and copies the schema, then delegates to the resolver. - helpers.ts::assertTestDatabase stays and its message now points at the resolver rather than at `docker inspect`. It still earns its place: running vitest directly bypasses the pretest hook, and a stale .env.test would then still be read. Docs: CLAUDE.md gains a Testing section carrying this, the prisma-generate trap for a fresh checkout or worktree, and the frozen-clock rule for tests whose subject is defined relative to "now". AGENTS.md updated for the same, and for lint now being blocking.
This commit is contained in:
Executable
+66
@@ -0,0 +1,66 @@
|
||||
#!/bin/bash
|
||||
# Resolve the integration-test DSN at run time and (re)write .env.test.
|
||||
#
|
||||
# Why this exists (#161): .env.test used to be written ONCE by
|
||||
# setup-test-db.sh with the Postgres container's bridge IP baked in. Container
|
||||
# IPs are reassigned on recreation, so the file rotted silently — by
|
||||
# 2026-08-21 its 172.22.0.18 belonged to *gitea*, and an earlier drift had the
|
||||
# suite pointing its TRUNCATE at postgres-pantry. It only failed safe because
|
||||
# the credentials happened not to match.
|
||||
#
|
||||
# The fix is to stop persisting a host at all: this runs as `pretest:integration`,
|
||||
# so the DSN is re-derived from the live container immediately before every run.
|
||||
#
|
||||
# Resolution order:
|
||||
# 1. An explicit TEST_DATABASE_URL in the environment wins (CI, remote DB).
|
||||
# 2. The container's published host port (`docker port`) — postgres-personal
|
||||
# publishes 127.0.0.1:5432 since the 2026-08-17 PG17 migration. Preferred
|
||||
# because a loopback address cannot be reassigned to another container.
|
||||
# 3. The container's bridge IP, for a host where the port is not published.
|
||||
#
|
||||
# helpers.ts::assertTestDatabase is the backstop: it refuses to TRUNCATE
|
||||
# anything whose current_database() is not `personal_test`.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
PG_CONTAINER=${PG_CONTAINER:-postgres-personal}
|
||||
PG_USER=${PG_USER:-personal}
|
||||
PG_PASS=${PG_PASS:-personalpassword123}
|
||||
TEST_DB=${TEST_DB:-personal_test}
|
||||
|
||||
ENV_FILE="$(cd "$(dirname "$0")/.." && pwd)/.env.test"
|
||||
|
||||
if [ -n "${TEST_DATABASE_URL:-}" ]; then
|
||||
DSN="$TEST_DATABASE_URL"
|
||||
SOURCE="TEST_DATABASE_URL"
|
||||
else
|
||||
if ! docker inspect "$PG_CONTAINER" >/dev/null 2>&1; then
|
||||
echo "ERROR: container '$PG_CONTAINER' not found. Start it, or set TEST_DATABASE_URL." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# `docker port` prints e.g. "127.0.0.1:5432" (possibly several lines).
|
||||
PUBLISHED=$(docker port "$PG_CONTAINER" 5432/tcp 2>/dev/null | head -1 || true)
|
||||
|
||||
if [ -n "$PUBLISHED" ]; then
|
||||
HOSTPORT="$PUBLISHED"
|
||||
SOURCE="published port"
|
||||
else
|
||||
IP=$(docker inspect "$PG_CONTAINER" \
|
||||
--format '{{range .NetworkSettings.Networks}}{{.IPAddress}} {{end}}' 2>/dev/null \
|
||||
| tr ' ' '\n' | grep -v '^$' | head -1)
|
||||
if [ -z "$IP" ]; then
|
||||
echo "ERROR: '$PG_CONTAINER' has neither a published 5432 nor a bridge IP." >&2
|
||||
exit 1
|
||||
fi
|
||||
HOSTPORT="$IP:5432"
|
||||
SOURCE="bridge IP"
|
||||
fi
|
||||
|
||||
DSN="postgresql://$PG_USER:$PG_PASS@$HOSTPORT/$TEST_DB"
|
||||
fi
|
||||
|
||||
printf 'DATABASE_URL=%s\n' "$DSN" > "$ENV_FILE"
|
||||
|
||||
# Never print the password.
|
||||
echo "resolve-test-db: ${DSN%%://*}://***@${DSN#*@} (via $SOURCE)"
|
||||
Reference in New Issue
Block a user