feat: settings page, sidebar and drawer destination "Settings" (icon settings, after Integration), route /settings (verified), showing only the running build's Version
This commit is contained in:
1 parent
b146dfead7
commit
162fea4afb
26 files changed
+805
-45
No files matched your search
@@ -0,0 +1,131 @@
|
||||
Feature: Settings page and GET /v1/settings
|
||||
|
||||
As a verified RenewCMS user in the shared workspace
|
||||
I want a Settings page that shows the version of the RenewCMS build I am running
|
||||
So that I can tell which build is deployed when I report a problem or check an upgrade
|
||||
|
||||
Background:
|
||||
Given GET /v1/settings is mounted under /v1 in api/router.go in its own router, behind
|
||||
jwtauth.Verifier, jwtauth.Authenticator, middleware.RequireCurrentToken and
|
||||
middleware.RequireVerifiedUser, the same chain as NewUserRouter
|
||||
And the response of a verified caller is 200 with the JSON body {"version": "<value>"}, one
|
||||
snake_case field and nothing else, built from the domain entity settings.Settings in
|
||||
internal/domain/settings
|
||||
And GetSettingsUseCase (internal/infrastructure/useCases) is built with the version string and
|
||||
depends only on the domain package; the handler in api/handlers/settings receives it
|
||||
through NewHandler and never reads config, os or the environment
|
||||
And config.Settings gains a Version field; cmd/server/main.go sets it from its own
|
||||
main.version variable ("dev" unless set with -ldflags -X main.version) right after
|
||||
config.NewSettingsFromEnv, and api/dependecyInjection.go passes settings.Version to the use
|
||||
case; no os.Getenv or os.LookupEnv for the version appears outside cmd/server/main.go and
|
||||
internal/infrastructure/config, and the version is captured once at startup
|
||||
And /settings is a verified route (meta.auth "verified", meta.title "Settings") rendered as
|
||||
SettingsPage inside DashboardShell, per docs/design/routes.md and docs/design/screens/settings.md
|
||||
And SettingsPage calls GET /v1/settings once per navigation to it
|
||||
And the PageHeader shows breadcrumb "Settings" (mobile title "Settings"), the page title
|
||||
"Settings" and the subtitle "Workspace information."
|
||||
And the page is at most 720 px wide, with padding 32 px on desktop and 20px 16px 88px on
|
||||
mobile, and 20 px between the header and the card
|
||||
And the page holds one card, --ui-bg with a 1 px --ui-border, 20 px padding on desktop and
|
||||
16 px on mobile, containing a single row laid out as the board frame draws it: the label at
|
||||
the left, the value at the right, vertically centred, 16 px apart
|
||||
And the label is "Version" (15 px semibold, --ui-text-highlighted) and the value is the
|
||||
server's version string in IBM Plex Mono 14 px, --ui-text-toned, with no "v" added by the
|
||||
SPA
|
||||
And the page has no action, toggle, link or input; every verified user sees it, owner and
|
||||
admin alike
|
||||
And DashboardShell's navigation gains "Settings" (icon settings, i-lucide-settings, linking to
|
||||
/settings) after "Integration", in the sidebar and in the mobile drawer, so it lists five
|
||||
destinations in this order: Home, Articles, Users, Integration, Settings
|
||||
And skeleton placeholders show only after 150 ms of loading, so a fast response never flashes
|
||||
them
|
||||
And the Loading and Error states are not drawn in docs/design; they are decided in this spec
|
||||
and covered by Vitest only (no visual state)
|
||||
|
||||
Scenario Outline: A verified caller gets the running build's version
|
||||
Given the process was started with a config.Settings whose Version is "<version>"
|
||||
And a verified user is logged in with a valid jwt cookie
|
||||
When the client sends GET /v1/settings
|
||||
Then the response status is 200
|
||||
And the response body is exactly the JSON object {"version": "<version>"}
|
||||
|
||||
Examples:
|
||||
| version |
|
||||
| v2.2.0-beta |
|
||||
| dev |
|
||||
|
||||
Scenario: The use case returns the version it was built with
|
||||
Given GetSettingsUseCase is built with the version "v2.2.0-beta"
|
||||
When GetSettings is called
|
||||
Then it returns a settings.Settings whose Version is "v2.2.0-beta"
|
||||
|
||||
Scenario: A request without a jwt cookie is refused
|
||||
When the client sends GET /v1/settings with no jwt cookie
|
||||
Then the response status is 401
|
||||
And the use case is not consulted for the response
|
||||
|
||||
Scenario: A token whose version no longer matches the account is refused
|
||||
Given user id 7 exists with token version 1
|
||||
And the jwt cookie was minted for user 7 with token version 0
|
||||
When the client sends GET /v1/settings with that cookie
|
||||
Then the response status is 401
|
||||
And the response body is apperr.UnauthorizedErrorMessage
|
||||
|
||||
Scenario: An unverified account is refused
|
||||
Given user id 8 exists with is_verified false and a valid jwt cookie
|
||||
When the client sends GET /v1/settings with that cookie
|
||||
Then the response status is 403
|
||||
And the response body is apperr.VerificationRequiredErrorMessage
|
||||
And the response does not contain the version
|
||||
|
||||
Scenario: Default — the Version row shows the server's version
|
||||
Given GET /v1/settings resolves 200 with {"version": "v2.2.0-beta"}
|
||||
When the user navigates to /settings
|
||||
Then the breadcrumb reads "Settings" (mobile title "Settings")
|
||||
And the header shows the title "Settings" and the subtitle "Workspace information."
|
||||
And the card shows one row: the label "Version" and the value "v2.2.0-beta"
|
||||
And the "Settings" nav item has aria-current="page" and no other nav item does
|
||||
|
||||
Scenario: The value is shown exactly as reported, with no prefix added
|
||||
Given GET /v1/settings resolves 200 with {"version": "dev"}
|
||||
When the user navigates to /settings
|
||||
Then the value reads "dev"
|
||||
|
||||
Scenario: Navigation — "Settings" sits after Integration
|
||||
Given the user is on /home
|
||||
When DashboardShell mounts
|
||||
Then the sidebar and the mobile drawer list Home, Articles, Users, Integration, Settings in
|
||||
that order
|
||||
And "Settings" has the settings icon and links to /settings
|
||||
|
||||
Scenario: Loading — the value becomes a skeleton after 150 ms while the request is in flight
|
||||
Given GET /v1/settings has not yet resolved
|
||||
When the user navigates to /settings
|
||||
Then the label "Version" is visible at once, and nothing stands in for the value during the
|
||||
first 150 ms
|
||||
And after 150 ms a skeleton replaces the value only, and the card is aria-busy="true"
|
||||
When GET /v1/settings then resolves 200 with {"version": "v2.2.0-beta"}
|
||||
Then the skeleton is replaced by "v2.2.0-beta" and aria-busy is no longer set
|
||||
|
||||
Scenario Outline: Error — a 500 or a network failure replaces the row with an inline alert and a retry
|
||||
Given GET /v1/settings <failure>
|
||||
When the user navigates to /settings
|
||||
Then the card shows an error alert with no close button and no title, reading "Couldn't reach
|
||||
RenewCMS. Check your connection and try again.", with an action "Try again" (icon rotate-cw)
|
||||
And the "Version" row is not shown
|
||||
And the header (title and subtitle) is still shown
|
||||
When the user activates "Try again"
|
||||
Then the Loading state shows and GET /v1/settings is sent again
|
||||
When it then resolves 200 with {"version": "v2.2.0-beta"}
|
||||
Then the Default state's row replaces the alert
|
||||
|
||||
Examples:
|
||||
| failure |
|
||||
| resolves with a 500 response |
|
||||
| never reaches the server |
|
||||
|
||||
Scenario: A 401 or an unverified 403 is handled globally, not by the page
|
||||
Given GET /v1/settings resolves 401
|
||||
When the user navigates to /settings
|
||||
Then useApi's global handling redirects to "/login?redirect=/settings&reason=expired"
|
||||
And SettingsPage shows no error alert of its own
|
||||
Reference in new issue
Block a user