import { APIRequestContext, request } from '@playwright/test'; import { E2E } from '../../playwright.config'; import { OFFICER } from './officer'; /** * The workflow endpoints, called as the officer. * * Used where a test's subject is what an approval *does* — the seafarer * number, the activated record, the two child applications — rather than which * buttons produce it. Driving six sections of wizard and a review screen to * reach a completion effect would make those tests about forms. * * Nothing here writes to the database directly. The completion effect runs * inside the API's approval transaction, so a test that faked the status would * assert against an approval that never happened. */ /** Routes served by the applicant-facing controller rather than the review one. */ const APPLICANT_STEPS = new Set(['submit', 'resubmit']); async function officerContext(): Promise { const context = await request.newContext({ baseURL: E2E.apiUrl }); const response = await context.post('/auth/login', { data: { email: OFFICER.email, password: OFFICER.password }, }); if (!response.ok()) { throw new Error( `Officer login failed (${response.status()}): ${await response.text()}`, ); } const body = await response.json(); const token = body?.token ?? body?.accessToken ?? body?.access_token; if (!token) { throw new Error(`No access token in login response: ${JSON.stringify(body)}`); } await context.dispose(); return request.newContext({ baseURL: E2E.apiUrl, extraHTTPHeaders: { Authorization: `Bearer ${token}` }, }); } export interface WorkflowStep { /** Route under the review controller, e.g. `claim`, `final-approve`. */ path: string; data?: Record; /** Set when a step is expected to be refused — the refusal is the assertion. */ expectFailure?: boolean; } /** * Runs a sequence of workflow calls against one application. * * Returns each step's status code so a caller can assert on a refusal as * readily as on a success — "an officer may not evaluate a registration" is a * result worth checking, not an error to swallow. */ export async function runWorkflow( applicationId: string, steps: WorkflowStep[], ): Promise { const api = await officerContext(); const codes: number[] = []; try { for (const step of steps) { // Applicant-side actions (`submit`, `resubmit`) live on the // applications controller; everything an officer does is on the review // controller. Routing by step keeps callers from having to know. const base = APPLICANT_STEPS.has(step.path) ? 'license-applications' : 'license-application-review'; const response = await api.post( `/${base}/${applicationId}/${step.path}`, { data: step.data ?? {} }, ); codes.push(response.status()); if (!step.expectFailure && !response.ok()) { throw new Error( `Step "${step.path}" failed (${response.status()}): ${await response.text()}`, ); } } } finally { await api.dispose(); } return codes; } /** Claim then final-approve — the whole officer path for a registration. */ export async function approveRegistration(applicationId: string): Promise { await runWorkflow(applicationId, [ { path: 'claim' }, { path: 'final-approve', data: { remark: 'E2E approval' } }, ]); }