Files
RenewCMS/features/roles_sync_conflicts.qa.md
T

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

  1. Baseline task test:web and task test first (known Go failure from slice 7: TestRouterRoutesAreInTheCatalogue).
  2. 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.