Files
RenewCMS/features/roles_sync_conflicts.qa.md
T

56 lines
6.1 KiB
Markdown

# QA procedure: Role 409 conflicts (roles sync pass 6)
Spec: features/roles_sync_conflicts.feature. Vitest + Vue Test Utils under `task test:web`, /v1 mocked through the existing kits (the useApi mock used in views/__tests__), access built with web/src/test/access.ts. The backend Rule adds Go tests. No live server, no real SMTP.
## Honesty rules
0. Baseline `task test:web` and `task test` first (known Go failure from slice 7: TestRouterRoutesAreInTheCatalogue).
1. A runner that cannot execute: its items are NOT RUN. 2. Report new and edited specs and baseline failures.
3. Assert only the copy the design gives. "at 11:02" is rendered from a fixed `updated_at` through a pinned time zone; no spec freezes the system date.
4. The closed-surface toasts are component-level specs (user decision), with no page-level trigger.
## Automated (Vitest)
| Spec | Cases |
|---|---|
| views/__tests__/RoleEditorView.spec.ts (extend) | banner copy with updated_by and time, Save off, Reload role == Use the saved role, Review differences dialog rows (only disagreeing settings), Keep my edits (reload, edits on top, markers, Save on, info toast "Role reloaded · Your 2 changes..." and counter), Use the saved role, Escape keeps banner, a second 409, name taken is not a conflict |
| components/__tests__/RoleDifferencesDialog.spec.ts (new) | table Setting/Saved/Yours, EN/FR, the two actions, focus on open and return on close |
| lib/__tests__/roleDiff.spec.ts (new) | settings that disagree, tri-state and Create switch, custom type rows, grid order |
| components/__tests__/ChangeRoleDialog.spec.ts (extend) | 409 keeps the dialog open, alert copy with the re-read role, choice kept, Current mark moves, button enabled / disabled when the chosen role became current, second try sends the same role_id, no toast while open, member gone closes and refreshes, unmounted-dialog toast with Reload (component level) |
| components/__tests__/TransferOwnershipDialog.spec.ts (extend) | 409 alert copy with Reload member, typed email kept, primary off until reload, reload re-enables, unmounted-dialog toast, other answers unchanged |
| views/__tests__/RolesListView.spec.ts (extend) | 409 role_conflict on Duplicate, Delete, Move members: warning toast with Reload that refetches and dismisses; 404 removes the row and shows the info toast closing at 6 s (fake timers) |
| components/__tests__/DeleteRoleDialog.spec.ts (extend) | 409 target_role_not_found: inline error, picker cleared and re-read, no toast; 400 invalid_reassign unchanged |
| composables/__tests__/useRoles.spec.ts (extend) | refusal mapping adds target_role_not_found |
| composables/__tests__/useUsers.spec.ts (extend) | user_role_changed leaves the re-read to the caller |
| i18n/roleMessages.spec.ts (extend) | every group 12, 13 and 15 key exists in EN and FR, placeholders match |
Mocks to reuse: Contributor (version 2, 3), Jonas Weber (updated_by, 11:02), Ines Duarte Writer then Admin, Jonas Weber Writer, the board's difference rows (Events Update Own/All, Events Delete None/Own).
## Automated (Go, httptest, in-memory fakes) in test/
Build the role handler with `NewHandler(...)` over the use cases and the in-memory fakes of role.Repository, user.Repository and the audit recorder of the existing roles API tests. Mount on a fresh `chi.NewRouter()` mirroring api/router.go with `jwtauth.Verifier` + `Authenticator` and a test `jwtauth.New("HS256", …)`. Call `validator.InitValidator()`. No SQLite file, no SMTP. Unique fake names.
| Test | Steps and assertions |
|---|---|
| TestReassignMissingTargetIsConflict | the Owner reassigns Contributor to an id that does not exist: 409, body `target_role_not_found`, no member moved, no event recorded |
| TestReassignInvalidTargetsStay400 | table: itself, 0, Owner (locked), an uncovered role for a People manager: 400 `invalid_reassign`, nothing moved |
| TestReassignMissingSourceIs404 | unknown source: 404 `role_not_found` |
| TestReassignTargetSentinelLayering | the use case returns role.ErrReassignTargetNotFound (errors.Is); internal/domain/role imports no gorm, chi or infrastructure |
Update the existing tests of test/roles_api_* that expected 400 for a missing target. Mutation scope: `MUTATE_FILES=ReassignRoleMembers.go MUTATE_RUN='TestReassign' task mutate -- ./internal/infrastructure/useCases`. The existing 409 tests of ChangeUserRole and TransferOwnership (test/users_api_*) are the regression for `user_role_changed`.
## Visual (task visual:check)
`task visual:check -- roles` for the group 15 states: Stale save banner, Stale save review differences, Stale save edits kept after reload, Change role member's role changed, Transfer ownership target's role changed; and the group 12 toasts. Board sample data as above. Light, dark, desktop, mobile within 10 %. The banner's "11:02" is a fixed frame string and the setup pins the time zone, so it is not clock-sensitive.
## Manual checks
1. Two browsers: an admin edits Contributor, the owner saves first, the admin's save shows the banner with the real name and time; Keep my edits, then Save succeeds.
2. Two browsers: an owner changes a member's role while an admin has Change role open, then confirms; the alert shows the member's real new role.
3. Same for two simultaneous ownership transfers.
4. Two browsers: delete Writer in one, in the other confirm "Move members" to Writer; the inline error shows.
5. Focus: the alert is announced politely and focus returns to the dialog's primary or "Reload member"; Escape blocked while sending.
6. Mobile 390 px: the difference table scrolls or stacks; 44 px targets; FR; dark.
7. `task lint:web` green; `task test:web` and `task test` exit 0.
## Design gaps and notes
- G1 The design draws a 409 for a deleted reassign target; the backend now matches through `target_role_not_found` (the body name is a proposal, not in the design).
- G2 "Review differences" lists Setting / Saved / Yours rows; how a name or description difference is shown is not drawn.
- G3 When the chosen role became current after the re-read, the design does not say what the button does; the draft disables it.
- G4 Backend change here: one sentinel, one apperr string, one refusals-table row, docs/api.md.