Skip to content

Instantly share code, notes, and snippets.

@moui72
Created November 25, 2025 22:07
Show Gist options
  • Select an option

  • Save moui72/a2bbaa16acaabd08b9291053c8499a72 to your computer and use it in GitHub Desktop.

Select an option

Save moui72/a2bbaa16acaabd08b9291053c8499a72 to your computer and use it in GitHub Desktop.
Permission Testing Plan for arch-2025 branch

Permission Testing Plan: arch-2025 Branch

Overview

This document provides a testing plan for the permission changes in the arch-2025 branch, which replaces scope-based authorization with permission-based authorization.

Change Summary: @RequiredScope decorator → @RequiredPermission decorator


Test User Setup

Create test users with the following role assignments:

User Role Scope Purpose
User A (none) - Baseline - should be denied everywhere
User B Viewer Global Read access to all resources
User C Editor Global Full CRUD access to all resources
User D SurveyTemplateEditor Org: org-123 Survey template management
User E WellnessScoreEditor Org: org-123 Wellness score management
User F RiskClassificationEditor Org: org-123 Risk classification uploads
User G PlatformEditor Org: org-123 Platform management
User H SurveyEditor Org: org-123 Survey management
User I MemberEditor Org: org-123 Member data management

Role Inheritance:

  • All editor roles inherit from Viewer
  • MemberEditor inherits from MemberViewer (which inherits from Viewer)

Database Verification

Before testing, verify the migration has been applied. Connect to your database and run:

-- Verify new roles exist
SELECT name FROM role
WHERE name IN (
  'RiskClassificationEditor', 'SurveyTemplateEditor', 'WellnessScoreEditor',
  'PlatformViewer', 'PlatformEditor', 'SurveyViewer', 'SurveyEditor',
  'MemberViewer', 'MemberEditor'
) ORDER BY name;

-- Verify role inheritance
SELECT child.name AS role, parent.name AS inherits_from
FROM role_inheritance ri
JOIN role parent ON ri.inherited_role_id = parent.id
JOIN role child ON ri.role_id = child.id
WHERE child.name LIKE '%Editor' OR child.name LIKE '%Viewer'
ORDER BY child.name;

Endpoint Tests

For each endpoint, we list:

  • 2XX: Users who should succeed
  • 403: Users who should be forbidden

1. Survey Template Controller

Base Path: /survey-templates

Method Endpoint 2XX (Success) 403 (Forbidden)
GET /survey-templates Anyone (no auth required) -
GET /survey-templates/:id Anyone (no auth required) -
POST /survey-templates User C (Editor), User D (SurveyTemplateEditor) User A, User B (Viewer)
PUT /survey-templates/:id User C (Editor), User D (SurveyTemplateEditor) User A, User B (Viewer)
DELETE /survey-templates/:id User C (Editor), User D (SurveyTemplateEditor) User A, User B (Viewer)

2. Survey Template Section Controller

Base Path: /survey-templates/:surveyTemplateId/sections

Method Endpoint 2XX (Success) 403 (Forbidden)
GET .../sections Anyone (no auth required) -
GET .../sections/:id Anyone (no auth required) -
POST .../sections User C (Editor), User D (SurveyTemplateEditor) User A, User B (Viewer)
PUT .../sections/:id User C (Editor), User D (SurveyTemplateEditor) User A, User B (Viewer)
DELETE .../sections/:id User C (Editor), User D (SurveyTemplateEditor) User A, User B (Viewer)

3. Survey Template Question Controller

Base Path: /survey-templates/:surveyTemplateId/sections/:sectionId/questions

Method Endpoint 2XX (Success) 403 (Forbidden)
GET .../questions Anyone (no auth required) -
GET .../questions/:id Anyone (no auth required) -
POST .../questions User C (Editor), User D (SurveyTemplateEditor) User A, User B (Viewer)
PUT .../questions/:id User C (Editor), User D (SurveyTemplateEditor) User A, User B (Viewer)
DELETE .../questions/:id User C (Editor), User D (SurveyTemplateEditor) User A, User B (Viewer)

4. Risk Classification Controller

Base Path: /risk-classifications

Method Endpoint 2XX (Success) 403 (Forbidden)
POST /risk-classifications/file User C (Editor), User F (RiskClassificationEditor) User A, User B (Viewer)

5. Wellness Score Definition Controller

Base Path: /wellness-scores/definitions

Method Endpoint 2XX (Success) 403 (Forbidden)
GET /wellness-scores/definitions Anyone (no auth required) -
GET /wellness-scores/definitions/:id Anyone (no auth required) -
POST /wellness-scores/definitions User C (Editor), User E (WellnessScoreEditor) User A, User B (Viewer)
PUT /wellness-scores/definitions/:id User C (Editor), User E (WellnessScoreEditor) User A, User B (Viewer)
DELETE /wellness-scores/definitions/:id User C (Editor), User E (WellnessScoreEditor) User A, User B (Viewer)

6. Wellness Category Controller

Base Path: /wellness-scores/definitions/:definitionId/categories

Method Endpoint 2XX (Success) 403 (Forbidden)
GET .../categories Anyone (no auth required) -
GET .../categories/:id Anyone (no auth required) -
POST .../categories User C (Editor), User E (WellnessScoreEditor) User A, User B (Viewer)
PUT .../categories/:id User C (Editor), User E (WellnessScoreEditor) User A, User B (Viewer)
DELETE .../categories/:id User C (Editor), User E (WellnessScoreEditor) User A, User B (Viewer)

7. Wellness Category Rubric Controller

Base Path: /wellness-scores/definitions/:definitionId/categories/:categoryId/rubrics

