Skip to content

QA Test Scenarios

This document is FINAL and ENFORCEABLE for QA, UAT, and access-control validation.

It converts the architecture, access matrix, middleware, cache rules, admin flows, and migration assumptions into concrete test scenarios.

It complements:

  • 2.4.-Access-Control-Integration.md
  • 2.5.-Access-Matrix.md
  • 2.6.-Middleware-Implementation.md
  • 2.7.-Access-Caching-Strategy.md
  • 2.8.-Admin-Platform-Spec.md
  • 2.9.-Migration-Plan-Mongo-to-Auth-Core.md
  • 3.-Frontend-Implementation.md
  • 3.1.-Frontend-Tasks-Breakdown.md

This document defines:

  • test cases
  • expected responses
  • expected UI outcomes
  • edge cases
  • failure behavior
  • regression scenarios

It is designed so QA does not need to infer the access model from architecture docs.


A tenant-scoped request is allowed only if all are true:

  1. JWT is valid
  2. x-org is present and valid
  3. user has valid membership in the company
  4. company owns the module
  5. membership is granted the module
  6. membership has the required permission
  7. Auth access resolution is available

StatusMeaningTypical QA interpretation
200 / 201SuccessRequest allowed
400Bad requestMissing or invalid request context, usually x-org
401UnauthorizedMissing/invalid/expired token
403ForbiddenAuthenticated, but access denied
404Not foundResource not found or intentionally hidden depending on endpoint policy
409ConflictBusiness conflict, not access issue
503Service unavailableAuth/Core/dependency not available for access resolution

QA should have the following reusable test fixtures.

Create at minimum:

  • Company A → Basic + Finance + Market
  • Company B → Finance only
  • Company C → Basic only
  • Company D → no active entitlements
  • Company E → Venue + AI only

Create at minimum:

  • Platform Admin User
  • Tenant Superadmin User
  • Admin User
  • Finance User
  • Manager User
  • Submitter/User
  • User with no membership
  • Vendor User (if vendor routes tested)

At minimum, prepare:

  • User 1 → Company A → Basic + Finance + Market
  • User 2 → Company A → Finance only
  • User 3 → Company A → Basic only
  • User 4 → Company A → Finance permission revoked
  • User 5 → Company B → Finance only
  • User 6 → Company C → Basic only
  • User 7 → Company D → no modules
  • User 8 → Company E → Venue only
  • User 9 → Company E → AI only

Use permission keys such as:

  • basic.dashboard.view
  • basic.event.view
  • basic.event.create
  • finance.expense.view
  • finance.expense.create
  • finance.expense.edit
  • market.contract.view
  • market.contract.approve
  • venue.calendar.view
  • ai.chat.use

User logs in successfully and frontend loads access.

  1. login with valid credentials
  2. receive JWT + refresh token
  3. call /auth/me
  4. select company
  5. call /auth/me/access with x-org
  • login returns 200
  • /auth/me returns 200
  • /auth/me/access returns 200
  • frontend receives effective modules and permissions
  • module tiles render according to effective access

User has Basic through company entitlement and membership grant.

  • company owns Basic
  • membership granted Basic
  • user has basic.dashboard.view
GET /api/basic/dashboard
Authorization: Bearer <token>
x-org: <companyId>
  • 200
  • backend executes
  • frontend shows Basic module

User has Finance entitlement and membership grant.

  • company owns Finance
  • membership granted Finance
  • user has finance.expense.view
GET /api/finance/expenses
Authorization: Bearer <token>
x-org: <companyId>
  • 200
  • finance page loads
  • create button shown only if finance.expense.create also exists

Company has Finance only, no Basic.

  • company owns Finance only
  • membership granted Finance
  • no Basic entitlement
  • Core App hidden
  • Finance module visible
  • finance routes still succeed
  • Basic routes return 403

No Authorization header

  • backend returns 401
  • frontend redirects to login or shows unauthenticated state

Malformed bearer token

  • 401
  • error code indicates unauthorized
  • request not executed

  • initial request returns 401
  • frontend attempts refresh if flow supports it
  • if refresh succeeds, request may retry
  • if refresh fails, user is logged out

  • 401
  • request denied before access resolution

Tenant request without x-org

  • 400
  • error clearly indicates missing x-org
  • backend must not infer default company

  • 400
  • request rejected before business logic

User switches from Company A to Company B

  1. current access loaded for Company A
  2. switch active company
  3. frontend calls /auth/me/access again with new x-org
  • old access state discarded
  • new effective modules loaded
  • visible modules update immediately
  • subsequent requests carry new x-org

  • valid user
  • valid token
  • user has no membership in selected company
  • /auth/me/access or backend access resolution returns 403
  • backend route returns 403
  • frontend shows access denied / company not accessible

  • access allowed only if membership has module
  • role alone must not auto-grant every module if architecture forbids auto-grant

8.3 Finance role without Finance module grant

Section titled “8.3 Finance role without Finance module grant”
  • user role indicates finance-oriented scope
  • company owns Finance
  • membership NOT granted Finance
  • Finance module hidden
  • Finance route returns 403

  • user is low-role / submitter
  • company owns Finance
  • membership explicitly granted Finance
  • permission exists
  • Finance module allowed
  • only permitted actions available
  • role does not block basic usage if grant exists

  • membership granted module
  • company does NOT own module
  • effective access excludes module
  • frontend hides module
  • backend route returns 403

9.2 Entitlement removed after previous access existed

Section titled “9.2 Entitlement removed after previous access existed”

