test: send-to-person workflow (assign + reassign) across complaint/inquiry/observation
All checks were successful
Build and Push Docker Image / build (push) Successful in 2m21s
All checks were successful
Build and Push Docker Image / build (push) Successful in 2m21s
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.
This commit is contained in:
parent
45b75eb9ef
commit
553364b82c
43
apps/core/management/commands/get_e2e_assignment_state.py
Normal file
43
apps/core/management/commands/get_e2e_assignment_state.py
Normal file
@ -0,0 +1,43 @@
|
||||
"""
|
||||
Test-only helper: print the assignment state (assigned_to + assigned_at) for a
|
||||
complaint / inquiry / observation, so the Playwright spec can assert the
|
||||
send-to-person flow. Node can't read the Django DB.
|
||||
|
||||
Usage:
|
||||
manage.py get_e2e_assignment_state complaint <complaint_id>
|
||||
manage.py get_e2e_assignment_state inquiry <inquiry_id>
|
||||
manage.py get_e2e_assignment_state observation <observation_id>
|
||||
"""
|
||||
|
||||
from django.core.management.base import BaseCommand, CommandError
|
||||
|
||||
KIND_TO_MODEL = {
|
||||
"complaint": ("apps.complaints.models", "Complaint"),
|
||||
"inquiry": ("apps.complaints.models", "Inquiry"),
|
||||
"observation": ("apps.observations.models", "Observation"),
|
||||
}
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = "Print assigned_to/assigned_at for a complaint/inquiry/observation (E2E helper)."
|
||||
|
||||
def add_arguments(self, parser):
|
||||
parser.add_argument("kind", choices=list(KIND_TO_MODEL))
|
||||
parser.add_argument("item_id", help="Item UUID")
|
||||
|
||||
def handle(self, *args, **options):
|
||||
kind = options["kind"]
|
||||
iid = options["item_id"]
|
||||
app_mod, model_name = KIND_TO_MODEL[kind]
|
||||
import importlib
|
||||
mdl = getattr(importlib.import_module(app_mod), model_name)
|
||||
try:
|
||||
it = mdl.objects.get(id=iid)
|
||||
except Exception as exc:
|
||||
raise CommandError(f"{kind} {iid} not found: {exc}")
|
||||
|
||||
print(f"status={it.status}")
|
||||
print(f"assigned_to={it.assigned_to_id or 'NONE'}")
|
||||
print(f"assigned_at={'True' if it.assigned_at else 'False'}")
|
||||
if it.assigned_to_id:
|
||||
print(f"assigned_to_email={it.assigned_to.email}")
|
||||
25
apps/core/management/commands/get_e2e_user_id.py
Normal file
25
apps/core/management/commands/get_e2e_user_id.py
Normal file
@ -0,0 +1,25 @@
|
||||
"""
|
||||
Test-only helper: print the User id for a given email (so the Playwright spec
|
||||
can build send-to-person `person_id` values). Local CLI only.
|
||||
|
||||
Usage:
|
||||
manage.py get_e2e_user_id <email>
|
||||
"""
|
||||
|
||||
from django.core.management.base import BaseCommand, CommandError
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = "Print the User id for a given email (E2E test helper)."
|
||||
|
||||
def add_arguments(self, parser):
|
||||
parser.add_argument("email", help="User email")
|
||||
|
||||
def handle(self, *args, **options):
|
||||
from apps.accounts.models import User
|
||||
|
||||
try:
|
||||
u = User.objects.get(email=options["email"])
|
||||
except User.DoesNotExist as exc:
|
||||
raise CommandError(f"User {options['email']} not found: {exc}")
|
||||
print(u.id)
|
||||
128
e2e/tests/workflows/send-to-person-workflow.spec.ts
Normal file
128
e2e/tests/workflows/send-to-person-workflow.spec.ts
Normal file
@ -0,0 +1,128 @@
|
||||
/* 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');
|
||||
});
|
||||
Loading…
x
Reference in New Issue
Block a user