The §9 deployed-environment E2E harness (0.52.0), run against a PPE host
with per-collection-isolated content, surfaced a latent multi-collection
bug: RFCView computed the collection id from the route but called
getRFC(pid, slug) without it, so a named-collection entry was always
fetched via the project default-collection route — which 404s for an entry
that exists only in a named collection ("Error: Not found"; metadata panel
absent). Local/Tier-1 stacks masked it (same slug also reachable via the
default collection). Thread cid through all three getRFC call sites; re-run
the load effect on collection change.
Harness/test-infra (not in the deployed artifact):
- e2e: pre-record cookie consent via addInitScript (lib/fixtures.js) so the
bottom-fixed consent banner can't intercept catalog row-select clicks on
the slower deployed edge.
- testing/seed-ppe.sh: fail loudly on any non-2xx Gitea response (a
swallowed 403 org-repo create had reached the deploy as a 502).
- testing/ppe-deploy-and-test.sh: seed via the Keychain admin token
(write:organization needed to create the PPE repos); store the E2E secret
newline-free; read EXPECT_VERSION from VERSION.
Patch bump 0.52.0 → 0.52.1; CHANGELOG updated.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Test harness (handbook §10.3 two-tier testing)
One environment-agnostic suite, two targets.
Tier 1 — local Docker (every PR)
make e2e-install # one-time: install Playwright + chromium (cold checkout)
make tier1-up # build + start: gitea(seeded) + backend + web(nginx) + mailpit
make e2e # run Playwright against http://localhost:8080
make fe-unit # run Vitest frontend unit tests
make tier1-down # stop + wipe volumes
- App (SPA + API): http://localhost:8080
- Mailpit UI / API: http://localhost:8025
- Gitea (disposable): http://localhost:3001
The stack is hermetic and disposable — fresh SQLite + fresh seeded Gitea each
tier1-up. e2e signs in via the email OTC flow, reading the code back from
Mailpit, so no real OAuth provider is needed.
CI note: the backend enforces a per-IP OTC request limiter (5 requests /
300s, backend/app/ratelimit.py). A single make e2e run uses exactly one OTC
request, so the normal "fresh tier1-up then one e2e" flow is well clear of
it. Do not retry make e2e more than ~4 times in a 5-minute window against the
same running stack, or the 6th OTC request will 429. Restarting the backend
container (or tier1-down/tier1-up) resets the in-process limiter. Note:
this is distinct from the per-email 60s OTC cooldown (OTC_REQUEST_COOLDOWN_SECONDS,
backend/app/otc.py); the smoke spec handles that separately by using a unique
email address per run.
Tier 2 — PPE (deploy gate)
The SAME suite, pointed at the PPE instance (once rfc-app-ppe.<base> is stood
up via flotilla — see the engineering handbook §10.1/§10.3):
cd e2e && BASE_URL=https://rfc-app-ppe.<base> MAILSINK_URL=<ppe-mailpit-api> npm run e2e
PPE provides the real nginx/systemd/SQLite topology + its own isolated Gitea + always-pass Turnstile keys. Standing up the PPE VM is an operator task, not part of this repo.