Files
RenewCMS/features/settings_page.qa.md
T

6.0 KiB

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.