import { test, expect } from '@playwright/test'; import { ApiHelper, HOSPITAL_ID } from '../../helpers/api-helper'; test.describe('Hospital Data Isolation', () => { test('Hospital Admin can only see own hospital complaints via API', async ({ page }) => { const api = new ApiHelper(page); await api.authenticate('hospital_admin'); const resp = await api.get('/complaints/api/complaints/?page_size=50'); expect(resp.status()).toBe(200); const body = await resp.json(); const results = body.results || body; for (const complaint of results) { if (complaint.hospital) { expect(complaint.hospital).toBe(HOSPITAL_ID); } } }); test('Hospital Admin can only see own hospital inquiries via API', async ({ page }) => { const api = new ApiHelper(page); await api.authenticate('hospital_admin'); const resp = await api.get('/complaints/api/inquiries/?page_size=50'); expect(resp.status()).toBe(200); const body = await resp.json(); const results = body.results || body; for (const inquiry of results) { if (inquiry.hospital) { expect(inquiry.hospital).toBe(HOSPITAL_ID); } } }); test('PX Admin without hospital selection sees all complaints via API', async ({ page }) => { const api = new ApiHelper(page); await api.authenticate('px_admin'); const resp = await api.get('/complaints/api/complaints/?page_size=5'); expect(resp.status()).toBe(200); const body = await resp.json(); const results = body.results || body; expect(results.length).toBeGreaterThanOrEqual(0); }); test('Dept Manager gets filtered complaint data via API', async ({ page }) => { const api = new ApiHelper(page); await api.authenticate('dept_manager'); const resp = await api.get('/complaints/api/complaints/?page_size=50'); expect(resp.status()).toBe(200); const body = await resp.json(); expect(body.results || body).toBeTruthy(); }); test('Source user can access own source data', async ({ page }) => { const api = new ApiHelper(page); await api.authenticate('source_user'); const resp = await api.get('/px-sources/api/sources/'); expect([200, 403]).toContain(resp.status()); }); test('Staff user gets limited data via API', async ({ page }) => { const api = new ApiHelper(page); await api.authenticate('staff'); const resp = await api.get('/complaints/api/complaints/?page_size=5'); expect(resp.status()).toBe(200); const body = await resp.json(); expect(body.results || body).toBeTruthy(); }); test('Viewer gets read-only data via API', async ({ page }) => { const api = new ApiHelper(page); await api.authenticate('viewer'); const resp = await api.get('/complaints/api/complaints/?page_size=5'); expect(resp.status()).toBe(200); const createResp = await api.post('/complaints/api/complaints/', { patient_name: 'blocked', national_id: 'blocked', description: 'blocked', hospital: HOSPITAL_ID, }); expect([400, 403, 405]).toContain(createResp.status()); }); test('survey instances filtered by hospital for Hospital Admin', async ({ page }) => { const api = new ApiHelper(page); await api.authenticate('hospital_admin'); const resp = await api.get('/surveys/api/instances/?page_size=50'); expect(resp.status()).toBe(200); const body = await resp.json(); const results = body.results || body; for (const instance of results) { if (instance.survey_template?.hospital) { expect(instance.survey_template.hospital).toBe(HOSPITAL_ID); } } }); }); test.describe('Cross-Tenant Write Protection', () => { test('Hospital Admin cannot POST complaint to different hospital', async ({ page }) => { const api = new ApiHelper(page); await api.authenticate('hospital_admin'); const resp = await api.post('/complaints/api/complaints/', { patient_name: 'Cross Tenant Test', national_id: `CROSS${Date.now()}`, relation_to_patient: 'patient', incident_date: '2026-01-15', description: 'Cross tenant test', hospital: '00000000-0000-0000-0000-000000000000', complaint_type: 'complaint', }); expect([400, 403, 404]).toContain(resp.status()); }); test('Hospital Admin cannot update user from different hospital', async ({ page }) => { const api = new ApiHelper(page); await api.authenticate('hospital_admin'); const userResp = await api.get('/accounts/users/?page_size=50'); const userBody = await userResp.json(); const users = userBody.results || userBody; const otherHospitalUser = users.find((u: any) => u.hospital && u.hospital !== HOSPITAL_ID); if (!otherHospitalUser) { test.skip(); return; } const resp = await api.patch(`/accounts/users/${otherHospitalUser.id}/`, { first_name: 'Hacked', }); expect([403, 404]).toContain(resp.status()); }); test('Source user cannot access complaints API directly', async ({ page }) => { const api = new ApiHelper(page); await api.authenticate('source_user'); const resp = await api.get('/complaints/api/complaints/'); expect([200, 403, 500]).toContain(resp.status()); }); test('non-PX-Admin cannot access config API', async ({ page }) => { const api = new ApiHelper(page); await api.authenticate('hospital_admin'); const resp = await api.get('/actions/api/sla-configs/'); expect([403, 404]).toContain(resp.status()); }); }); test.describe('Known Isolation Gaps', () => { test('Standards API filters by hospital after fix', async ({ page }) => { const api = new ApiHelper(page); await api.authenticate('hospital_admin'); const resp = await api.get('/standards/api/standards/'); expect(resp.status()).toBe(200); const body = await resp.json(); const results = body.results || body; for (const standard of results) { if (standard.hospital) { expect(standard.hospital).toBe(HOSPITAL_ID); } } }); test('Appreciation API filters by hospital after fix', async ({ page }) => { const api = new ApiHelper(page); await api.authenticate('hospital_admin'); const resp = await api.get('/api/v1/appreciation/api/appreciations/'); expect(resp.status()).toBe(200); const body = await resp.json(); const results = body.results || body; for (const item of results) { if (item.hospital) { expect(item.hospital).toBe(HOSPITAL_ID); } } }); test('Physician ratings accessible via API', async ({ page }) => { const api = new ApiHelper(page); await api.authenticate('hospital_admin'); const resp = await api.get('/physicians/api/physicians/'); expect(resp.status()).toBe(200); }); });