feat: register T1 (pending, verify)
This commit is contained in:
1 parent
3c3f7c6194
commit
4e41d60142
29 files changed
+2162
-42
No files matched your search
@@ -0,0 +1,94 @@
|
||||
# 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 text
|
||||
"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); advance fake timers by
|
||||
800 ms; assert `retry()` was called; assert the router's current path is /home and a toast was
|
||||
raised with title "Your account is verified", description "Welcome to RenewCMS."
|
||||
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 — submit disabled until filled** — from state 7, assert the submit button is
|
||||
disabled; fill both fields; assert it becomes enabled.
|
||||
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, transient toast over the /home board.
|
||||
- [ ] 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`.
|
||||
Reference in new issue
Block a user