Users Management Facility Scoping — QA Guide
Scope: Smoke test for the facility selector on Shell's
/account/userspage and the new CMS native users page at/settings/users(shipped in #1429).Prerequisites: Staging access (or local dev with the backend on
:3000and Shell on:3000, CMS lazy-loaded). A multi-facility tenant with at least one active practitioner role at each facility — seed via/account/usersinvites if missing.Related: Users management developer guide · Access control overview
1. Test environment setup
1.1 Tenants and facilities
You need:
- One tenant with two facilities (e.g. "Acme Health" with "Acme Downtown" + "Acme Westside").
- At least one practitioner with an active
PractitionerRoleat each facility. - At least one staff with an active
StaffRoleat one of the facilities (for the Staff tab path). - Optional: a second tenant with its own facility, to verify cross-tenant
facility_idreturns 404.
If staging doesn't have this, seed via Shell /account/users → Doctors tab → Invite Practitioner, repeated for each facility.
1.2 Users
Log in as:
owneroradminof the multi-facility tenant (to see all three tabs in Shell, and Staff in CMS).- For role-gating spot-checks: a
practitionerat the tenant (Staff/Stakeholders tabs in Shell should be hidden; CMS only shows Practitioners).
2. Shell — /account/users
2.1 Facility selector renders for multi-facility tenants
- Navigate to
/account/users. - Verify: a "Facility" dropdown appears above the four stats cards.
- Verify: dropdown contains "All Facilities" (default) plus one entry per facility in the tenant.
2.2 "All Facilities" default — tenant-wide view
- With "All Facilities" selected, verify:
- Stats cards show counts across the entire tenant.
- Doctors tab lists practitioners from every facility.
- Pending invites (visible at the bottom of Doctors / Staff tabs) include invites from any facility.
2.3 Specific facility — scoped view
- Switch the selector to Facility A. Verify:
- Stats drop to Facility A counts.
- Doctors tab lists only practitioners with an active role at Facility A.
- If you're on a later page of results, pagination resets to page 1.
- Switch to Facility B. Verify the same pattern with a different roster.
2.4 Stakeholders tab when facility is selected
- With Facility A selected, click the Stakeholders tab. Verify: empty list (stakeholders are tenant-scoped, not facility-scoped; backend returns
[]forrole=stakeholder&facility_id=...).
2.5 Role-gating (spot check)
- Log out, log in as a
practitioner. Navigate to/account/users. Verify: Staff and Stakeholders tabs are hidden — only Doctors is visible.
3. CMS — /settings/users
3.1 Page loads natively, not a redirect
- Log back in as
owner/admin. Enter the CMS app, ensure the active facility is Facility A. - Navigate to
/settings/users(Settings → Users in the sidebar). Verify:- The URL stays at
/cms/settings/users(does NOT redirect to/account/users). - The page renders with two tabs: Practitioners and Staff. There is no Stakeholders tab — stakeholders are tenant-scoped and managed in Shell only.
- Stats cards reflect Facility A counts (not tenant-wide).
- The URL stays at
3.2 Tab clicks stay inside CMS
- Click the Staff tab. Verify:
- URL becomes
/cms/settings/users/staff(NOT/account/users/staff— a regression bug we shipped + fixed during QA). - You stay on the CMS page; the Shell layout does not take over.
- URL becomes
- Click back to Practitioners. URL becomes
/cms/settings/users/practitioners. Same containment.
3.3 Facility switch updates the view
- Via the CMS clinic switcher (or Shell facility switcher, then re-enter CMS), switch the active facility to Facility B.
- Re-open
/settings/users. Verify: Practitioners + Staff lists now reflect Facility B's roster, not Facility A.
3.4 No-facility guard
- If the test account has any state where the JWT lacks
currentFacilityId(rare; usually only mid-switch or for a tenant with no facilities),/settings/usersshould render the "Select a facility from the clinic switcher to see users here" message instead of loading data. - Verify in DevTools network tab: when the guard is showing, no
/identity/usersor/identity/practitioner_invitesor/identity/staff_invitesrequest fires.
3.5 Role-gating
- As a
practitionerat the tenant, navigate to/settings/users. Verify:- Only the Practitioners tab is visible.
- No Staff tab.
4. Backend API spot-checks
Optional but useful for confirming the query shape is right. Use the clinician JWT.
4.1 Users endpoint
# Practitioners at a specific facility
curl -H "Authorization: Bearer $CLINICIAN_TOKEN" \
"$API_BASE/identity/users?role=practitioner&facility_id=$FACILITY_A_ID"
# Owner/admin with facility_id → empty list (tenant-scoped role)
curl -H "Authorization: Bearer $CLINICIAN_TOKEN" \
"$API_BASE/identity/users?role=admin&facility_id=$FACILITY_A_ID"
# Expect: { "data": [], ... }
# Cross-tenant facility_id → empty list (defense in depth)
curl -H "Authorization: Bearer $CLINICIAN_TOKEN" \
"$API_BASE/identity/users?role=practitioner&facility_id=$OTHER_TENANT_FACILITY_ID"
# Expect: { "data": [], ... }4.2 Invites endpoints
# Default — current_facility from JWT
curl -H "Authorization: Bearer $CLINICIAN_TOKEN" \
"$API_BASE/identity/practitioner_invites?status=pending"
# Specific facility
curl -H "Authorization: Bearer $CLINICIAN_TOKEN" \
"$API_BASE/identity/practitioner_invites?facility_id=$FACILITY_A_ID&status=pending"
# Tenant-wide aggregation
curl -H "Authorization: Bearer $CLINICIAN_TOKEN" \
"$API_BASE/identity/practitioner_invites?all_facilities=true&status=pending"
# Cross-tenant facility_id → 404
curl -i -H "Authorization: Bearer $CLINICIAN_TOKEN" \
"$API_BASE/identity/practitioner_invites?facility_id=$OTHER_TENANT_FACILITY_ID"
# Expect: HTTP/1.1 404 Not FoundSame shape for staff_invites — substitute the path.
5. Regression checks (single-facility tenant)
A single-facility tenant must keep working without showing a no-op selector.
- Log in as
ownerof a tenant with exactly one facility. - Navigate to
/account/users. Verify:- No facility dropdown — the selector is hidden, not disabled.
- Stats and lists show normally.
- Navigate to
/cms/settings/users. Verify:- Page loads, tabs visible, scoped to the one facility.
6. Network-tab sanity (CMS)
In DevTools → Network, with /cms/settings/users loaded and Facility A active:
- Every
/identity/usersrequest: query string includesfacility_id=<facility-a-uuid>. - Every
/identity/practitioner_invitesand/identity/staff_invitesrequest: query string includesfacility_id=<facility-a-uuid>. - No request fires with
all_facilities=trueor withoutfacility_id— CMS is facility-locked, never tenant-wide.
7. Sign-off checklist
- §2.1–2.5: Shell facility selector works (default, switch, tab visibility, role-gating).
- §3.1–3.5: CMS native page works (loads, tab clicks stay in /cms, facility switch updates, guard renders, role-gating).
- §4: Backend endpoints behave per spec (specific facility, tenant-wide aggregation, cross-tenant 404, owner/admin returns empty).
- §5: Single-facility tenant has no selector and works normally.
- §6: CMS network tab shows facility-scoped requests only.
If anything fails, file a follow-up with the failing step number and a screenshot / cURL output.