Company previously had Finance, then Finance removed.

  1. user has cached Finance access
  2. admin removes Finance add-on
  3. entitlementVersion changes
  4. next request happens
  • stale cache not reused
  • access rebuilt
  • Finance removed from effective modules
  • route returns 403
  • frontend refresh removes Finance UI

9.3 Basic removed but standalone module remains

Section titled “9.3 Basic removed but standalone module remains”
  • company loses Basic
  • company still owns Finance
  • Core App hidden
  • Finance remains accessible if membership granted
  • Basic endpoints return 403

  • module allowed
  • permission present
  • route returns 200
  • UI action visible

  • module allowed
  • permission absent
  • route returns 403
  • UI action hidden if frontend refreshed correctly

Permission existed, then removed.

  1. user loads page with permission
  2. admin revokes permission
  3. accessVersion changes
  4. user retries action
  • stale access invalidated
  • next protected action returns 403
  • frontend refresh hides action

This is a critical regression scenario.


10.4 Permission belongs to disabled module

Section titled “10.4 Permission belongs to disabled module”
  • permission exists in raw assignment
  • module not effective
  • permission must not grant access by itself
  • backend denies route with 403

  • success
  • accessVersion changes
  • affected user cache invalidated

11.2 Admin tries to grant module outside delegation scope

Section titled “11.2 Admin tries to grant module outside delegation scope”
  • 403
  • no DB change
  • no accessVersion bump for target user

11.3 Manager tries to grant unauthorized permission

Section titled “11.3 Manager tries to grant unauthorized permission”
  • 403
  • audit entry created if policy requires
  • no change persisted

11.4 User without delegation attempts user-management endpoint

Section titled “11.4 User without delegation attempts user-management endpoint”
  • 403

  • identical user/company/version inputs
  • access already cached
  • Auth returns cached access successfully
  • response still correct
  • no stale mismatch

12.2 tokenVersion change invalidates cache

Section titled “12.2 tokenVersion change invalidates cache”
  1. user has cached access
  2. tokenVersion increases (logout-all / session reset)
  3. same route requested
  • old key not reused
  • access rebuilt
  • request succeeds only if new session valid

12.3 entitlementVersion change invalidates cache

Section titled “12.3 entitlementVersion change invalidates cache”
  1. company entitlement changes
  2. entitlementVersion increments
  3. request executed
  • cache miss / rebuild
  • new access reflects entitlement change

12.4 accessVersion change invalidates cache

Section titled “12.4 accessVersion change invalidates cache”
  1. membership permissions change
  2. accessVersion increments
  3. request executed
  • old cached access not reused
  • rebuilt access reflects new grants

12.5 Redis unavailable but recompute succeeds

Section titled “12.5 Redis unavailable but recompute succeeds”
  • request still succeeds
  • no incorrect 403/503 if recomputation is possible

12.6 Redis unavailable and recompute fails

Section titled “12.6 Redis unavailable and recompute fails”
  • 503
  • request denied
  • fail-closed respected

  • protected backend routes return 503
  • no optimistic allow
  • frontend shows temporary unavailable state

This is a required edge case.


13.2 Core unavailable during access resolution

Section titled “13.2 Core unavailable during access resolution”
  • Auth cannot resolve entitlements
  • Auth returns failure
  • backend returns 503
  • frontend shows retry state

This is a required edge case.


  • backend treats access resolution as failed
  • returns 503
  • no fallback to guessed access

  • module tile absent
  • direct route attempt denied or redirected

UI still shows button briefly after revoke.

  • backend action returns 403
  • frontend refreshes access
  • button disappears

  • module menu updates
  • route guards update
  • stale screens are not preserved incorrectly

  • clear UX message
  • no generic crash
  • no misleading “not found” unless intentionally designed

  • package created in Core
  • visible in admin catalog
  • no direct user access change until company assigned

  • entitlementVersion increments
  • company cache invalidation triggered
  • users see module after next access rebuild only if granted

  • accessVersion changes for affected users if model requires it
  • UI controls update after refresh
  • unauthorized grants now blocked

  • approved company becomes active
  • users can load company after proper membership/access exists

  • unauthorized or forbidden according to vendor auth model

  • denied
  • vendor auth must not satisfy tenant access model

  • no JWT needed
  • no x-org needed
  • protected data not exposed

  • membership joins work
  • access resolution works
  • old Mongo identity is no longer required at runtime

  • canonical company id used in x-org
  • no mixed legacy company identifiers in active flows

  • access decisions no longer rely on legacy backend-local permission truth
  • Auth/Core model is authoritative

Use this format for execution tracking:

Example: QA-ACCESS-001

Example: Missing x-org on tenant route returns 400

  • user exists
  • route is tenant-scoped
  1. send request without x-org
  • HTTP 400
  • no business logic execution
  • mapped to section 7.1

  • valid user
  • valid token
  • tenant route /api/finance/expenses
  1. send GET request without x-org
  • status 400
  • body indicates validation error
  • no downstream DB logic executed

  • user previously had finance.expense.create
  • company owns Finance
  • membership has Finance
  • permission revoked before request
  1. user clicks Create Expense
  2. frontend sends request
  3. backend resolves fresh access
  • status 403
  • no create occurs
  • frontend refreshes access and hides create action

  • company previously had Market
  • Market entitlement removed
  • user still has stale UI state
  1. user opens Market page or sends Market API request
  • fresh access resolution excludes Market
  • status 403
  • frontend removes Market module after refresh

  • backend depends on Auth for access
  • Auth unavailable
  1. send protected request
  • status 503
  • request denied
  • no optimistic allow

QA must always validate the full chain:
JWT → x-org → membership → entitlement → module → permission → dependency health

A request is only truly valid when identity, company context, entitlements, grants, permissions, and dependency health all succeed together.