Live-E2E QA harness (BRI-444)
Live-E2E QA harness (BRI-444)
Live-E2E QA harness (BRI-444)
How to run the real login flow on a deployed env (beta/prod) with no real
email delivery and no code path that bypasses auth.
Pieces
- Loopback inbox —
EMAIL_LOOPBACK_DOMAINS(comma/space-separated;
defaultbrioso.test;-disables).sendOtpEmail/sendOnceroute any
recipient whose domain is listed to theloopback_inboxtable instead of
the configured provider. Routing is per-recipient — Brevo delivery for
real users is untouched. GET /internal/admin/inbox— readsloopback_inbox, newest first.
Query params:to=<addr>(optional filter),limit(default 20, max 100).
Each message carriesotpextracted from the html when present.POST /internal/admin/qa-fixture— idempotent upsert of the QA
account:qa@brioso.testuser, permanentactiveProsubscriptions
row (period end 2099), permanent spaceqa-e2e-space(deep-linkable at
/spaces/qa-e2e-space), andusers.daily_job_cap(default 20; override
with{"daily_job_cap": n}).?reset=1first deletes every QA-scoped row
(jobs + dependent rows, kits, inbox, verifications, usage counters,
catalog state) — never other users’ rows.- Daily job cap —
users.daily_job_cap, NULL = uncapped (every normal
account). Enforced incheckGenerationQuotaahead of entitlement tiers,
so it bounds Pro too;429 DAILY_JOB_CAP_EXCEEDEDwhen today’sjobs
rows for the email reach the cap. This is the spend bound for a runaway
QA generation loop.
All /internal/* routes require Authorization: Bearer $WORKER_SHARED_SECRET (the Nitro sensitive-route-guard plugin enforces
it — no route-local auth needed).
Setup
BASE=https://beta.usebrioso.com
AUTH="Authorization: Bearer $WORKER_SHARED_SECRET"
# 1. (Re)create the fixture. ?reset=1 = clean slate.
curl -sf -X POST "$BASE/internal/admin/qa-fixture?reset=1" \
-H "$AUTH" -H 'content-type: application/json' -d '{}'
# → {"email":"qa@brioso.test","user_id":"…","space_id":"qa-e2e-space",
# "plan":"pro","daily_job_cap":20,"reset":true,"removed_jobs":N}
Login — the real OTP flow, no alternate session path
# 2. Request the OTP exactly like a user login.
curl -sf -X POST "$BASE/auth/request-otp" \
-H 'content-type: application/json' -d '{"email":"qa@brioso.test"}'
# 3. Read it back from the loopback inbox.
curl -sf "$BASE/internal/admin/inbox?to=qa@brioso.test&limit=1" -H "$AUTH"
# → {"messages":[{"otp":"123456", …}]}
# 4. Verify — sets a real session cookie.
curl -sf -X POST "$BASE/auth/verify-otp" -c qa.cookies \
-H 'content-type: application/json' \
-d '{"email":"qa@brioso.test","code":"123456"}'
# 5. Drive the product with the cookie jar.
curl -sf "$BASE/studio" -b qa.cookies
The verified session is the same session cookie a real user gets — every
subsequent request (generation, spaces, billing UI) exercises the real
auth path.
Cleanup
- Between runs:
POST /internal/admin/qa-fixture?reset=1re-creates the
fixture from scratch. Re-running withoutresetis a no-op upsert. - Removing everything QA-owned is the same call — reset deletes all rows
the fixture can produce. Theqa-e2e-spacecanvas itself is re-emptied. - Orphaned
sent_emailsclaim rows (otp-<uuid>slugs) are per-issuance
and inert; they’re deliberately not swept.
Caveats
- OTP rate limits are in-memory (
@brioso/auth-corerate-limit Map):
they are per-process and not resettable via the DB or the fixture
endpoint. A loop that spams request-otp will 429 until the window expires
or the process restarts. - Loopback is recipient-scoped, not provider-scoped: disabling it
(EMAIL_LOOPBACK_DOMAINS=-) sendsqa@brioso.testOTPs to real Brevo —
leave the default unless a smoke specifically needs real delivery. - The inbox stores the rendered html;
otpis extracted at read time (same
regex as the file provider’s outbox reader), so non-OTP mail shows
otp: null-ish (absent).
Deliberately not automated
- Stripe/payments — the fixture’s Pro row is synthetic (
qa_sub_…);
checkout, webhooks, and portal flows are never exercised by this harness. - Real email delivery — Brevo for
brioso.testrecipients is defined
away; testing real deliverability needsEMAIL_LOOPBACK_DOMAINS=-and a
human inbox. - GPU job completion — the cap bounds submissions, not runner
execution; a job that reaches a runtime still burns GPU per the normal
pipeline. Pair QA runs with the fake/dev runner or a cheap runtime. - OTP rate-limit reset — per-process state, restart-gated by design.
- Concurrent fixture resets —
qa-fixtureis not atomic across
parallel callers; run it once per environment before the QA sweep starts.