Accounts and admin

26 roles and permissions test cases, checked at the server and not the button

Twenty-six test cases for roles and permissions — privilege escalation, horizontal access, the hidden button that still works, and what happens the moment a role changes.

  • 26 cases
  • 7 coverage types
  • Last verified Sep 16, 2026

There is one mistake that accounts for most permission bugs, and it is not a missing check. It is a check that exists in the interface and nowhere else.

The button is hidden from a viewer, so the feature looks locked. The endpoint behind it is not, so anyone who knows the URL is an editor. Every test that drives the browser passes, because the browser never shows the tester the thing they are not supposed to click.

So most of these cases are written in pairs: once through the interface, once straight at the endpoint. If you only run the first half of each pair, you have tested the design, not the permission.

Type
Priority

Showing 26 of 26 cases

26 test cases for Test cases for user roles and permissions
IDTest caseTypePriorityPreconditionsStepsTest dataExpected result
ROLE-01Each role sees the pages it shouldFunctionalHighOne account per role
  1. Sign in as each role in turn
  2. List the navigation entries shown
admin, editor, viewerEach role sees exactly the documented set, with no entry that errors when opened.
ROLE-02A new member gets the least privileged role by defaultFunctionalHighAn open invitation
  1. Accept an invitation without choosing a role
The account is created as the lowest role. Nothing is granted by omission.
ROLE-03Changing a role takes effect without a new sign-inFunctionalHighA signed-in editor
  1. Demote the editor to viewer in another session
  2. Have them act on a page they could edit a moment ago
The action is refused. The permission is read per request, not cached from sign-in.
ROLE-04A promoted user gains access immediatelyFunctionalHighA signed-in viewer
  1. Promote them to editor
  2. Have them open an editor-only page
The page opens without signing out and in again.
ROLE-05A custom role grants exactly what it listsFunctionalMediumCustom roles are supported
  1. Create a role with one permission
  2. Assign it
  3. Exercise the boundary either side
one permissionThe listed action succeeds and the adjacent ones are refused.
ROLE-06Permissions inherited from a group applyFunctionalMediumGroup-based access is supported
  1. Add a user to a group with elevated access
  2. Act as that user
The group grant applies, and removing them from the group removes it again.
ROLE-07The last administrator cannot be removedFunctionalHighExactly one admin remains
  1. Attempt to demote or delete the last admin
Refused with an explanation. An account with no administrator is unrecoverable.
ROLE-08A viewer cannot edit through the interfaceNegativeHighSigned in as a viewer
  1. Open a record
  2. Look for edit and delete controls
The controls are absent or disabled, and no keyboard path reaches them.
ROLE-09A viewer cannot edit through the endpointSecurityHighA viewer session and a proxy
  1. Send the edit request directly, copied from an editor session
the editor's PATCHRefused with 403. This is the case that catches a check that lives only in the interface.
ROLE-10A direct URL to a restricted page is refusedSecurityHighSigned in as a viewer
  1. Paste an admin-only URL into the address bar
/settings/membersRefused or redirected. Not rendered with the data already loaded.
ROLE-11One user cannot read another user's recordSecurityHighTwo users with their own records
  1. Signed in as A, request B's record by id
B's record idRefused. Horizontal access is checked on ownership, not only on role.
ROLE-12A role field in the request body is ignoredSecurityHighSigned in as a viewer
  1. Add a role field set to admin to a profile update
  2. Send it
role=adminThe role is unchanged. The field is not bound from user input.
ROLE-13An invitation cannot grant more than the inviter holdsSecurityHighSigned in as an editor
  1. Invite a new member as an administrator
role=adminRefused. Escalation by proxy is the same escalation.
ROLE-14Permissions are re-checked on every request, not just the firstSecurityHighA signed-in admin
  1. Capture a request
  2. Demote the account
  3. Replay the captured request
Refused. A valid session is not a standing grant.
ROLE-15An expired or revoked session cannot actSecurityHighAn admin signed in on two devices
  1. Revoke one session
  2. Act from the revoked device
Refused immediately, not at the next natural expiry.
ROLE-16Elevated access is time-bound where documentedSecurityMediumTemporary elevation is supported
  1. Elevate
  2. Wait past the stated window
  3. Retry the elevated action
Access lapses on its own without an administrator having to remember.
ROLE-17Support impersonation is visible and loggedSecurityHighImpersonation is supported
  1. Impersonate a user
  2. Act
  3. Stop impersonating
A persistent banner shows the impersonation, and the log records both identities on every action.
ROLE-18Every permission change is written to the audit trailData integrityHighAudit logging is on
  1. Change a role
  2. Open the audit log
An entry names who changed what, for whom, and when. The entry cannot be edited or deleted from the interface.
ROLE-19Deleting a role does not orphan its membersData integrityHighA custom role with members
  1. Delete the role
Either the deletion is refused while members remain, or they fall back to the lowest role. They never keep a permission that no longer exists.
ROLE-20Removing a member revokes their access everywhereData integrityHighA member with an active session
  1. Remove them from the workspace
Their session stops working, their invitations lapse, and their API tokens stop authenticating.
ROLE-21Ownership transfers completelyData integrityMediumTwo admins
  1. Transfer ownership from A to B
B holds every owner-only permission and A holds none. There is no window where both or neither do.
ROLE-22A permission denial explains itself without leakingUsabilityMediumSigned in as a viewer
  1. Open a restricted page
The message says access is needed and who to ask, and does not describe the record that is being withheld.
ROLE-23The permission matrix is a real tableAccessibilityMediumThe role settings page
  1. Inspect the matrix with a screen reader
Header cells are associated with their rows and columns, so a cell is announced with the role and permission it belongs to.
ROLE-24Every permission control is keyboard operableAccessibilityHighThe role settings page
  1. Change a role using only the keyboard
Every control is reachable and operable, and the result is announced.
ROLE-25Role checks do not slow the pagePerformanceLowAn account in many groups
  1. Load a permission-heavy page and time it
50 groupsWithin the documented budget. Permission resolution does not grow linearly with group count on every request.
ROLE-26Two admins editing the same role do not overwrite each otherData integrityMediumTwo admin sessions
  1. Both open the same role
  2. Both save different changes
The second save is either merged or refused with a conflict. It does not silently discard the first.

Case IDs are positional within this set, not stable identifiers. Import the set, then let your own tool assign its IDs.

How to use this set

Build the fixture first. Every case here needs at least two accounts at different levels plus a record that neither of them owns, and half of them need a proxy or an HTTP client to send a request the interface would not let you build.

What we deliberately left out

Signing in, session handling and SSO are covered elsewhere. Field-level and row-level rules inside a single record get their own set, because those are usually expressed in the data layer rather than in the role.

Questions about this set

We only have two roles. Is this overkill?

No. The escalation and horizontal access cases apply to any product with more than one account, and those are the ones that matter.

How do we test the endpoint half without writing code?

Any proxy that lets you replay a captured request works. Sign in as the privileged role, capture the action, then replay it with the unprivileged session cookie or token.

Should the refusal be 403 or 404?

Either, as long as it is consistent. A 404 for records you do not own reveals less, but mixing the two lets someone tell the difference between "does not exist" and "not yours".

Somewhere to keep these once you have run them

Tesbo holds the cases, the runs and the results in one place, so the next release starts from what the last one proved.

Where this coverage comes from