import { test, expect } from '@playwright/test' import { waitForLatestOtc, clearMailpit } from './lib/mailpit.js' // Unique per-run address. The §6.2 request path enforces a per-email // cooldown (OTC_REQUEST_COOLDOWN_SECONDS, default 60s), so a fixed // address would 429 on any re-run inside the window. A fresh address per // run sidesteps that without touching backend config. const EMAIL = `e2e-${Date.now()}-${Math.floor(Math.random() * 1e6)}@example.test` // First end-to-end smoke spec (M3-0 Task 7). Validates the whole Tier-1 // harness: the web tier serves the app, the backend's OTC sign-in path // (§6.2) issues a code, Mailpit captures the outbound mail, and a verify // of that code succeeds and mints a session. // // No precondition/provisioning step is needed: the §6.2 OTC path admits // any syntactically-valid email and provisions a fresh `pending` user on // verify (see backend/app/otc.py). Turnstile is open in this stack // (TURNSTILE_REQUIRED=false, no secret), so the request body carries only // the email. test('app loads and an OTC sign-in succeeds', async ({ page, request }) => { await clearMailpit() await page.goto('/') await expect(page).toHaveTitle(/.+/) const reqRes = await request.post('/auth/otc/request', { data: { email: EMAIL }, }) expect(reqRes.ok()).toBeTruthy() const code = await waitForLatestOtc(EMAIL) expect(code).toMatch(/^\d{6}$/) const verifyRes = await request.post('/auth/otc/verify', { data: { email: EMAIL, code }, }) expect(verifyRes.ok()).toBeTruthy() // Prove the sign-in actually took: the verify handler returns ok:true // and sets the `rfc_session` session cookie (§ SessionMiddleware, // backend/app/main.py). Asserting the cookie — not brittle UI text — // is what distinguishes a real sign-in from a bare 2xx. const verifyBody = await verifyRes.json() expect(verifyBody.ok).toBe(true) const setCookie = verifyRes.headers()['set-cookie'] || '' expect(setCookie).toContain('rfc_session=') })