/* eslint-disable */ /** * "Send to person" workflow E2E — assigns a complaint/inquiry/observation to a * specific user (the `recipient_type=person` branch of the unified send-to * endpoint). Covers assign + reassign + the assignee being able to open the item. * * Run headed: * E2E_MAXIMIZED=1 E2E_TIMEOUT=120000 E2E_ACTION_TIMEOUT=12000 E2E_NAV_TIMEOUT=20000 \ * npx playwright test --headed --project chromium send-to-person-workflow --workers=1 */ import { test } from '@playwright/test'; import { execSync } from 'child_process'; import * as path from 'path'; import { attachObservers, observe, loginAndScope, OBS, BASE_URL } from '../../helpers/audit'; import { RoleName } from '../../helpers/helpers'; const PROJECT_ROOT = path.resolve(__dirname, '..', '..', '..'); const PXT: RoleName = 'hospital_admin'; const ASSIGNEE: RoleName = 'px_employee'; const REASSIGNEE_EMAIL = 'e2e-staff@px360.test'; type Page = import('@playwright/test').Page; interface KindCfg { label: string; module: string; seedKind: 'complaint' | 'inquiry' | 'observation'; sendTo: (id: string) => string; activate: (id: string) => string; detail: (id: string) => string; } const KINDS: KindCfg[] = [ { label: 'Complaint', module: 'SendToPerson', seedKind: 'complaint', sendTo: (id) => `/complaints/${id}/send-to/`, activate: (id) => `/complaints/${id}/activate/`, detail: (id) => `/complaints/${id}/` }, { label: 'Inquiry', module: 'SendToPerson', seedKind: 'inquiry', sendTo: (id) => `/inquiries/${id}/send-to/`, activate: (id) => `/inquiries/${id}/activate/`, detail: (id) => `/inquiries/${id}/` }, { label: 'Observation', module: 'SendToPerson', seedKind: 'observation', sendTo: (id) => `/observations/${id}/send-to/`, activate: (id) => `/observations/${id}/activate/`, detail: (id) => `/observations/${id}` }, ]; function uv(args: string): string { return execSync(args, { cwd: PROJECT_ROOT }).toString(); } function seedId(kind: string): string { const cmd = kind === 'complaint' ? 'seed_e2e_complaint' : `seed_e2e_dept_response ${kind}`; const out = uv(`uv run manage.py ${cmd}`); const m = out.match(/(?:complaint_id|item_id)=([0-9a-f-]+)/); if (!m) throw new Error('seed parse failed: ' + out); return m[1]; } function userId(email: string): string { return uv(`uv run manage.py get_e2e_user_id ${email}`).trim(); } function assignState(kind: string, id: string): Record { const out = uv(`uv run manage.py get_e2e_assignment_state ${kind} ${id}`); const s: Record = {}; for (const line of out.split('\n')) { const i = line.indexOf('='); if (i > 0) s[line.slice(0, i)] = line.slice(i + 1); } return s; } 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 }, }); } async function login(page: Page, role: RoleName) { await page.context().clearCookies(); await loginAndScope(page, role, 'SendToPerson'); } for (const K of KINDS) { test.describe(`${K.label} send-to-person`, () => { test(`assign -> assignee can open -> reassign`, async ({ page }) => { const M = `${K.label}SendToPerson`; attachObservers(page, M, PXT); const assigneeId = userId('e2e-px-employee@px360.test'); const reassigneeId = userId(REASSIGNEE_EMAIL); let itemId = ''; try { itemId = seedId(K.seedKind); observe(M, 'seed', 'INFO', `${K.label} ${itemId}`, { role: PXT }); // 1. PX activates (send requires in_progress) then sends to a person await login(page, PXT); await postForm(page, `${BASE_URL}${K.activate(itemId)}`, {}); const send = await postForm(page, `${BASE_URL}${K.sendTo(itemId)}`, { recipient_type: 'person', person_id: assigneeId, note: 'E2E assign' }); const sendOk = send.status() === 200; try { const j = await send.json(); observe(M, 'assign', sendOk && j.success ? 'PASS' : 'FAIL', `HTTP ${send.status()} success=${j.success}`, { role: PXT, http: send.status() }); } catch { observe(M, 'assign', sendOk ? 'PASS' : 'FAIL', `HTTP ${send.status()}`, { role: PXT, http: send.status() }); } const st = assignState(K.seedKind, itemId); observe(M, 'assign-state', st.assigned_to === assigneeId ? 'PASS' : 'FAIL', `assigned_to=${st.assigned_to_email || st.assigned_to} (want e2e-px-employee); assigned_at=${st.assigned_at}`, { role: PXT }); // 2. The assignee (px_employee) can open the item detail await login(page, ASSIGNEE); await page.goto(`${BASE_URL}${K.detail(itemId)}`); await page.waitForLoadState('domcontentloaded'); const onLogin = page.url().includes('/accounts/login/'); observe(M, 'assignee-access', onLogin ? 'FAIL' : 'PASS', `${ASSIGNEE} opening ${K.label} detail: ${onLogin ? 'BLOCKED(login)' : 'OK'}`, { role: ASSIGNEE, url: page.url() }); // 3. PX reassigns to a different person await login(page, PXT); const re = await postForm(page, `${BASE_URL}${K.sendTo(itemId)}`, { recipient_type: 'person', person_id: reassigneeId, note: 'E2E reassign' }); observe(M, 'reassign', re.status() === 200 ? 'PASS' : 'FAIL', `reassign HTTP ${re.status()}`, { role: PXT, http: re.status() }); const st2 = assignState(K.seedKind, itemId); observe(M, 'reassign-state', st2.assigned_to === reassigneeId ? 'PASS' : 'FAIL', `assigned_to=${st2.assigned_to_email || st2.assigned_to} (want ${REASSIGNEE_EMAIL})`, { role: PXT }); } catch (e) { observe(M, 'flow', 'FAIL', `exception: ${(e as Error).message}`, { role: PXT }); } }); }); } test.afterAll(async () => { const counts = OBS.reduce>((a, o) => ((a[o.status] = (a[o.status] || 0) + 1), a), {}); console.log('\n=========== SEND-TO-PERSON SUMMARY ==========='); console.log('Total observations:', OBS.length, JSON.stringify(counts)); console.log('==============================================\n'); });