Files
RenewCMS/features/roles_permissions_foundation.qa.md
T

5.6 KiB

QA procedure: roles_permissions_foundation

Superseded in part by roles_permissions_enforcement: the owner and admin 403 clauses now answer 403 "missing_permission" with the header X-Missing-Permission, decided by the caller's role permissions. An Admin can now invite and write sites, and a role without the needed permission is refused on routes that were open to every verified user.

Package: test (black-box). Name new fakes uniquely, for example mockRoleRepo, rolesFoundationUserRepo. Do not copy the old Role == checks into new code.

Deferred to later slices

  • 2a: permission-based route and middleware checks, API keys on the creator's live role, admin relaxations, 403 missing_permission, Writer effective.
  • 2b: own/all enforcement after loading the item, publish separate from update, Draft and "Ask an editor to publish."
  • 3: /v1/role CRUD, reassign, no-escalation, role_locked, role_has_members, role_name_taken, role_conflict, per-type content override keys content.<action>:<type_key>, role events.
  • 4: PUT /v1/user/{id}/role, invite role (default Writer), transfer ownership, protected owner, removal of the legacy role string.
  • 5-7: all SPA states in docs/design/screens/roles.md.

Design note for the user

The "Owner-only items" state in docs/design/screens/roles.md disables View roles and Manage roles for an admin. This slice follows the user's decision that Admin keeps roles.view and roles.manage (29 of 34 keys), matching the catalogue table. roles.md needs a design fix by the user; no stage edits docs/design.

Test infrastructure needed first

  1. mockRoleRepo implementing the role domain Repository in memory. Seed it with the three built-ins through the same Go function the migration uses, or assert it matches the Background tables.
  2. Extend the user fake with RoleID, and the article fake with CreatedBy.
  3. The persistence tests use an in-memory SQLite DSN (as TestBackfillUserRoles in test/user_roles_test.go), never a file. Register models.Role, models.RolePermission, models.User, models.Article and models.Entry via database.Migrate where the whole sequence is asserted, and via targeted AutoMigrate for single-backfill tests.

A. Domain (pure Go, no HTTP, no GORM)

  1. Table-test Role.Allows with the Examples of "Role.Allows evaluates scope and ownership", plus the absent-key and unknown-key cases. Assert errors.Is(err, role.ErrUnknownPermission) for the lookup.
  2. Catalogue test: length 34, group order, key order, the 5 owner-only keys, the 9 scoped keys, and per_type only for the 5 content keys. A unique-keys check guards against duplicates.
  3. A hygiene test that internal/domain/role imports only the stdlib (grep the import list, as in the existing *_hygiene_test.go files).

B. Persistence (in-memory SQLite)

  1. Seed on an empty DB: assert 3 roles, flags, versions, and the 34 / 29 / 9 grant counts and scopes. Run again: still 3 roles with the same ids and 72 role_permission rows.
  2. Edited built-in: bump Writer's version to 4 and add media.delete=own, seed again, assert untouched.
  3. Name collision: pre-insert a custom "Writer" (built_in false), seed, assert no error and the row is unchanged.
  4. BackfillUserRoleIDs: cases for owner/admin mapping, the legacy string unchanged, an existing role_id kept, double run, empty DB.
  5. Full database.Migrate on a DB created with the old schema (users.role = "", no role_id): assert ordering (legacy role first, then role_id).
  6. BackfillCreatedBy: articles and entries with 0, entries with an existing value, double run, no owner.
  7. Portability: assert there is no raw SQL specific to SQLite in the new repositories (code review item); MySQL and PostgreSQL are not exercised here.

C. Use cases (fakes only)

  1. GetMyAccessUseCase: owner, admin, writer, custom role, legacy fallback (role_id 0 with "owner" and "admin"), live change after a grant edit, unresolvable role (expect a non-sentinel error).
  2. ListPermissionsUseCase: returns the domain catalogue unchanged.
  3. CreateArticleUseCase sets CreatedBy to the acting user id.

D. HTTP (httptest, fresh chi.NewRouter() mirroring api/router.go)

  1. Call validator.InitValidator(). Use the token helper the current /me tests use. Mint cookies as get_current_user.qa.md step 7 does.
  2. Mount /v1/auth/me as in NewAuthRouter (Verifier, apiKeyAuth, Authenticator, RequireCurrentToken) with auth.NewMeHandler(getUser, getMyAuthor, getMyAccess). Re-run all get_current_user cases unchanged (additive field) and add the access cases: owner, admin, writer, custom, fallback, live change, unverified, unresolvable role (500).
  3. Mount /v1/permission as in the new NewPermissionRouter (adds RequireVerifiedUser). Cases: Writer 200 with the full shape, Owner and Writer bodies equal, API key 200, no cookie 401, stale token 401, unverified 403.
  4. "Unchanged behaviour" Outline: build the existing NewUserRouter, NewSettingsRouter, NewContentTypeRouter and NewSystemRouter groups over users carrying role_id, and assert the statuses are as before. The simplest source is the existing router tests; add a seeded-role_id variant.

Manual verification

  • Start a dev build (task dev:api) on a copy of a database created before this change. Check that the startup migration succeeds, that no data is lost, and that GET /v1/auth/me and GET /v1/permission respond for the existing owner and admin.
  • MySQL and PostgreSQL migration of the new tables is a manual item; the repo has no CI and no automated run for it.
  • The SPA is untouched in this slice. SPA states in docs/design/screens/roles.md are informational.