/* eslint-disable */ /** * DEEP WORKFLOW V5 — exports, source user creation, OVR, satisfaction, * appreciation leaderboard/badges, government ticket import, presentation generate. * * Run headed: * E2E_MAXIMIZED=1 npx playwright test --headed --project chromium deep-workflow-v5 --workers=1 */ import { test } from '@playwright/test'; import { execSync } from 'child_process'; import * as path from 'path'; import * as fs from 'fs'; import { attachObservers, observe, loginAndScope, OBS, BASE_URL, getE2EHospitalId } from '../../helpers/audit'; import { RoleName } from '../../helpers/helpers'; const PROJECT_ROOT = path.resolve(__dirname, '..', '..', '..'); const M = 'DeepWorkflowV5'; const ADMIN: RoleName = 'hospital_admin'; type Page = import('@playwright/test').Page; async function login(page: Page, role: RoleName = ADMIN) { await page.context().clearCookies(); await loginAndScope(page, role, M); } async function csrfOf(page: Page): Promise { return page.context().cookies().then((c) => c.find((x) => x.name === 'csrftoken')?.value || ''); } async function postForm(page: Page, url: string, data: Record) { const csrf = await csrfOf(page); return page.context().request.post(url, { maxRedirects: 0, headers: { 'X-Requested-With': 'XMLHttpRequest', ...(csrf ? { 'X-CSRFToken': csrf } : {}) }, form: { csrfmiddlewaretoken: csrf, ...data }, }); } function uv(args: string): string { return execSync(args, { cwd: PROJECT_ROOT }).toString(); } function shellPy(code: string): string { const tmpFile = `/tmp/e2e_v5_${Date.now()}_${Math.random().toString(36).slice(2,6)}.py`; fs.writeFileSync(tmpFile, code); const out = uv(`uv run manage.py shell < ${tmpFile}`); try { fs.unlinkSync(tmpFile); } catch {} return out; } test.describe('Deep Workflow V5', () => { test.describe.configure({ mode: 'serial' }); // ── 1. Complaint CSV + Excel export ───────────────────────────────────── test('Complaint exports: CSV + Excel', async ({ page }) => { attachObservers(page, M, ADMIN); await login(page); try { const csv = await page.context().request.get(`${BASE_URL}/complaints/export/csv/`); const csvOk = csv.status() === 200 && (csv.headers()['content-type'] || '').includes('csv'); observe(M, 'complaint-csv', csvOk ? 'PASS' : 'FAIL', `CSV HTTP ${csv.status()} ct=${csv.headers()['content-type']}`, { http: csv.status() }); const xls = await page.context().request.get(`${BASE_URL}/complaints/export/excel/`); const xlsCt = xls.headers()['content-type'] || ''; const xlsOk = xls.status() === 200 && (xlsCt.includes('spreadsheet') || xlsCt.includes('excel') || xlsCt.includes('octet')); observe(M, 'complaint-excel', xlsOk ? 'PASS' : 'FAIL', `Excel HTTP ${xls.status()} ct=${xlsCt}`, { http: xls.status() }); } catch (e) { observe(M, 'complaint-exports', 'FAIL', `exception: ${(e as Error).message}`, {}); } }); // ── 2. Inquiry exports ────────────────────────────────────────────────── test('Inquiry exports: incoming + outgoing', async ({ page }) => { attachObservers(page, M, ADMIN); await login(page); try { const inc = await page.context().request.get(`${BASE_URL}/inquiries/export/incoming/`); observe(M, 'inq-export-incoming', inc.status() < 400 ? 'PASS' : 'WARN', `incoming HTTP ${inc.status()}`, { http: inc.status() }); const out = await page.context().request.get(`${BASE_URL}/inquiries/export/outgoing/`); observe(M, 'inq-export-outgoing', out.status() < 400 ? 'PASS' : 'WARN', `outgoing HTTP ${out.status()}`, { http: out.status() }); } catch (e) { observe(M, 'inq-exports', 'FAIL', `exception: ${(e as Error).message}`, {}); } }); // ── 3. Complaint update satisfaction ───────────────────────────────────── test('Complaint: update satisfaction', async ({ page }) => { attachObservers(page, M, ADMIN); await login(page); try { const out = shellPy(` import django, os os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings.dev') django.setup() from apps.complaints.models import Complaint from apps.organizations.models import Hospital h = Hospital.objects.get(code='E2E-HOSP') c = Complaint.objects.filter(hospital=h).first() print('CID=' + str(c.id) if c else 'CID=NONE') `); const m = out.match(/CID=(\S+)/); if (!m || m[1] === 'NONE') { observe(M, 'satisfaction', 'WARN', 'no complaint', {}); return; } const r = await postForm(page, `${BASE_URL}/complaints/${m[1]}/update-satisfaction/`, { satisfaction: 'satisfied' }); observe(M, 'complaint-satisfaction', r.status() < 400 ? 'PASS' : 'WARN', `satisfaction HTTP ${r.status()}`, { http: r.status() }); } catch (e) { observe(M, 'satisfaction', 'FAIL', `exception: ${(e as Error).message}`, {}); } }); // ── 4. Appreciation: leaderboard + badges ─────────────────────────────── test('Appreciation: leaderboard + my badges + admin badges', async ({ page }) => { attachObservers(page, M, ADMIN); await login(page); try { await page.goto(`${BASE_URL}/appreciation/leaderboard/`); await page.waitForLoadState('domcontentloaded'); observe(M, 'apr-leaderboard', 'PASS', 'leaderboard page loads', { url: page.url() }); await page.goto(`${BASE_URL}/appreciation/badges/`); await page.waitForLoadState('domcontentloaded'); observe(M, 'apr-my-badges', 'PASS', 'my badges page loads', { url: page.url() }); await page.goto(`${BASE_URL}/appreciation/admin/badges/`); await page.waitForLoadState('domcontentloaded'); observe(M, 'apr-admin-badges', 'PASS', 'admin badges page loads', { url: page.url() }); } catch (e) { observe(M, 'apr-extra', 'FAIL', `exception: ${(e as Error).message}`, {}); } }); // ── 5. Presentation generate (with longer timeout) ────────────────────── test('Presentations: generate page + create', async ({ page }) => { attachObservers(page, M, ADMIN); await login(page); const hospId = await getE2EHospitalId(page); try { // generate page (may take a while rendering charts) await page.goto(`${BASE_URL}/presentations/generate/`, { timeout: 30000 }).catch(() => {}); await page.waitForLoadState('domcontentloaded').catch(() => {}); await page.waitForTimeout(1000); const body = (await page.textContent('body').catch(() => '')) || ''; const renders = body.length > 200; observe(M, 'pres-gen-page', renders ? 'PASS' : 'WARN', `generate page: ${renders} (${body.length} chars)`, { url: page.url() }); // try POST to generate (may timeout on data processing — that's OK) if (renders) { const r = await postForm(page, `${BASE_URL}/presentations/generate/`, { hospital: hospId, year: '2026', report_type: 'complaints', }); observe(M, 'pres-generate', r.status() < 500 ? 'PASS' : 'WARN', `generate POST HTTP ${r.status()}`, { http: r.status() }); } } catch (e) { observe(M, 'pres', 'FAIL', `exception: ${(e as Error).message}`, {}); } }); // ── 6. Government ticket import page ──────────────────────────────────── test('Government ticket import page', async ({ page }) => { attachObservers(page, M, ADMIN); await login(page); try { await page.goto(`${BASE_URL}/complaints/government-tickets/import/`); await page.waitForLoadState('domcontentloaded'); const renders = (await page.textContent('body') || '').length > 200; observe(M, 'gov-import', renders ? 'PASS' : 'WARN', `import page: ${renders}`, { url: page.url() }); } catch (e) { observe(M, 'gov-import', 'FAIL', `exception: ${(e as Error).message}`, {}); } }); // ── 7. Complaint OVR toggle ───────────────────────────────────────────── test('Complaint: OVR toggle', async ({ page }) => { attachObservers(page, M, ADMIN); await login(page); try { const out = shellPy(` import django, os os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings.dev') django.setup() from apps.complaints.models import Complaint from apps.organizations.models import Hospital h = Hospital.objects.get(code='E2E-HOSP') c = Complaint.objects.filter(hospital=h).first() print('CID=' + str(c.id) if c else 'CID=NONE') `); const m = out.match(/CID=(\S+)/); if (!m || m[1] === 'NONE') { observe(M, 'ovr', 'WARN', 'no complaint', {}); return; } const cid = m[1]; const r = await postForm(page, `${BASE_URL}/complaints/${cid}/toggle-escalated-ovr/`, {}); observe(M, 'ovr-toggle', r.status() < 400 ? 'PASS' : 'WARN', `OVR toggle HTTP ${r.status()}`, { http: r.status() }); } catch (e) { observe(M, 'ovr', 'FAIL', `exception: ${(e as Error).message}`, {}); } }); // ── 8. Complaint analytics page ───────────────────────────────────────── test('Complaint analytics page', async ({ page }) => { attachObservers(page, M, ADMIN); await login(page); try { await page.goto(`${BASE_URL}/complaints/analytics/`); await page.waitForLoadState('domcontentloaded'); const body = (await page.textContent('body')) || ''; const hasCharts = await page.locator('canvas, svg, .apexcharts-canvas, [id*="chart"]').count() > 0; observe(M, 'complaint-analytics', body.length > 500 ? 'PASS' : 'WARN', `analytics: ${body.length} chars, charts=${hasCharts}`, { url: page.url() }); } catch (e) { observe(M, 'analytics', 'FAIL', `exception: ${(e as Error).message}`, {}); } }); // ── 9. Source user: actually create a complaint ───────────────────────── test('PX Source user: create complaint', async ({ page }) => { attachObservers(page, M, 'source_user'); await login(page, 'source_user'); const hospId = await getE2EHospitalId(page); try { const out = shellPy(` import django, os os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings.dev') django.setup() from apps.organizations.models import Department from apps.organizations.models import Hospital h = Hospital.objects.get(code='E2E-HOSP') d = Department.objects.filter(hospital=h).first() print('DID=' + str(d.id)) `); const m = out.match(/DID=(\S+)/); const did = m ? m[1] : ''; const r = await postForm(page, `${BASE_URL}/px-sources/complaints/new/`, { complainant_name: `E2E Source ${Date.now()}`, mobile_number: '0551234567', patient_name: 'E2E Patient', national_id: `E2E-SRC-${Date.now()}`, incident_date: '2026-06-17', hospital: hospId, department: did, complaint_details: 'E2E source user complaint test', severity: 'medium', priority: 'medium', }); observe(M, 'source-complaint-create', r.status() < 400 ? 'PASS' : 'WARN', `source complaint HTTP ${r.status()}`, { http: r.status() }); } catch (e) { observe(M, 'source-create', 'FAIL', `exception: ${(e as Error).message}`, {}); } }); // ── 10. Appreciation category create ──────────────────────────────────── test('Appreciation: admin category create', async ({ page }) => { attachObservers(page, M, ADMIN); await login(page); const hospId = await getE2EHospitalId(page); try { const r = await postForm(page, `${BASE_URL}/appreciation/admin/categories/create/`, { code: `E2E-CAT-${Date.now().toString().slice(-6)}`, name_en: 'E2E Test Category', name_ar: 'E2E', hospital: hospId, is_active: 'on', }); observe(M, 'apr-category', r.status() < 400 ? 'PASS' : 'WARN', `category create HTTP ${r.status()}`, { http: r.status() }); } catch (e) { observe(M, 'apr-cat', 'FAIL', `exception: ${(e as Error).message}`, {}); } }); }); test.afterAll(async () => { const counts = OBS.reduce>((a, o) => ((a[o.status] = (a[o.status] || 0) + 1), a), {}); console.log('\n=========== DEEP WORKFLOW V5 SUMMARY ==========='); console.log('Total observations:', OBS.length, JSON.stringify(counts)); console.log('=================================================\n'); });