Files
RenewCMS/features/settings_page.qa.md

93 lines
6.0 KiB
Markdown

# QA: Settings page and GET /v1/settings
## Automated Go (`task test`, package `test`, black-box)
Files: `test/settings_test.go` (use case) and `test/settings_http_test.go` (HTTP). Fakes: reuse
`mockUserRepo` from `test/user_test.go`; no settings repository exists, so none is needed. Name any
new fake uniquely, since all fakes share one package.
Setup shared by the HTTP cases:
1. Call `validator.InitValidator()` once.
2. Build `token := jwtauth.New("HS256", []byte("test-secret"), nil)`, `getUser :=
useCases.NewGetUserUseCase(userRepo)` and `settingsHandler :=
settings.NewHandler(useCases.NewGetSettingsUseCase(version))`.
3. Mount on a fresh `chi.NewRouter()` mirroring `NewSettingsRouter`:
`r.Route("/v1/settings", ...)` with `jwtauth.Verifier(token)`, `jwtauth.Authenticator(token)`,
`middleware.RequireCurrentToken(getUser)`, `middleware.RequireVerifiedUser(getUser)`, then
`r.Get("/", settingsHandler.GetSettings)`. No live server, no SQLite, no SMTP.
4. Mint cookies with `token.Encode(map[string]any{"user_id": uint32(id), "token_version":
uint32(v)})`, as in `test/stale_token_invalidation_http_test.go`.
Cases:
5. Use case, table-driven over versions "v2.2.0-beta" and "dev": `NewGetSettingsUseCase(v)
.GetSettings().Version == v`. The rule that `internal/domain/settings` and the use case do not
import `config`, `os`, `net/http` or `gorm` is a review item, not a Go test.
6. Verified user id 7 (`IsVerified: true`, `TokenVersion: 0`), matching cookie, version
"v2.2.0-beta": `GET /v1/settings` returns 200 and the body decodes to exactly one key
`version` equal to "v2.2.0-beta" (decode into `map[string]any` and assert `len == 1`). Repeat
with version "dev".
7. No cookie: 401. Do not assert an exact body (it is produced by `jwtauth.Authenticator`).
8. User 7 with `TokenVersion: 1`, cookie encoding `token_version: 0`: 401, body exactly
`apperr.UnauthorizedErrorMessage`.
9. User 8 with `IsVerified: false`, matching cookie: 403, body exactly
`apperr.VerificationRequiredErrorMessage`, and the body does not contain the version string.
10. After building the router, call `t.Setenv("ENVIRONMENT", "development")` and re-request as in
case 6: the response is unchanged (nothing is read from the environment at request time).
## Automated SPA (Vitest + Vue Test Utils, `task test:web`)
Mock `useApi`'s `request` for `GET /v1/settings`. Mount SettingsPage inside DashboardShell with a
resolved verified session and a test router at /settings. Use fake timers for the 150 ms cases. No
test freezes the system date.
1. **Default** — resolve `{ version: 'v2.2.0-beta' }`; assert breadcrumb "Settings", title
"Settings", subtitle "Workspace information.", one row with the label "Version" and the value
exactly "v2.2.0-beta", `request` called once with `/v1/settings`, and no button, link, switch or
input inside the card.
2. **No prefix** — resolve `{ version: 'dev' }`; assert the value text is exactly "dev".
3. **Navigation** — assert the sidebar and the drawer list Home, Articles, Users, Integration,
Settings in that order with the settings icon on the last; on /settings only "Settings" carries
`aria-current="page"`.
4. **Loading** — unresolved request; at 149 ms assert no skeleton and that "Version" is present; at
150 ms assert a skeleton in place of the value and `aria-busy="true"` on the card; resolve and
assert the value and no `aria-busy`.
5. **Error, Examples-driven** — reject with `ApiError(500)`, then `NetworkError`; assert the alert
text "Couldn't reach RenewCMS. Check your connection and try again.", the "Try again" action, no
close button, no title and no "Version" row, with the header still shown. Click "Try again";
assert Loading and a second `request`; resolve and assert the Default row.
6. **401** — reject with `ApiError(401)`; assert the page shows no alert of its own (the redirect
itself is covered by `useApi`'s existing tests).
7. **Router meta** — assert the `/settings` route has `meta.auth === 'verified'` and
`meta.title === 'Settings'`.
8. **Amended shell specs** — update the existing Vitest for `nuxt_ui_shell_foundation` case 14,
`dashboard_shell_branding`, users_screen and profile_page to five nav items, in the same change.
## Visual (`task visual:check -- settings`, desktop and mobile, light and dark)
Reference: `docs/design/screens/settings.md`; board group "Settings" in `docs/design/source/RenewCMS
Screens Back-office.dc.html`; exact values from the `isSettings` block of
`docs/design/source/AppFrame.dc.html`. Add the mock to `web/visual/states.ts`. The gate is hard:
every capture within 10 %.
- [ ] Default: mock `GET /v1/settings` returning `{ "version": "v2.2.0-beta" }` (the board's
sample). Layout as the frame draws it: the header is a 20/28 semibold title with the muted
subtitle 2 px below; the card is the header's next sibling, 20 px below; the card is
`--ui-bg` with a 1 px `--ui-border` and no drawn radius; the row is a flex row with
`justify-content: space-between`, `align-items: center` and a 16 px gap, the label at the
left and the mono value at the right; the whole page is at most 720 px wide.
- [ ] Not drawn, no visual state: Loading and Error. Do not add them to `web/visual/states.ts`;
Vitest cases 4 and 5 cover them.
- [ ] Not covered by screenshots: keyboard order (Tab reaches the "Settings" nav item; on Error,
Tab reaches "Try again", and Enter and Space activate it); focus after a navigation; the
skeleton pulse under reduced motion.
## Manual verification
- With a build made through `task build` or `task release` (`-ldflags -X main.version=...`),
open /settings and confirm the value equals the injected version. A build without the flag must
show `dev`.
- Confirm against a running `task dev` API that an unverified account is sent to /register/pending
and a logged-out browser to /login?redirect=/settings&reason=expired.
- Confirm the embedded SPA (built without `-tags nofrontend`) serves /settings on a hard refresh,
since Vitest never exercises the embedded frontend.