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;
    default brioso.test; - disables). sendOtpEmail/sendOnce route any
    recipient whose domain is listed to the loopback_inbox table instead of
    the configured provider. Routing is per-recipient — Brevo delivery for
    real users is untouched.
  • GET /internal/admin/inbox — reads loopback_inbox, newest first.
    Query params: to=<addr> (optional filter), limit (default 20, max 100).
    Each message carries otp extracted from the html when present.
  • POST /internal/admin/qa-fixture — idempotent upsert of the QA
    account: qa@brioso.test user, permanent active Pro subscriptions
    row (period end 2099), permanent space qa-e2e-space (deep-linkable at
    /spaces/qa-e2e-space), and users.daily_job_cap (default 20; override
    with {"daily_job_cap": n}). ?reset=1 first 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 in checkGenerationQuota ahead of entitlement tiers,
    so it bounds Pro too; 429 DAILY_JOB_CAP_EXCEEDED when today’s jobs
    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=1 re-creates the
    fixture from scratch. Re-running without reset is a no-op upsert.
  • Removing everything QA-owned is the same call — reset deletes all rows
    the fixture can produce. The qa-e2e-space canvas itself is re-emptied.
  • Orphaned sent_emails claim rows (otp-<uuid> slugs) are per-issuance
    and inert; they’re deliberately not swept.

Caveats

  • OTP rate limits are in-memory (@brioso/auth-core rate-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=-) sends qa@brioso.test OTPs to real Brevo —
    leave the default unless a smoke specifically needs real delivery.
  • The inbox stores the rendered html; otp is 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.test recipients is defined
    away; testing real deliverability needs EMAIL_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-fixture is not atomic across
    parallel callers; run it once per environment before the QA sweep starts.