refac: DELETE /v1/user/{id}?expected_role_id=N, user.remove audit event, 409 target_role_not_found, GetAll returns its error, Own-scope visibility filter moved into repos, JSON tags removed from use-case types
This commit is contained in:
1 parent
5d6f378fce
commit
b39a41be88
124 files changed
+8816
-523
No files matched your search
@@ -0,0 +1,169 @@
|
||||
Feature: Roles sync pass 6: final copy, neutral wording and role activity events
|
||||
|
||||
As a person using roles, users and activity in EN or FR
|
||||
I want the final toast, error and activity wording of the roles-sync design
|
||||
So that every message names what happened, says what was kept and says what to do next
|
||||
|
||||
Background:
|
||||
Given the spec of record is docs/design/screens/roles.md groups 8, 12 and 13, the tables "Activity events: sentences", "Activity events: failed sentences", "Activity filter: category names" and "Final copy: toasts and errors, EN and FR", docs/design/states.md "Roles sync (pass 6)", docs/design/screens/activity.md "Roles sync (pass 6)" and docs/design/change-log-roles-sync.md section 1
|
||||
And the stale-state toasts, banners and dialog alerts of group 15 are in features/roles_sync_conflicts.feature, not here
|
||||
And the removal of a user (route, dialog, toast, user.remove event) is in features/roles_sync_remove_user.feature
|
||||
And the audit actions are role.create, role.edit, role.delete, role.reassign, user.change_role and user.transfer_ownership (internal/domain/audit/auditDomain.go), the outcome is a field and a failed event keeps its name
|
||||
And the SPA lives in web/src, Nuxt UI only, design tokens only, EN and FR in i18n/messages.ts, no comments in code
|
||||
And a string is written "Title · Body": the first part is the toast or alert title, the second its description
|
||||
And French is asserted only for the strings of the "Final copy" table and the activity tables; strings the design gives in EN only are listed in the qa as design gaps
|
||||
And the "Why it failed" reasons of the design (role_conflict, role_locked, missing_permission) are out of this slice
|
||||
|
||||
Rule: Neutral wording
|
||||
|
||||
Scenario: No gendered pronoun in the role copy
|
||||
Then the Change role dialog reads "Their API keys follow the new role."
|
||||
And the Transfer ownership confirmation reads "You become an Admin. You can't take ownership back unless they transfer it."
|
||||
And no EN string of the roles, users, profile, API keys and activity screens contains he, she, his, her or him as a word
|
||||
|
||||
Rule: Role changed (group 12)
|
||||
|
||||
Scenario: The success toast
|
||||
Given Ines Duarte holds Writer and the Change role dialog is open
|
||||
When the person picks Content lead and PUT /v1/user/{id}/role answers 200
|
||||
Then a success toast reads "Role changed · Ines Duarte is now Content lead, and their API keys follow the new role."
|
||||
And it closes after 6 s
|
||||
And in FR it reads "Rôle modifié · Ines Duarte a maintenant le rôle Content lead, et ses clés API suivent le nouveau rôle."
|
||||
|
||||
Rule: Role editor inline errors (group 13)
|
||||
|
||||
Scenario Outline: Save failed
|
||||
Given the person edited Contributor and presses Save changes
|
||||
When the server answers <failure>
|
||||
Then the editor shows "Couldn't save the role · Your changes are still here. Try again in a moment." with the action "Try again"
|
||||
And every edit is kept, Save changes stays enabled and "Try again" sends the same save again
|
||||
And in FR it reads "Impossible d'enregistrer le rôle · Vos modifications sont toujours là. Réessayez dans un instant." with "Réessayer"
|
||||
And it applies to POST /v1/role (new role) and PUT /v1/role/{id}
|
||||
Examples:
|
||||
| failure |
|
||||
| 500 |
|
||||
| a network failure |
|
||||
|
||||
Scenario: Replaces the generic error
|
||||
Then the placeholder wording of the previous slice for a failed save is gone from EN and FR
|
||||
|
||||
Rule: Change role inline error (group 13)
|
||||
|
||||
Scenario: Change role failed
|
||||
Given the Change role dialog is open with Contributor chosen
|
||||
When PUT /v1/user/{id}/role answers 500 or the network fails
|
||||
Then an alert in the dialog reads "Couldn't change the role · Nothing was changed. Try again in a moment."
|
||||
And the choice is kept and "Change role" stays enabled
|
||||
And in FR it reads "Impossible de modifier le rôle · Rien n'a été modifié. Réessayez dans un instant."
|
||||
|
||||
Scenario: Role above the caller
|
||||
Then a role above the caller's reads "Your role can't assign a role with more power than yours." in the picker and in the dialog
|
||||
And in FR "Votre rôle ne peut pas attribuer un rôle plus puissant que le vôtre."
|
||||
|
||||
Rule: Delete role inline errors (group 13)
|
||||
|
||||
Scenario Outline: Delete answers inside the dialog
|
||||
Given the Delete role dialog of <role> is open
|
||||
When <trigger>
|
||||
Then <effect>
|
||||
Examples:
|
||||
| role | trigger | effect |
|
||||
| Intern | POST /v1/role/{id}/reassign answers 409 target_role_not_found for the chosen target Writer | alert "Writer no longer exists · It was deleted somewhere else. Choose another role to move the members to.", the picker is cleared and "Move 1 member and delete role" is disabled until a role is chosen |
|
||||
| Intern | DELETE answers 409 role_has_members and the re-read of GET /v1/role shows 1 member | the dialog turns into the blocked state with "Role still has members · Intern now has 1 member. Move them to another role first." and "Move 1 member and delete role" disabled until a role is chosen |
|
||||
| Intern | POST /v1/role/{id}/reassign answers 500 or the network fails with Writer chosen | alert "Couldn't move the members · Nothing was changed. Try again in a moment.", Writer stays chosen, the action stays enabled, DELETE is not sent |
|
||||
| Intern | POST /v1/role/{id}/reassign answers 400 invalid_reassign (same role, locked or uncovered target) | the existing refusal copy of features/roles_spa_screens.feature, the picker is cleared |
|
||||
And in FR the first three read "Writer n'existe plus · Il a été supprimé ailleurs. Choisissez un autre rôle pour y déplacer les membres.", "Le rôle a encore des membres · Intern a maintenant 1 membre. Déplacez-le d'abord vers un autre rôle." and "Impossible de déplacer les membres · Rien n'a été modifié. Réessayez dans un instant."
|
||||
|
||||
Rule: Invite inline errors (group 13)
|
||||
|
||||
Scenario Outline: Invite answers
|
||||
Given the Invite user dialog holds "lea.martin@renew.example" and Writer
|
||||
When <trigger>
|
||||
Then <effect>
|
||||
And the dialog stays open and keeps the email and the role
|
||||
And in FR it reads <french>
|
||||
Examples:
|
||||
| trigger | effect | french |
|
||||
| the client check or a 400 rejects the address | under Email: "Enter a valid email address." | "Saisissez une adresse e-mail valide." |
|
||||
| POST /v1/user/invite answers 409 (existing account) | under Email: "This address already has an account." | "Cette adresse a déjà un compte." |
|
||||
| POST /v1/user/invite answers 403 escalation | under Role: "Your role can't assign a role with more power than yours." | "Votre rôle ne peut pas attribuer un rôle plus puissant que le vôtre." |
|
||||
| POST /v1/user/invite answers 500 or the network fails | alert "Couldn't send the invite · Try again in a moment." | "Impossible d'envoyer l'invitation · Réessayez dans un instant." |
|
||||
And the existing 409 "pending invite" state of users.md is unchanged
|
||||
|
||||
Rule: Final copy parity
|
||||
|
||||
Scenario: Every string of the "Final copy" table exists in EN and FR
|
||||
Then each key of the table exists in i18n/messages.ts with matching placeholders
|
||||
And roleMessages.spec.ts lists them
|
||||
|
||||
Rule: Backend: the destination name of a reassignment
|
||||
|
||||
Background for this Rule (house style of roles_permissions_roles_api.feature):
|
||||
Given the audit event role.reassign carries the params from (source role id), to (destination role id) and moved
|
||||
And this slice adds one additive param, to_name: the destination role's name at move time, a string
|
||||
And the event target keeps naming the source role, so the source name stays available after the source is deleted
|
||||
And no new route, no new permission and no schema change: params are stored as the existing JSON map
|
||||
And the layer touched is internal/infrastructure/useCases/ReassignRoleMembers.go and its audit param constant in internal/domain/audit; docs/api.md gains one line
|
||||
|
||||
Scenario: The param is recorded
|
||||
When POST /v1/role/{id}/reassign moves 4 members from Contributor to Writer
|
||||
Then the recorded role.reassign event has params from, to, moved 4 and to_name "Writer"
|
||||
And a reassignment that moves 0 members still records nothing
|
||||
And a refusal still records nothing
|
||||
|
||||
Scenario: Old events
|
||||
Given a stored role.reassign event without to_name
|
||||
Then GET /v1/activity returns it unchanged and the SPA uses the fallback below
|
||||
|
||||
Rule: Activity sentences for the six events
|
||||
|
||||
Scenario Outline: Success sentences
|
||||
Given GET /v1/activity returns an event <action> with outcome success
|
||||
Then the sentence reads <sentence> and in FR <french>
|
||||
Examples:
|
||||
| action | data | sentence | french |
|
||||
| role.create | target role Contributor | Role Contributor created | Rôle Contributor créé |
|
||||
| role.edit | target role Contributor | Role Contributor edited | Rôle Contributor modifié |
|
||||
| role.delete | target role Intern | Role Intern deleted | Rôle Intern supprimé |
|
||||
| role.reassign | target role Intern, params to_name Writer, moved 3 | Moved 3 members from Intern to Writer | 3 membres déplacés de Intern vers Writer |
|
||||
| role.reassign | target role Intern, params to_name Writer, moved 1 | Moved 1 member from Intern to Writer | 1 membre déplacé de Intern vers Writer |
|
||||
| user.change_role | target Ines Duarte, changes role before Writer after Content lead | Ines Duarte's role changed from Writer to Content lead | Le rôle d'Ines Duarte est passé de Writer à Content lead |
|
||||
| user.transfer_ownership | actor Marta Alves, target Jonas Weber | Ownership transferred from Marta Alves to Jonas Weber | Propriété transférée de Marta Alves à Jonas Weber |
|
||||
And the sentences "Moved the members of the role {0}" and "Changed the role of {0}" are no longer the output for these events
|
||||
|
||||
Scenario Outline: Failed sentences
|
||||
Given the event <action> has the outcome failed
|
||||
Then the sentence reads <sentence> followed by the Failed badge, and in FR <french>
|
||||
Examples:
|
||||
| action | sentence | french |
|
||||
| role.create | Failed to create the role Contributor | Échec de la création du rôle Contributor |
|
||||
| role.edit | Failed to edit the role Contributor | Échec de la modification du rôle Contributor |
|
||||
| role.delete | Failed to delete the role Intern | Échec de la suppression du rôle Intern |
|
||||
| role.reassign | Failed to move 3 members from Intern to Writer | Échec du déplacement de 3 membres de Intern vers Writer |
|
||||
| user.change_role | Failed to change the role of Ines Duarte | Échec du changement de rôle d'Ines Duarte |
|
||||
| user.transfer_ownership | Failed to transfer ownership to Jonas Weber | Échec du transfert de la propriété à Jonas Weber |
|
||||
|
||||
Scenario Outline: role.reassign without the destination name or the count (old events, failed events)
|
||||
Given a role.reassign event with outcome <outcome> whose params lack <missing>
|
||||
Then the sentence reads <sentence>
|
||||
And it never contains "undefined" or an empty name
|
||||
Examples:
|
||||
| outcome | missing | sentence |
|
||||
| success | to_name | Moved 3 members from Intern to another role |
|
||||
| success | moved | Moved the members of the role Intern |
|
||||
| failed | to_name, moved | Failed to move the members of the role Intern |
|
||||
And these fallback wordings are a design gap (the design draws only the full sentences); the coder uses them and the qa lists them
|
||||
|
||||
Scenario: Unknown action
|
||||
Given an event whose action the catalogue does not know
|
||||
Then it renders as before and does not crash
|
||||
|
||||
Rule: Activity category names
|
||||
|
||||
Scenario: The category select
|
||||
When the Category select is open
|
||||
Then the options read All categories, Articles, Media, Users and invites, Roles, API keys, Authentication, System, in that order
|
||||
And in FR Toutes les catégories, Articles, Médias, Utilisateurs et invitations, Rôles, Clés API, Authentification, Système
|
||||
And "Users & invites" no longer appears in the Activity filter, the permission groups or the API catalogue
|
||||
And choosing Roles requests category roles and shows the role events
|
||||
And choosing Users and invites keeps requesting category users
|
||||
Reference in new issue
Block a user