Files
RenewCMS/features/verify_screen.qa.md

7.4 KiB

QA: Verify account screen

Automated (Vitest + Vue Test Utils, task test:web)

Mock useApi (POST /v1/auth/verify, POST /v1/auth/login, DELETE /v1/auth/register, POST /v1/auth/logout) and useToast per case; no real backend, no httptest server. Mount VerifyPage inside AuthShell with a test router whose current route starts at the "c" query the case needs; use fake timers for the 800 ms success delay and the 429 countdown.

  1. Verifying state — mount at "/register/validate?c=abc123"; assert the full-card spinner title "Verifying your account…" and that POST /v1/auth/verify was called exactly once with { code: 'abc123' } and { onUnauthorized: 'passthrough', onForbidden: 'passthrough' }.
  2. Success state — mock POST /v1/auth/verify resolving 200; mock the session store's retry() to resolve; mount and let it settle; assert the ResultPanel (success, shield-check, "Your account is verified", "Taking you to your dashboard…", no actions); assert a toast was already raised with title "Your account is verified", description "Welcome to RenewCMS." while the path is still /register/validate; advance by 800 ms; assert retry() was called and the router's current path is /home, with no second toast.
  3. Invalid or expired state — mock POST /v1/auth/verify resolving 403; assert the ResultPanel (error, link-2-off, "This verification link doesn't work", the text and note above) with actions "Cancel registration and start over" (primary) and "Log out" (secondary).
  4. Cancel registration and start over — success — from state 3, mock DELETE /v1/auth/register resolving 200; click the action; assert it was called with { onForbidden: 'passthrough' } and that no ConfirmDialog was opened; assert the router's current path is /register and a toast was raised with title "Registration cancelled", description "You can create the owner account again."
  5. Cancel registration and start over — failure shows an alert, not a redirect — three sub-cases from state 3: (a) DELETE /v1/auth/register resolves 403 (already verified elsewhere), (b) resolves 500, (c) a rejected fetch. In each, click the action and assert an alert is shown with color "error", icon "i-lucide-circle-x", title "Couldn't cancel registration", description "Something went wrong on the server. Try again in a moment."; assert the ResultPanel and both of its actions ("Cancel registration and start over", "Log out") are still rendered, unchanged; assert the router's current path did not change (in particular, sub-case (a) must NOT navigate to /home — unlike Pending's own already-verified handling, this screen never redirects on this 403).
  6. Log out from Invalid or expired — from state 3, mock POST /v1/auth/logout resolving 200; click "Log out"; assert the session store's clear() was called and the router's current path is /login.
  7. No session state — mock POST /v1/auth/verify resolving 401; assert the form (title "Log in to finish verifying", description, Username placeholder "Your username", Password with show/hide, submit "Log in and verify") and assert no "Forgot password?" link and no registration footer are rendered.
  8. No session — empty fields are caught on submit — from state 7, assert the submit button is enabled; submit with both fields empty; assert "Use at least 3 characters." and "Enter your password." are shown and POST /v1/auth/login was not called.
  9. No session — successful login retries — from state 7, fill valid credentials; mock POST /v1/auth/login resolving 200; mock a second POST /v1/auth/verify call; submit; assert the spinner "Verifying your account…" reappears and the second POST /v1/auth/verify call was made with the same code and the same passthrough options.
  10. No session — wrong credentials — from state 7, mock POST /v1/auth/login resolving 403; submit "marta" / "wrong-password"; assert the error alert ("That username and password don't match", "Check both and try again. Passwords are case-sensitive."), the Password field cleared and focused, and that POST /v1/auth/verify was not called again.
  11. No session — too many attempts — from state 7, mock POST /v1/auth/login resolving 429 with Retry-After: 30; submit; assert the info alert ("Too many attempts", "For your security, wait a moment before trying again."), the submit button reading "Try again in 0:30" and disabled; advance fake timers 30s; assert it re-enables.
  12. No session — server/network error — two sub-cases (500, rejected fetch) from state 7; submit valid-looking credentials; assert the error alert ("Couldn't reach RenewCMS", "Check your connection and try again.") and that both fields keep their values and re-enable.
  13. Missing code state — mount at "/register/validate" (no "c"); assert no request was made (neither POST /v1/auth/verify nor POST /v1/auth/login); assert the ResultPanel (error, link-2-off, "This link is incomplete", the text above) with a single secondary action "Go to log in"; click it and assert the router's current path is /login.

Visual (manual verification — desktop and mobile, light and dark)

Reference: docs/design/source/RenewCMS Screens Auth.dc.html, group "Verify". Run task visual:check -- verify for each state and compare against the board frame.

  • Verifying state: the full-card spinner (the only one in the product) matches the board's layout exactly, with no title header shown.
  • Success state: ResultPanel with no actions and the success toast over it, captured before the 800 ms redirect (the visual setup freezes timers with paused: true).
  • Invalid or expired state: ResultPanel with note and both actions, button order (primary "Cancel registration and start over" first per components.md's safe/primary-on-top rule for stacked mobile buttons).
  • Cancel-failed alert: the error alert renders above the ResultPanel via AuthShell's #notice slot, matching Register's own server-error alert styling exactly.
  • No session state: the trimmed-down log-in form (no forgot-password link, no footer) matches the board.
  • No session, retrying state: identical spinner to Verifying.
  • Missing code state: ResultPanel with a single secondary action.
  • Keyboard/focus: focus moves to the ResultPanel title on the Success, Invalid-or-expired, and Missing-code states; on No session, focus order is Username → Password → show/hide → submit; the spinner's role="status" text is announced once, not on every re-render; the cancel-failed alert is role="alert" and does not steal focus from the ResultPanel title.

Manual verification

  • Real POST /v1/auth/verify and POST /v1/auth/login against a running server — Vitest only mocks useApi; confirm the real 200/403/401 round-trips (including the actual bcrypt code check and 2-hour expiry) against task dev with a live SQLite file. The endpoint's own contract is already covered by features/account_verification.qa.md and features/login_screen.qa.md; this slice only re-verifies the screen consumes it correctly.
  • Real DELETE /v1/auth/register from this screen — confirm the 200/403/500 round-trips against a live server; the endpoint's own contract is already covered by features/cancel_pending_registration.qa.md.