From f66cd2933ab1032c96e0263c6f5ed7c46ddd71db Mon Sep 17 00:00:00 2001 From: Moritz Date: Thu, 29 Jan 2026 23:56:18 +0100 Subject: [PATCH] docs: add page permission route and test coverage - page-permission-route-coverage.md: route matrix, test coverage per role, reserved segments. --- docs/page-permission-route-coverage.md | 88 ++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 docs/page-permission-route-coverage.md diff --git a/docs/page-permission-route-coverage.md b/docs/page-permission-route-coverage.md new file mode 100644 index 0000000..7eb9a6d --- /dev/null +++ b/docs/page-permission-route-coverage.md @@ -0,0 +1,88 @@ +# Page Permission – Route and Test Coverage + +This document lists all protected routes, which permission set may access them, and how they are covered by tests. + +## Protected Routes (Router scope with CheckPagePermission in :browser) + +| Route | own_data | read_only | normal_user | admin | +|-------|----------|-----------|-------------|-------| +| `/` | ✗ | ✓ | ✓ | ✓ | +| `/members` | ✗ | ✓ | ✓ | ✓ | +| `/members/new` | ✗ | ✗ | ✓ | ✓ | +| `/members/:id` | ✓ (linked only) | ✓ | ✓ | ✓ | +| `/members/:id/edit` | ✗ | ✗ | ✓ | ✓ | +| `/members/:id/show/edit` | ✗ | ✗ | ✓ | ✓ | +| `/users` | ✗ | ✗ | ✗ | ✓ | +| `/users/new` | ✗ | ✗ | ✗ | ✓ | +| `/users/:id` | ✓ (own only) | ✗ | ✗ | ✓ | +| `/users/:id/edit` | ✗ | ✗ | ✗ | ✓ | +| `/users/:id/show/edit` | ✗ | ✗ | ✗ | ✓ | +| `/settings` | ✗ | ✗ | ✗ | ✓ | +| `/membership_fee_settings` | ✗ | ✗ | ✗ | ✓ | +| `/membership_fee_types` | ✗ | ✗ | ✗ | ✓ | +| `/membership_fee_types/new` | ✗ | ✗ | ✗ | ✓ | +| `/membership_fee_types/:id/edit` | ✗ | ✗ | ✗ | ✓ | +| `/groups` | ✗ | ✓ | ✓ | ✓ | +| `/groups/new` | ✗ | ✗ | ✗ | ✓ | +| `/groups/:slug` | ✗ | ✓ | ✓ | ✓ | +| `/groups/:slug/edit` | ✗ | ✗ | ✗ | ✓ | +| `/admin/roles` | ✗ | ✗ | ✗ | ✓ | +| `/admin/roles/new` | ✗ | ✗ | ✗ | ✓ | +| `/admin/roles/:id` | ✗ | ✗ | ✗ | ✓ | +| `/admin/roles/:id/edit` | ✗ | ✗ | ✗ | ✓ | + +**Note:** Permission sets define `/custom_field_values` and related paths, but there are no such routes in the router; those entries are for future use. + +## Public Paths (no permission check) + +- `/auth*`, `/register`, `/reset`, `/sign-in`, `/sign-out`, `/confirm*`, `/password-reset*`, `/set_locale` + +## Test Coverage + +**File:** `test/mv_web/plugs/check_page_permission_test.exs` + +### Unit tests (plug called directly with mock conn) + +- Static: own_data denied `/members`; read_only allowed `/members`; flash on denial. +- Dynamic: read_only allowed `/members/123`; normal_user allowed `/members/456/edit`; read_only denied `/members/123/edit`. +- read_only / normal_user: denied `/admin/roles`; read_only denied `/members/new`. +- Wildcard: admin allowed `/admin/roles`, `/members/999/edit`. +- Unauthenticated: nil user denied, redirect `/sign-in`. +- Public: unauthenticated allowed `/auth/sign-in`, `/register`. +- Error: no role, invalid permission_set_name → denied. + +### Integration tests (full router, Mitglied = own_data) + +**Denied (Mitglied gets 302 → `/users/:id`):** + +- `/members`, `/members/new`, `/users`, `/users/new`, `/settings`, `/membership_fee_settings`, `/membership_fee_types`, `/membership_fee_types/new`, `/groups`, `/groups/new`, `/admin/roles`, `/admin/roles/new` +- `/members/:id/edit`, `/members/:id/show/edit`, `/users/:id` (other user), `/users/:id/edit` (other), `/users/:id/show/edit` (other), `/membership_fee_types/:id/edit`, `/groups/:slug`, `/admin/roles/:id`, `/admin/roles/:id/edit` + +**Allowed (Mitglied gets 200):** + +- `/users/:id` (own profile), `/users/:id/edit`, `/users/:id/show/edit` +- `/members/:id`, `/members/:id/edit`, `/members/:id/show/edit` for linked member (plug unit tests; full-router tests for linked member skipped: session/LiveView constraints) + +**Root:** `GET /` redirects Mitglied to profile (root not allowed for own_data). + +All protected routes above are either covered by integration “denied” tests for Mitglied or by unit tests for the relevant permission set. + +### Integration tests (full router, read_only = Vorstand/Buchhaltung) + +**Allowed (200):** `/`, `/members`, `/members/:id`, `/groups`, `/groups/:slug`. + +**Denied (302 → `/users/:id`):** `/members/new`, `/members/:id/edit`, `/users`, `/users/new`, `/settings`, `/membership_fee_settings`, `/membership_fee_types`, `/groups/new`, `/groups/:slug/edit`, `/admin/roles`, `/admin/roles/:id`. + +### Integration tests (full router, normal_user = Kassenwart) + +**Allowed (200):** `/`, `/members`, `/members/new`, `/members/:id`, `/members/:id/edit`, `/groups`, `/groups/:slug`. + +**Denied (302 → `/users/:id`):** `/users`, `/users/new`, `/users/:id` (other user), `/settings`, `/membership_fee_settings`, `/membership_fee_types`, `/groups/new`, `/groups/:slug/edit`, `/admin/roles`, `/admin/roles/:id`. + +### Integration tests (full router, admin) + +**Allowed (200):** All protected routes (sample covered: `/`, `/members`, `/users`, `/settings`, `/membership_fee_settings`, `/admin/roles`, `/members/:id`, `/admin/roles/:id`, `/groups/:slug`). + +## Plug behaviour: reserved segments + +The plug treats `"new"` as a reserved path segment so that patterns like `/members/:id` and `/groups/:slug` do not match `/members/new` or `/groups/new`. Thus `/groups/new` is only allowed when the permission set explicitly lists `/groups/new` (currently only admin).