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:
@@ -59,6 +59,49 @@ docker exec postgres-personal psql -U personal -d personal
|
||||
docker logs finance -f
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
```bash
|
||||
npm test # unit — pure logic, no database
|
||||
npm run test:integration # database-backed; resolves the DSN itself (see below)
|
||||
npm run test:all # both
|
||||
npm run test:setup # once, and after any migration: creates/refreshes personal_test
|
||||
npm run lint # blocking in CI since #79 — 0 errors, warnings allowed
|
||||
npx tsc --noEmit # blocking in CI; nothing else compiles the test files
|
||||
```
|
||||
|
||||
**The integration DSN is resolved at run time — never hand-write a host into
|
||||
`.env.test`.** `scripts/resolve-test-db.sh` runs automatically as
|
||||
`pretest:integration` and rewrites that file from the live container before
|
||||
every run. It prefers the container's **published host port** (`docker port
|
||||
postgres-personal` → `127.0.0.1:5432`, published since the 2026-08-17 PG17
|
||||
migration) and falls back to the bridge IP only where that is absent. Set
|
||||
`TEST_DATABASE_URL` to override it entirely.
|
||||
|
||||
This replaces the old "refresh the container IP" instruction, which described
|
||||
the defect rather than a workaround (#161). `setup-test-db.sh` used to bake the
|
||||
bridge IP into `.env.test` once, and Docker reassigns those on recreation: by
|
||||
2026-08-21 the stored `172.22.0.18` belonged to **gitea**, and an earlier drift
|
||||
had pointed the suite at **postgres-pantry** — whose database it would have
|
||||
TRUNCATEd had the credentials happened to match. A loopback address cannot be
|
||||
reassigned to another container, which is why the published port wins.
|
||||
|
||||
**Two guards, and both matter.** `helpers.ts::assertTestDatabase` refuses to
|
||||
truncate anything whose `current_database()` is not `personal_test`, and the
|
||||
suite runs `fileParallelism: false` because every file shares that one database
|
||||
and `resetDB()` TRUNCATEs it.
|
||||
|
||||
**A fresh checkout or worktree needs `npx prisma generate` first.** The client is
|
||||
generated into the gitignored `src/generated/prisma`, so without it every test
|
||||
that reaches `src/lib/db.ts` fails on a missing package — which reads as a code
|
||||
error rather than a setup one. CI does this explicitly for the same reason.
|
||||
|
||||
**Freeze the clock in any test whose subject is defined relative to "now".**
|
||||
`analytics-priority0.test.ts` uses `vi.useFakeTimers({ toFake: ["Date"] })` —
|
||||
only `Date`, because `pg` needs real timers for connection handling. Without it
|
||||
the fixtures drift out of the routes' rolling windows over time and the
|
||||
assertions quietly stop testing anything.
|
||||
|
||||
## Architecture
|
||||
|
||||
### Key Files
|
||||
|
||||
Reference in New Issue
Block a user