/* eslint-disable */ /** * DEEP WORKFLOW V3 — remaining module workflows: * Survey public form (token), PX Source user CRUD, Callcenter complaint create, * Adverse actions, SLA config, Escalation rules, Thresholds, * Journey templates, Reference folders. * * Run headed: * E2E_MAXIMIZED=1 npx playwright test --headed --project chromium deep-workflow-v3 --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 = 'DeepWorkflowV3'; 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_v3_${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 V3', () => { test.describe.configure({ mode: 'serial' }); // ── 1. Survey instance + public form (token) ──────────────────────────── test('Survey: instance + public form token flow', async ({ page }) => { attachObservers(page, M, ADMIN); await login(page); const hospId = await getE2EHospitalId(page); try { // create template + instance via ORM const out = shellPy(` import django, os os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings.dev') django.setup() from apps.surveys.models import SurveyTemplate, SurveyQuestion, SurveyInstance 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 V3 Survey', hospital=h, survey_type='general', scoring_method='average', negative_threshold=3.0, is_active=True) SurveyQuestion.objects.create(survey_template=t, text='How was your experience?', question_type='rating', order=1, is_required=True, is_base=True) i = SurveyInstance.objects.create(survey_template=t, status='sent') print(f'TOKEN={i.access_token}') `); const m = out.match(/TOKEN=(\S+)/); if (!m) { observe(M, 'survey-token', 'FAIL', 'no token', {}); return; } const token = m[1]; observe(M, 'survey-token', 'PASS', `token=${token.slice(0,8)}...`, {}); // visit the public survey form (anonymous, token-based) await page.context().clearCookies(); await page.goto(`${BASE_URL}/surveys/s/${token}/`); await page.waitForLoadState('domcontentloaded'); await page.waitForTimeout(1000); const body = (await page.textContent('body')) || ''; const renders = !page.url().includes('invalid') && body.length > 200; observe(M, 'survey-public', renders ? 'PASS' : 'WARN', `public survey form: ${renders ? 'renders' : 'failed'} (${body.length} chars)`, { url: page.url() }); // try submitting (might need question answers) if (renders) { // find any rating/answer inputs and submit const forms = page.locator('form#survey-form, form').first(); if (await forms.count()) { // fill any visible input/select with a valid value const inputs = page.locator('input[type="radio"], input[type="number"], select'); const cnt = await inputs.count(); for (let i = 0; i < cnt; i++) { const inp = inputs.nth(i); const type = await inp.getAttribute('type'); if (type === 'radio') { await inp.check({ force: true }).catch(() => {}); } else if (type === 'number') { await inp.fill('4').catch(() => {}); } } await page.locator('button[type="submit"], #submitBtn').first().click({ force: true }).catch(() => {}); await page.waitForLoadState('networkidle', { timeout: 8000 }).catch(() => {}); await page.waitForTimeout(1500); observe(M, 'survey-submit', page.url().includes('thank') || page.url().includes('complete') ? 'PASS' : 'WARN', `survey submitted, url=${page.url().slice(-50)}`, { url: page.url() }); } } } catch (e) { observe(M, 'survey-flow', 'FAIL', `exception: ${(e as Error).message}`, {}); } }); // ── 2. PX Source user: create complaint ───────────────────────────────── test('PX Source user: create complaint + inquiry', async ({ page }) => { attachObservers(page, M, 'source_user'); await login(page, 'source_user'); try { // complaint create page await page.goto(`${BASE_URL}/px-sources/complaints/new/`); await page.waitForLoadState('domcontentloaded'); const cRenders = !page.url().includes('login'); observe(M, 'source-complaint-page', cRenders ? 'PASS' : 'WARN', `source user complaint form: ${cRenders}`, { url: page.url() }); // inquiry create page await page.goto(`${BASE_URL}/px-sources/inquiries/new/`); await page.waitForLoadState('domcontentloaded'); const iRenders = !page.url().includes('login'); observe(M, 'source-inquiry-page', iRenders ? 'PASS' : 'WARN', `source user inquiry form: ${iRenders}`, { url: page.url() }); } catch (e) { observe(M, 'source-flow', 'FAIL', `exception: ${(e as Error).message}`, {}); } }); // ── 3. Callcenter complaint create ────────────────────────────────────── test('Callcenter: complaint create page', async ({ page }) => { attachObservers(page, M, ADMIN); await login(page); try { await page.goto(`${BASE_URL}/callcenter/complaints/create/`); await page.waitForLoadState('domcontentloaded'); const renders = !page.url().includes('login') && (await page.textContent('body') || '').length > 200; observe(M, 'callcenter-create', renders ? 'PASS' : 'WARN', `callcenter complaint form: ${renders}`, { url: page.url() }); } catch (e) { observe(M, 'callcenter', 'FAIL', `exception: ${(e as Error).message}`, {}); } }); // ── 4. Adverse action create (linked to a complaint) ──────────────────── test('Adverse action: create on a complaint', 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(f'CID={c.id if c else "NONE"}') `); const m = out.match(/CID=(\S+)/); if (!m || m[1] === 'NONE') { observe(M, 'adverse', 'WARN', 'no complaint for adverse action', {}); return; } const cid = m[1]; const r = await postForm(page, `${BASE_URL}/complaints/${cid}/adverse-actions/add/`, { action_type: 'medication_error', description: 'E2E adverse action test', severity: 'medium', action_taken: 'Reported to supervisor', }); observe(M, 'adverse-create', r.status() < 400 ? 'PASS' : 'WARN', `adverse action HTTP ${r.status()}`, { http: r.status() }); } catch (e) { observe(M, 'adverse', 'FAIL', `exception: ${(e as Error).message}`, {}); } }); // ── 5. SLA config + escalation rule + threshold create ────────────────── test('Admin config: SLA + escalation + threshold', async ({ page }) => { attachObservers(page, M, ADMIN); await login(page); const hospId = await getE2EHospitalId(page); try { // SLA config create (px_admin only — hospital_admin may get 403) const sl = await postForm(page, `${BASE_URL}/complaints/settings/sla-management/new/`, { hospital: hospId, category: 'medical', severity: 'medium', sla_hours: '48', }); observe(M, 'sla-config', sl.status() < 400 ? 'PASS' : 'WARN', `SLA config HTTP ${sl.status()}`, { http: sl.status() }); // escalation rule create const er = await postForm(page, `${BASE_URL}/complaints/settings/escalation-rules/new/`, { hospital: hospId, priority: 'medium', escalation_hours: '24', escalation_level: '1', }); observe(M, 'escalation-rule', er.status() < 400 ? 'PASS' : 'WARN', `escalation rule HTTP ${er.status()}`, { http: er.status() }); // threshold create const th = await postForm(page, `${BASE_URL}/complaints/settings/thresholds/new/`, { hospital: hospId, category: 'medical', warning_threshold: '5', critical_threshold: '10', }); observe(M, 'threshold', th.status() < 400 ? 'PASS' : 'WARN', `threshold HTTP ${th.status()}`, { http: th.status() }); } catch (e) { observe(M, 'config-flow', 'FAIL', `exception: ${(e as Error).message}`, {}); } }); // ── 6. Journey template create ────────────────────────────────────────── test('Journey: template list + create page', async ({ page }) => { attachObservers(page, M, ADMIN); await login(page); try { // Journey uses DRF — list + create via API const csrf = await csrfOf(page); const r = await page.context().request.post(`${BASE_URL}/journeys/templates/`, { headers: { 'Content-Type': 'application/json', ...(csrf ? { 'X-CSRFToken': csrf } : {}) }, data: { name: `E2E Journey ${Date.now()}`, description: 'E2E test journey' }, }); observe(M, 'journey-create', r.status() < 400 ? 'PASS' : 'WARN', `journey template HTTP ${r.status()}`, { http: r.status() }); } catch (e) { observe(M, 'journey', 'FAIL', `exception: ${(e as Error).message}`, {}); } }); // ── 7. Reference folder create ────────────────────────────────────────── test('References: folder + document create', async ({ page }) => { attachObservers(page, M, ADMIN); await login(page); const hospId = await getE2EHospitalId(page); try { const r = await postForm(page, `${BASE_URL}/references/folders/new/`, { name: `E2E Folder ${Date.now()}`, hospital: hospId, }); observe(M, 'ref-folder', r.status() < 400 ? 'PASS' : 'WARN', `folder HTTP ${r.status()}`, { http: r.status() }); } catch (e) { observe(M, 'ref', 'FAIL', `exception: ${(e as Error).message}`, {}); } }); // ── 8. Complaint template use ─────────────────────────────────────────── test('Complaint template: list + detail', async ({ page }) => { attachObservers(page, M, ADMIN); await login(page); try { // list templates await page.goto(`${BASE_URL}/complaints/templates/`); await page.waitForLoadState('domcontentloaded'); observe(M, 'tpl-list', 'PASS', 'template list loads', { url: page.url() }); // get first template id + visit detail const out = shellPy(` import django, os os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings.dev') django.setup() from apps.complaints.models import ComplaintTemplate t = ComplaintTemplate.objects.first() print(f'TID={t.id if t else "NONE"}') `); const m = out.match(/TID=(\S+)/); if (m && m[1] !== 'NONE') { await page.goto(`${BASE_URL}/complaints/templates/${m[1]}/`); await page.waitForLoadState('domcontentloaded'); observe(M, 'tpl-detail', 'PASS', `template detail loads`, { url: page.url() }); } else { observe(M, 'tpl-detail', 'WARN', 'no templates exist', {}); } } catch (e) { observe(M, 'tpl', 'FAIL', `exception: ${(e as Error).message}`, {}); } }); // ── 9. Physician detail ───────────────────────────────────────────────── test('Physician: detail page', 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.organizations.models import Staff from apps.organizations.models import Hospital h = Hospital.objects.get(code='E2E-HOSP') s = Staff.objects.filter(hospital=h, staff_type='physician').first() if not s: s = Staff.objects.filter(hospital=h).first() print(f'PID={s.id if s else "NONE"}') `); const m = out.match(/PID=(\S+)/); if (m && m[1] !== 'NONE') { await page.goto(`${BASE_URL}/physicians/${m[1]}/`); await page.waitForLoadState('domcontentloaded'); const renders = (await page.textContent('body') || '').length > 200; observe(M, 'phys-detail', renders ? 'PASS' : 'WARN', `physician detail: ${renders}`, { url: page.url() }); } else { observe(M, 'phys-detail', 'WARN', 'no physicians in E2E-HOSP', {}); } } catch (e) { observe(M, 'phys', '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 V3 SUMMARY ==========='); console.log('Total observations:', OBS.length, JSON.stringify(counts)); console.log('=================================================\n'); });