/** * The staff access policy the proxy enforces (src/features/auth/services/ * staffAccess.ts). Run with `npm run test:access` — Node's built-in runner, no * dependencies. Covers the role matrix only; tenant isolation is the * platform's (the company comes from the upstream token, never the request). */ import {test} from 'node:test'; import assert from 'node:assert/strict'; import { STAFF_HOME, isStaffPage, isStaffRole, staffApiDecision, } from '../src/features/auth/services/staffAccess.ts'; const q = (o: Record = {}) => new URLSearchParams(o); const one = q({storeId: 'chennai', range: '30d'}); test('only role=staff is gated', () => { assert.equal(isStaffRole('staff'), true); for (const r of ['owner', 'manager', 'admin', undefined, '', 'STAFF']) { assert.equal(isStaffRole(r), false, String(r)); } }); test('staff home is a staff page', () => { assert.equal(isStaffPage(STAFF_HOME), true); }); test('staff pages: floor work allowed, merchant pages refused', () => { for (const p of ['/floor', '/customers', '/customers/abc', '/activity', '/settings/profile', '/settings/security']) { assert.equal(isStaffPage(p), true, p); } for (const p of ['/dashboard', '/commerce', '/stores', '/lyts', '/staff', '/settings', '/settings/team', '/settings/billing', '/settings/roles', '/settings/stores', '/admin', '/floorplan', '/customersX']) { assert.equal(isStaffPage(p), false, p); } }); test('staff APIs: floor work allowed with one store', () => { const allow: [string, string, URLSearchParams][] = [ ['GET', '/api/sites', q()], ['GET', '/api/floor/visits', one], ['POST', '/api/visits/v1/attend', q()], ['POST', '/api/visits/v1/release', q()], ['POST', '/api/visits/v1/complete', q()], ['GET', '/api/visits', one], ['GET', '/api/visits/stream', q({storeId: 'chennai'})], ['POST', '/api/customers', q()], ['GET', '/api/visitors', q()], ['GET', '/api/visitors/x/history', q()], ['GET', '/api/visitors/x/image', q()], ['PUT', '/api/visitors/x/profile', q()], ['GET', '/api/faces', q({src: '/api/faces/a'})], ['POST', '/api/sales', q()], ['POST', '/api/purchases', q()], ['GET', '/api/health', q()], ]; for (const [m, p, s] of allow) assert.equal(staffApiDecision(m, p, s), 'allow', `${m} ${p}`); }); test('staff APIs: "All stores" or no store is refused on scoped reads', () => { for (const p of ['/api/floor/visits', '/api/visits', '/api/visits/stream']) { assert.equal(staffApiDecision('GET', p, q({storeId: 'all'})), 'needs_store', p); assert.equal(staffApiDecision('GET', p, q()), 'needs_store', p); } }); test('staff APIs: merchant-only surfaces are forbidden', () => { const deny: [string, string][] = [ ['GET', '/api/reports/footfall'], ['GET', '/api/reports/conversion'], ['GET', '/api/reports/journey'], ['GET', '/api/dashboard/summary'], ['GET', '/api/sales'], ['GET', '/api/sales/abc'], ['GET', '/api/team'], ['GET', '/api/team/invitations'], ['GET', '/api/cameras'], ['GET', '/api/cameras/c1/live'], ['GET', '/api/images'], ['GET', '/api/campaigns'], ['GET', '/api/activities'], ['POST', '/api/assistant'], ['POST', '/api/sites'], ['PATCH', '/api/sites/chennai'], ['DELETE', '/api/sites/chennai'], ['DELETE', '/api/visitors/x'], ['GET', '/api/admin/clients'], ['GET', '/api/unknown-new-route'], // Method matters: an allowed path with the wrong verb is refused. ['DELETE', '/api/sales'], ['POST', '/api/floor/visits'], ]; for (const [m, p] of deny) assert.equal(staffApiDecision(m, p, one), 'forbidden', `${m} ${p}`); });