import { test, expect } from '@playwright/test'; import { RoleAuthHelper, PublicFormHelper } from '../../helpers/helpers'; test.describe('XSS & Input Validation', () => { test('script tag in complaint form is escaped', async ({ page }) => { const helper = new PublicFormHelper(page); await helper.goToPublicLanding(); await helper.selectFormType('complaint'); const xssPayload = ''; await page.fill('#id_complainant_name', `XSS Test ${Date.now()}`); await page.selectOption('#id_relation_to_patient', 'patient'); await page.fill('#id_mobile_number', '0501234567'); await page.fill('#id_patient_name', xssPayload); await page.fill('#id_national_id', '1234567890'); await page.fill('#id_incident_date', '2025-01-15'); await page.fill('#id_complaint_details', `Test XSS payload: ${xssPayload}`); await page.click('#submit_btn'); await page.waitForLoadState('domcontentloaded'); const bodyText = await page.textContent('body'); expect(bodyText).not.toContain(''); expect(bodyText).not.toContain('alert("xss")'); }); test('script tag in inquiry form is escaped', async ({ page }) => { const helper = new PublicFormHelper(page); await helper.goToPublicLanding(); await helper.selectFormType('inquiry'); const nameInput = page.locator('input[name="name"]'); await nameInput.waitFor({ state: 'visible', timeout: 15000 }).catch(() => {}); if (!(await nameInput.isVisible().catch(() => false))) { test.skip(); return; } const xssPayload = ''; await page.fill('input[name="name"]', `XSS Inquiry ${Date.now()}`); await page.fill('input[name="email"]', 'xss@test.com'); await page.fill('input[name="phone"]', '0501234567'); const hospitalSelect = page.locator('select[name="hospital"]'); const opts = hospitalSelect.locator('option'); if (await opts.count() > 1) { await hospitalSelect.selectOption({ index: 1 }); } await page.selectOption('select[name="category"]', 'general'); await page.fill('input[name="subject"]', xssPayload); await page.fill('textarea[name="message"]', `Body: ${xssPayload}`); await page.click('#inqSubmitBtn'); await page.waitForLoadState('domcontentloaded'); const bodyText = await page.textContent('body'); expect(bodyText).not.toContain('onerror=alert(1)'); }); test('script tag in observation form is escaped', async ({ page }) => { const helper = new PublicFormHelper(page); await helper.goToPublicLanding(); await helper.selectFormType('observation'); const xssPayload = ''; await page.fill('input[name="title"]', xssPayload); await page.fill('textarea[name="description"]', `XSS test: ${xssPayload}`); await page.fill('input[name="reporter_name"]', 'XSS Observer'); await page.fill('input[name="reporter_phone"]', '0501234567'); await page.click('button[type="submit"]'); await page.waitForLoadState('domcontentloaded'); const bodyText = await page.textContent('body'); expect(bodyText).not.toContain(''); }); test('SQL injection in complaint list search does not crash', async ({ page }) => { const auth = new RoleAuthHelper(page); await auth.login('hospital_admin'); await page.goto('/complaints/'); await page.waitForLoadState('domcontentloaded'); const sqlPayload = "'; DROP TABLE complaints; --"; const searchInput = page.locator('input[name="search"], #search, input[type="search"]'); await searchInput.waitFor({ state: 'visible', timeout: 20000 }).catch(() => {}); if (!(await searchInput.isVisible().catch(() => false))) { test.skip(); return; } await page.fill('input[name="search"]', sqlPayload); await page.waitForTimeout(500); const bodyText = await page.textContent('body'); expect(bodyText).not.toContain('Server Error'); expect(bodyText).not.toContain('Traceback'); expect(bodyText).not.toContain('DROP TABLE'); }); test('oversized text input does not crash complaint form', async ({ page }) => { const helper = new PublicFormHelper(page); await helper.goToPublicLanding(); await helper.selectFormType('complaint'); const longText = 'A'.repeat(10000); await page.fill('#id_complainant_name', `Long Text ${Date.now()}`); await page.selectOption('#id_relation_to_patient', 'patient'); await page.fill('#id_mobile_number', '0501234567'); await page.fill('#id_patient_name', 'Long Patient'); await page.fill('#id_national_id', '1234567890'); await page.fill('#id_incident_date', '2025-01-15'); await page.fill('#id_complaint_details', longText); await page.click('#submit_btn'); await page.waitForLoadState('domcontentloaded'); const bodyText = await page.textContent('body'); expect(bodyText).not.toContain('Server Error'); expect(bodyText).not.toContain('Traceback'); }); test('Arabic text and emoji in complaint form works', async ({ page }) => { const helper = new PublicFormHelper(page); await helper.goToPublicLanding(); await helper.selectFormType('complaint'); await page.fill('#id_complainant_name', `مريض تجريبي ${Date.now()}`); await page.selectOption('#id_relation_to_patient', 'patient'); await page.fill('#id_mobile_number', '0501234567'); await page.fill('#id_patient_name', 'اسم المريض 😊'); await page.fill('#id_national_id', '1234567890'); await page.fill('#id_incident_date', '2025-01-15'); await page.fill('#id_complaint_details', 'شكوى تجريبية من المريض 🏥'); await page.click('#submit_btn'); await page.waitForLoadState('domcontentloaded'); const bodyText = await page.textContent('body'); expect(bodyText).not.toContain('Server Error'); expect(bodyText).not.toContain('Traceback'); }); test('email injection in patient field is handled safely', async ({ page }) => { const helper = new PublicFormHelper(page); await helper.goToPublicLanding(); await helper.selectFormType('complaint'); const emailInjection = 'test@test.com\nBCC:evil@evil.com\nSubject:phishing'; await page.fill('#id_complainant_name', `Email Inject ${Date.now()}`); await page.selectOption('#id_relation_to_patient', 'patient'); await page.fill('#id_email', emailInjection); await page.fill('#id_mobile_number', '0501234567'); await page.fill('#id_patient_name', 'Email Inject Patient'); await page.fill('#id_national_id', '1234567890'); await page.fill('#id_incident_date', '2025-01-15'); await page.fill('#id_complaint_details', 'Email injection test'); await page.click('#submit_btn'); await page.waitForLoadState('domcontentloaded'); const bodyText = await page.textContent('body'); expect(bodyText).not.toContain('Server Error'); expect(bodyText).not.toContain('Traceback'); }); test('CSRF token required for POST to complaint create', async ({ request }) => { const resp = await request.fetch('http://localhost:8000/api/v1/complaints/', { method: 'POST', headers: { 'Content-Type': 'application/json', }, data: { title: 'CSRF test', description: 'testing', }, }); expect([200, 201, 403, 401, 404, 405]).toContain(resp.status()); }); });