/* eslint-disable */ /** * DEEP WORKFLOW LIFECYCLE V2 — remaining module CRUD + workflows: * Staff create, Patient create, Gov ticket, Observation category, * Section/Subsection, Complaint template usage, Appreciation full workflow, * Survey instance (token) flow, Callcenter interaction. * * Run headed: * E2E_MAXIMIZED=1 npx playwright test --headed --project chromium deep-workflow-v2 --workers=1 */ import { test } from '@playwright/test'; import { execSync } from 'child_process'; import * as path from 'path'; import { attachObservers, observe, loginAndScope, OBS, BASE_URL, getE2EHospitalId } from '../../helpers/audit'; import { RoleName } from '../../helpers/helpers'; const PROJECT_ROOT = path.resolve(__dirname, '..', '..', '..'); const M = 'DeepWorkflowV2'; const ADMIN: RoleName = 'hospital_admin'; type Page = import('@playwright/test').Page; async function login(page: Page) { await page.context().clearCookies(); await loginAndScope(page, ADMIN, 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(); } // Write a temp python file and run it (avoids shell quoting issues with f-strings) function shellPy(code: string): string { const fs = require('fs'); const tmpFile = `/tmp/e2e_shell_${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 { /* ignore */ } return out; } test.describe('Deep Workflow V2', () => { test.describe.configure({ mode: 'serial' }); test('Staff + Patient + Section CRUD', async ({ page }) => { attachObservers(page, M, ADMIN); await login(page); const hospId = await getE2EHospitalId(page); const ts = Date.now(); // Staff create try { const r = await postForm(page, `${BASE_URL}/organizations/staff/create/`, { employee_id: `E2E-S-${ts}`, first_name: 'E2E', last_name: 'StaffTest', staff_type: 'other', job_title: 'Test Staff', hospital: hospId, department: '', status: 'active', }); observe(M, 'staff-create', r.status() < 400 ? 'PASS' : 'WARN', `staff create HTTP ${r.status()}`, { http: r.status() }); } catch (e) { observe(M, 'staff-create', 'FAIL', `exception: ${(e as Error).message}`, {}); } // Patient create try { const r = await postForm(page, `${BASE_URL}/organizations/patients/create/`, { mrn: `E2E-PTN-${ts}`, first_name: 'E2E', last_name: 'Patient', gender: 'male', primary_hospital: hospId, status: 'active', }); observe(M, 'patient-create', r.status() < 400 ? 'PASS' : 'WARN', `patient create HTTP ${r.status()}`, { http: r.status() }); } catch (e) { observe(M, 'patient-create', 'FAIL', `exception: ${(e as Error).message}`, {}); } // Department create try { const r = await postForm(page, `${BASE_URL}/organizations/departments/create/`, { name: `E2E Dept ${ts}`, name_en: `E2E Dept ${ts}`, code: `E2E-D-${ts.toString().slice(-6)}`, category: 'non_medical', hospital: hospId, status: 'active', }); observe(M, 'dept-create', r.status() < 400 ? 'PASS' : 'WARN', `dept create HTTP ${r.status()}`, { http: r.status() }); } catch (e) { observe(M, 'dept-create', 'FAIL', `exception: ${(e as Error).message}`, {}); } // Section create try { const r = await postForm(page, `${BASE_URL}/organizations/sections/create/`, { name: `E2E Section ${ts}`, hospital: hospId, }); observe(M, 'section-create', r.status() < 400 ? 'PASS' : 'WARN', `section create HTTP ${r.status()}`, { http: r.status() }); } catch (e) { observe(M, 'section-create', 'FAIL', `exception: ${(e as Error).message}`, {}); } }); test('Government ticket create', async ({ page }) => { attachObservers(page, M, ADMIN); await login(page); const hospId = await getE2EHospitalId(page); try { const r = await postForm(page, `${BASE_URL}/complaints/government-tickets/create/`, { complainant_name: `E2E Gov ${Date.now()}`, hospital: hospId, patient_name: 'E2E Patient', national_id: `E2E-GOV-${Date.now()}`, incident_date: '2026-06-17', complaint_details: 'E2E government ticket test', complaint_source_type: 'moh', severity: 'medium', priority: 'medium', }); observe(M, 'gov-ticket-create', r.status() < 400 ? 'PASS' : 'WARN', `gov ticket HTTP ${r.status()}`, { http: r.status() }); } catch (e) { observe(M, 'gov-ticket', 'FAIL', `exception: ${(e as Error).message}`, {}); } }); test('Observation category create', async ({ page }) => { attachObservers(page, M, ADMIN); await login(page); try { const r = await postForm(page, `${BASE_URL}/observations/categories/create/`, { name_en: `E2E Category ${Date.now()}`, name_ar: 'E2E', description: 'E2E test category', sort_order: '99', is_active: 'on', }); observe(M, 'obs-category', r.status() < 400 ? 'PASS' : 'WARN', `category HTTP ${r.status()}`, { http: r.status() }); } catch (e) { observe(M, 'obs-cat', 'FAIL', `exception: ${(e as Error).message}`, {}); } }); test('Appreciation: create → activate → send (full internal workflow)', async ({ page }) => { attachObservers(page, M, ADMIN); await login(page); const hospId = await getE2EHospitalId(page); try { // create a draft appreciation via ORM const createOut = shellPy(`import django,os;os.environ.setdefault('DJANGO_SETTINGS_MODULE','config.settings.dev');django.setup() from apps.appreciation.models import Appreciation,AppreciationStatus from apps.organizations.models import Hospital h=Hospital.objects.get(id='${hospId}') a=Appreciation.objects.create(hospital=h,message_en='E2E V2 appreciation test',status=AppreciationStatus.DRAFT) print(f'apr={a.id} ref={a.reference_number}')`); const m = createOut.match(/apr=(\S+)\s+ref=(\S+)/); if (!m) { observe(M, 'apr-seed', 'FAIL', 'appreciation not created', {}); return; } const aprId = m[1]; observe(M, 'apr-created', 'PASS', `appreciation ${aprId} ref=${m[2]}`, {}); // activate const actR = await postForm(page, `${BASE_URL}/appreciation/detail/${aprId}/activate/`, { staff: '' }); observe(M, 'apr-activate', actR.status() < 400 ? 'PASS' : 'WARN', `activate HTTP ${actR.status()}`, { http: actR.status() }); // send const sendR = await postForm(page, `${BASE_URL}/appreciation/detail/${aprId}/send/`, {}); observe(M, 'apr-send', sendR.status() < 400 ? 'PASS' : 'WARN', `send HTTP ${sendR.status()}`, { http: sendR.status() }); } catch (e) { observe(M, 'apr-flow', 'FAIL', `exception: ${(e as Error).message}`, {}); } }); test('Survey instance + public form (token-based)', async ({ page }) => { attachObservers(page, M, ADMIN); await login(page); const hospId = await getE2EHospitalId(page); try { // ensure a survey template exists const tplOut = shellPy(`import django,os;os.environ.setdefault('DJANGO_SETTINGS_MODULE','config.settings.dev');django.setup() from apps.surveys.models import SurveyTemplate from apps.organizations.models import Hospital h=Hospital.objects.get(id='${hospId}') t=SurveyTemplate.objects.filter(hospital=h,is_active=True).first() if not t: t=SurveyTemplate.objects.create(name='E2E Test Survey',hospital=h,survey_type='general',scoring_method='average',negative_threshold=3.0,is_active=True) print(f'tpl={t.id}')`); const tm = tplOut.match(/tpl=(\S+)/); if (!tm) { observe(M, 'survey-tpl', 'FAIL', 'no template', {}); return; } const tplId = tm[1]; // create a survey instance const instOut = shellPy(`import django,os;os.environ.setdefault('DJANGO_SETTINGS_MODULE','config.settings.dev');django.setup() from apps.surveys.models import SurveyTemplate,SurveyInstance from apps.organizations.models import Hospital t=SurveyTemplate.objects.get(id='${tplId}') i=SurveyInstance.objects.create(survey_template=t,status='sent') print(f'inst={i.id} token={i.access_token}')`); const im = instOut.match(/inst=(\S+)\s+token=(\S+)/); if (!im) { observe(M, 'survey-inst', 'FAIL', 'no instance', {}); return; } const token = im[2]; observe(M, 'survey-inst', 'PASS', `instance created, token=${token.slice(0,8)}...`, {}); // visit the public survey form (token-based, no auth) await page.context().clearCookies(); await page.goto(`${BASE_URL}/surveys/s/${token}/`); await page.waitForLoadState('domcontentloaded'); await page.waitForTimeout(800); const body = (await page.textContent('body')) || ''; const renders = !page.url().includes('invalid') && body.length > 200; observe(M, 'survey-public-form', renders ? 'PASS' : 'WARN', `public form renders: ${renders} (${body.length} chars)`, { url: page.url() }); } catch (e) { observe(M, 'survey-flow', 'FAIL', `exception: ${(e as Error).message}`, {}); } }); test('Complaint note + escalate + change department', async ({ page }) => { attachObservers(page, M, ADMIN); await login(page); try { // get a complaint const cOut = 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(f'c={c.id if c else "NONE"}')`); const cm = cOut.match(/c=(\S+)/); if (!cm || cm[1] === 'NONE') { observe(M, 'complaint-note', 'FAIL', 'no complaint', {}); return; } const cid = cm[1]; // add note const n = await postForm(page, `${BASE_URL}/complaints/${cid}/add-note/`, { note: `E2E V2 note ${Date.now()}` }); observe(M, 'complaint-note', n.status() < 400 ? 'PASS' : 'WARN', `note HTTP ${n.status()}`, { http: n.status() }); // escalate const e = await postForm(page, `${BASE_URL}/complaints/${cid}/escalate/`, { escalation_reason: 'E2E test escalation' }); observe(M, 'complaint-escalate', e.status() < 400 ? 'PASS' : 'WARN', `escalate HTTP ${e.status()}`, { http: e.status() }); } catch (e) { observe(M, 'complaint-extra', 'FAIL', `exception: ${(e as Error).message}`, {}); } }); test('Inquiry respond + add note + escalate', async ({ page }) => { attachObservers(page, M, ADMIN); await login(page); try { const iOut = shellPy(`import django,os;os.environ.setdefault('DJANGO_SETTINGS_MODULE','config.settings.dev');django.setup() from apps.complaints.models import Inquiry from apps.organizations.models import Hospital h=Hospital.objects.get(code='E2E-HOSP') i=Inquiry.objects.filter(hospital=h).first() print(f'i={i.id if i else "NONE"}')`); const im = iOut.match(/i=(\S+)/); if (!im || im[1] === 'NONE') { observe(M, 'inq-extra', 'FAIL', 'no inquiry', {}); return; } const iid = im[1]; // respond const r = await postForm(page, `${BASE_URL}/inquiries/${iid}/respond/`, { response_en: `E2E V2 response ${Date.now()}` }); observe(M, 'inq-respond', r.status() < 400 ? 'PASS' : 'WARN', `respond HTTP ${r.status()}`, { http: r.status() }); // add note const n = await postForm(page, `${BASE_URL}/inquiries/${iid}/add-note/`, { note: 'E2E V2 inquiry note' }); observe(M, 'inq-note', n.status() < 400 ? 'PASS' : 'WARN', `note HTTP ${n.status()}`, { http: n.status() }); // escalate const e = await postForm(page, `${BASE_URL}/inquiries/${iid}/escalate/`, {}); observe(M, 'inq-escalate', e.status() < 400 ? 'PASS' : 'WARN', `escalate HTTP ${e.status()}`, { http: e.status() }); } catch (e) { observe(M, 'inq-extra', 'FAIL', `exception: ${(e as Error).message}`, {}); } }); test('Observation triage + add note + status change', async ({ page }) => { attachObservers(page, M, ADMIN); await login(page); try { const oOut = shellPy(`import django,os;os.environ.setdefault('DJANGO_SETTINGS_MODULE','config.settings.dev');django.setup() from apps.observations.models import Observation from apps.organizations.models import Hospital h=Hospital.objects.get(code='E2E-HOSP') o=Observation.objects.filter(hospital=h).first() print(f'o={o.id if o else "NONE"}')`); const om = oOut.match(/o=(\S+)/); if (!om || om[1] === 'NONE') { observe(M, 'obs-extra', 'FAIL', 'no observation', {}); return; } const oid = om[1]; // status change (if triage perm) const s = await postForm(page, `${BASE_URL}/observations/${oid}/status/`, { status: 'in_progress', comment: 'E2E V2 status change' }); observe(M, 'obs-status', s.status() < 400 ? 'PASS' : 'WARN', `status HTTP ${s.status()}`, { http: s.status() }); // add note const n = await postForm(page, `${BASE_URL}/observations/${oid}/note/`, { note: 'E2E V2 obs note' }); observe(M, 'obs-note', n.status() < 400 ? 'PASS' : 'WARN', `note HTTP ${n.status()}`, { http: n.status() }); } catch (e) { observe(M, 'obs-extra', 'FAIL', `exception: ${(e as Error).message}`, {}); } }); test('Report builder: save + export', async ({ page }) => { attachObservers(page, M, ADMIN); await login(page); const hospId = await getE2EHospitalId(page); try { // save a report const r = await postForm(page, `${BASE_URL}/reports/save/`, { name: `E2E Report ${Date.now()}`, data_source: 'complaints', config: JSON.stringify({ fields: ['title', 'status'], filters: {} }), chart_config: JSON.stringify({ type: 'bar' }), }); observe(M, 'report-save', r.status() < 400 ? 'PASS' : 'WARN', `save HTTP ${r.status()}`, { http: r.status() }); } catch (e) { observe(M, 'report-flow', '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 V2 SUMMARY ==========='); console.log('Total observations:', OBS.length, JSON.stringify(counts)); console.log('================================================\n'); });