6.1 KiB
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
- Baseline
task test:webandtask testfirst (known Go failure from slice 7: TestRouterRoutesAreInTheCatalogue). - A runner that cannot execute: its items are NOT RUN. 2. Report new and edited specs and baseline failures.
- Assert only the copy the design gives. "at 11:02" is rendered from a fixed
updated_atthrough a pinned time zone; no spec freezes the system date. - 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
- 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.
- 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.
- Same for two simultaneous ownership transfers.
- Two browsers: delete Writer in one, in the other confirm "Move members" to Writer; the inline error shows.
- Focus: the alert is announced politely and focus returns to the dialog's primary or "Reload member"; Escape blocked while sending.
- Mobile 390 px: the difference table scrolls or stacks; 44 px targets; FR; dark.
task lint:webgreen;task test:webandtask testexit 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.