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.
Status for this app now lives on the Vikunja board (saved filter
finance-app), which replaced the smarthome repo's ACTIONS.md on 2026-07-30.
Notes the thing a single-label filter hides: a ticket can carry several
system labels — the receipt→pantry work is finance-app, pantry-app and
email-ingestion at once — so the finance filter is a view, not the boundary
of what will touch this codebase.
Also records the one dated item here: postgres-personal runs PostgreSQL 14,
EOL 2026-11-12, and it holds statements, transactions, orders and
expense_metadata.
Backfill dry-run over 130 real messages surfaced one 422: 'payments sum to 1.17
but receipt states 16.50'. The receipt is a mixed Uber payment —
Uber Cash $1.17 + Westpac ••••8032 $15.33 — and the card regex only matched
Visa|MasterCard|American Express|Amex, so an issuer-named leg was dropped
entirely. validateOrderTotals correctly refused it rather than recording $1.17
as the cost of a $16.50 order.
Anchors on the ••••NNNN masking instead, which also covers the form already
seen in the corpus ('Mastercard ••••3893 (CBA Ultimate) CHF 51.23'). Fixture
and regression test added.
Also repoints .env.test at the current postgres-personal container IP and
documents why: the container publishes no host port, so the address changes on
every recreate and the whole integration suite fails with connection errors
until it is refreshed.