HH/e2e/tests/workflows/send-to-person-workflow.spec.ts
ismail 553364b82c
All checks were successful
Build and Push Docker Image / build (push) Successful in 2m21s
test: send-to-person workflow (assign + reassign) across complaint/inquiry/observation
Covers the recipient_type=person branch of the unified send-to endpoint for all
3 modules: assign to px_employee, verify the assignee can open the item detail,
then reassign to a different user. 3/3 pass, 0 FAIL.

Harness: get_e2e_user_id + get_e2e_assignment_state CLI helpers.
2026-06-15 15:11:54 +03:00

129 lines
6.2 KiB
TypeScript

/* 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<string, string> {
const out = uv(`uv run manage.py get_e2e_assignment_state ${kind} ${id}`);
const s: Record<string, string> = {};
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<string> {
return page.context().cookies().then((c) => c.find((x) => x.name === 'csrftoken')?.value || '');
}
async function postForm(page: Page, url: string, data: Record<string, string>) {
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<Record<string, number>>((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');
});