From 17981fdf82045ceaa70c70af0d567e5f054ec64c Mon Sep 17 00:00:00 2001 From: ismail Date: Sun, 14 Jun 2026 15:37:54 +0300 Subject: [PATCH] test: harden champion/manager spec against post-login session timing ensureAuth() probes an auth-required endpoint after each login() and re-logs-in if the session hasn't settled (occasionally needed under slowMo after rapid role swaps). Also observes the HTTP status of every second-pass POST so any auth bounce is visible. App logic unchanged (verified via the test client). --- .../champion-manager-workflow.spec.ts | 47 +++++++++++++------ 1 file changed, 33 insertions(+), 14 deletions(-) diff --git a/e2e/tests/workflows/champion-manager-workflow.spec.ts b/e2e/tests/workflows/champion-manager-workflow.spec.ts index 7dbc9a3..4384bb5 100644 --- a/e2e/tests/workflows/champion-manager-workflow.spec.ts +++ b/e2e/tests/workflows/champion-manager-workflow.spec.ts @@ -70,6 +70,25 @@ async function postForm(page: import('@playwright/test').Page, url: string, data }); } +/** POST and observe the HTTP status (used where we need to see a step's outcome). */ +async function postObs(page: import('@playwright/test').Page, url: string, data: Record, step: string, role?: string) { + const r = await postForm(page, url, data); + const loc = r.headers()['location'] || ''; + const loginBounce = loc.includes('/accounts/login'); + observe(M, step, r.status() < 400 && !loginBounce ? 'PASS' : 'FAIL', + `HTTP ${r.status()}${loc ? ` -> ${loc.slice(0, 60)}` : ''}${loginBounce ? ' (auth bounce!)' : ''}`, { role, http: r.status() }); + return r; +} + +/** Ensure the page is actually authenticated as `role` before POSTing; re-login if bounced. */ +async function ensureAuth(page: import('@playwright/test').Page, role: RoleName) { + // probe an auth-required page via the APIRequestContext + const probe = await page.context().request.get(`${BASE_URL}/complaints/?__probe=1`, { maxRedirects: 0 }); + if (probe.status() === 302 && (probe.headers()['location'] || '').includes('/accounts/login')) { + await login(page, role); // re-establish session + } +} + async function login(page: import('@playwright/test').Page, role: RoleName) { await page.context().clearCookies(); await loginAndScope(page, role, M); @@ -265,13 +284,13 @@ test.describe('Champion/Manager workflow', () => { `after reject: response_submitted=${sAfterReject.idept_response_submitted} (want False)`, { role: MGR }); // champion re-responds -> manager approves -> PX accepts -> resolve - await login(page, CHAMP); - await postForm(page, `${BASE_URL}/complaints/departments/${ideptId}/response/`, { response_notes_en: 'C1 revised response' }); - await login(page, MGR); - await postForm(page, `${BASE_URL}/organizations/departments/${seed.deptId}/manager-review/${ideptId}/`, { review_action: 'approve' }); - await login(page, PXT); - await postForm(page, `${BASE_URL}/complaints/departments/${ideptId}/review-response/`, { acceptance_status: 'acceptable' }); - await postForm(page, `${BASE_URL}/complaints/${cid}/change-status/`, { status: 'resolved', resolution: 'C1 resolved' }); + await login(page, CHAMP); await ensureAuth(page, CHAMP); + await postObs(page, `${BASE_URL}/complaints/departments/${ideptId}/response/`, { response_notes_en: 'C1 revised response' }, 'C1-re-response', CHAMP); + await login(page, MGR); await ensureAuth(page, MGR); + await postObs(page, `${BASE_URL}/organizations/departments/${seed.deptId}/manager-review/${ideptId}/`, { review_action: 'approve' }, 'C1-re-approve', MGR); + await login(page, PXT); await ensureAuth(page, PXT); + await postObs(page, `${BASE_URL}/complaints/departments/${ideptId}/review-response/`, { acceptance_status: 'acceptable' }, 'C1-accept', PXT); + await postObs(page, `${BASE_URL}/complaints/${cid}/change-status/`, { status: 'resolved', resolution: 'C1 resolved' }, 'C1-resolve', PXT); assertState(cid, { complaint_status: 'resolved', idept_acceptance_status: 'acceptable' }, 'C1-final-state', PXT); } catch (e) { observe(M, 'C1-flow', 'FAIL', `exception: ${(e as Error).message}`, { role: MGR }); @@ -308,13 +327,13 @@ test.describe('Champion/Manager workflow', () => { `after not-acceptable: response_submitted=${sAfterNA.idept_response_submitted} (want False)`, { role: PXT }); // champion re-responds -> manager approves -> PX accepts -> resolve - await login(page, CHAMP); - await postForm(page, `${BASE_URL}/complaints/departments/${ideptId}/response/`, { response_notes_en: 'C2 revised response' }); - await login(page, MGR); - await postForm(page, `${BASE_URL}/organizations/departments/${seed.deptId}/manager-review/${ideptId}/`, { review_action: 'approve' }); - await login(page, PXT); - await postForm(page, `${BASE_URL}/complaints/departments/${ideptId}/review-response/`, { acceptance_status: 'acceptable' }); - await postForm(page, `${BASE_URL}/complaints/${cid}/change-status/`, { status: 'resolved', resolution: 'C2 resolved' }); + await login(page, CHAMP); await ensureAuth(page, CHAMP); + await postObs(page, `${BASE_URL}/complaints/departments/${ideptId}/response/`, { response_notes_en: 'C2 revised response' }, 'C2-re-response', CHAMP); + await login(page, MGR); await ensureAuth(page, MGR); + await postObs(page, `${BASE_URL}/organizations/departments/${seed.deptId}/manager-review/${ideptId}/`, { review_action: 'approve' }, 'C2-re-approve', MGR); + await login(page, PXT); await ensureAuth(page, PXT); + await postObs(page, `${BASE_URL}/complaints/departments/${ideptId}/review-response/`, { acceptance_status: 'acceptable' }, 'C2-accept', PXT); + await postObs(page, `${BASE_URL}/complaints/${cid}/change-status/`, { status: 'resolved', resolution: 'C2 resolved' }, 'C2-resolve', PXT); assertState(cid, { complaint_status: 'resolved', idept_acceptance_status: 'acceptable' }, 'C2-final-state', PXT); } catch (e) { observe(M, 'C2-flow', 'FAIL', `exception: ${(e as Error).message}`, { role: PXT });