Method Endpoint 2XX (Success) 403 (Forbidden)
GET .../rubrics Anyone (no auth required) -
GET .../rubrics/:id Anyone (no auth required) -
POST .../rubrics User C (Editor), User E (WellnessScoreEditor) User A, User B (Viewer)
PUT .../rubrics/:id User C (Editor), User E (WellnessScoreEditor) User A, User B (Viewer)
DELETE .../rubrics/:id User C (Editor), User E (WellnessScoreEditor) User A, User B (Viewer)

8. Organization Survey Controller

Base Path: /organizations/:orgId/surveys

Method Endpoint 2XX (Success) 403 (Forbidden)
GET .../surveys User B (Viewer), User C (Editor), User H (SurveyEditor) User A
GET .../surveys/:id User B (Viewer), User C (Editor), User H (SurveyEditor) User A
POST .../surveys User C (Editor), User H (SurveyEditor) User A, User B (Viewer)
PUT .../surveys/:id User C (Editor), User H (SurveyEditor) User A, User B (Viewer)
DELETE .../surveys/:id User C (Editor), User H (SurveyEditor) User A, User B (Viewer)

9. Platform Controller

Base Path: /platforms

Method Endpoint 2XX (Success) 403 (Forbidden)
GET /platforms User B (Viewer), User C (Editor), User G (PlatformEditor) User A
GET /platforms/:id User B (Viewer), User C (Editor), User G (PlatformEditor) User A
POST /platforms User C (Editor), User G (PlatformEditor) User A, User B (Viewer)
PUT /platforms/:id User C (Editor), User G (PlatformEditor) User A, User B (Viewer)
DELETE /platforms/:id User C (Editor), User G (PlatformEditor) User A, User B (Viewer)

10. Provider Search Controller

Base Path: /provider-search

Method Endpoint 2XX (Success) 403 (Forbidden)
GET /provider-search/members/:id/search-capabilities User B (Viewer), User C (Editor) User A
GET /provider-search/members/:id/specialties User B (Viewer), User C (Editor) User A
PUT /provider-search/members/:id/search User B (Viewer), User C (Editor) User A

11. Member Address Controller (Admin)

Base Path: /members/:memberId/addresses

Method Endpoint 2XX (Success) 403 (Forbidden)
GET .../addresses User B (Viewer), User C (Editor), User I (MemberEditor) User A
POST .../addresses User C (Editor), User I (MemberEditor) User A, User B (Viewer)
PUT .../addresses/:id User C (Editor), User I (MemberEditor) User A, User B (Viewer)
DELETE .../addresses/:id User C (Editor), User I (MemberEditor) User A, User B (Viewer)

12. Member Communication Preferences Controller (Admin)

Base Path: /members/:memberId/communication-preferences

Method Endpoint 2XX (Success) 403 (Forbidden)
GET .../communication-preferences User B (Viewer), User C (Editor), User I (MemberEditor) User A
POST .../communication-preferences User C (Editor), User I (MemberEditor) User A, User B (Viewer)
PUT .../communication-preferences/:id User C (Editor), User I (MemberEditor) User A, User B (Viewer)
DELETE .../communication-preferences/:id User C (Editor), User I (MemberEditor) User A, User B (Viewer)

13. Member Platform Controller (Admin)

Base Path: /platforms/:platformId/members

Method Endpoint 2XX (Success) 403 (Forbidden)
GET .../members User B (Viewer), User C (Editor), User I (MemberEditor) User A
GET .../members/:id User B (Viewer), User C (Editor), User I (MemberEditor) User A
POST .../members User C (Editor), User I (MemberEditor) User A, User B (Viewer)
PUT .../members/:id User C (Editor), User I (MemberEditor) User A, User B (Viewer)
DELETE .../members/:id User C (Editor), User I (MemberEditor) User A, User B (Viewer)

14. Member Mapping Controller (API)

Base Path: /members/:externalId/mappings

Method Endpoint 2XX (Success) 403 (Forbidden)
GET .../mappings User B (Viewer), User C (Editor), User I (MemberEditor) User A

Test Checklist

Role-Based Access

  • User A (no role) is denied on all protected endpoints
  • User B (Viewer) can read but not write
  • User C (Editor) can read and write everything
  • User D (SurveyTemplateEditor) can only write survey templates/sections/questions
  • User E (WellnessScoreEditor) can only write wellness scores/categories/rubrics
  • User F (RiskClassificationEditor) can only upload risk classification files
  • User G (PlatformEditor) can only write platforms
  • User H (SurveyEditor) can only write surveys
  • User I (MemberEditor) can only write member data

Role Inheritance

  • SurveyTemplateEditor inherits Viewer (can read surveys, platforms, etc.)
  • WellnessScoreEditor inherits Viewer
  • RiskClassificationEditor inherits Viewer
  • PlatformEditor inherits Viewer
  • SurveyEditor inherits Viewer
  • MemberEditor inherits MemberViewer inherits Viewer

Edge Cases

  • Expired tokens return 401
  • Missing auth header returns 401 (on protected endpoints)
  • Invalid tokens return 401
  • Non-existent resources return 404 (not 403)

Troubleshooting

403 when expecting 200:

-- Check user's role assignments
SELECT r.name, ur.is_global, ur.organization_id
FROM user_role ur
JOIN role r ON ur.role_id = r.id
WHERE ur.user_id = '<user-id>';

-- Check role's permissions
SELECT p.name
FROM role_permission rp
JOIN permission p ON rp.permission_id = p.id
WHERE rp.role_id = (SELECT id FROM role WHERE name = '<role-name>');

401 errors: Verify token is valid and not expired

500 errors: Check API logs; may indicate migration wasn't applied

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment