import { expect, test, type APIRequestContext, type Playwright } from "@playwright/test"; import fs from "node:fs"; import path from "node:path"; const url = "http://localhost:5302/api"; const password = "E2ePassword123!"; type Account = { api: APIRequestContext; headers: Record; email: string }; async function login(playwright: Playwright, email: string): Promise { const api = await playwright.request.newContext(); expect((await api.post(`${url}/auth/login`, { data: { email, password } })).status()).toBe(200); const me = await (await api.get(`${url}/auth/me`)).json(); const csrf = (await api.storageState()).cookies.find(cookie => cookie.name === "XSRF-TOKEN")!.value; return { api, email, headers: { "X-CSRF-TOKEN": csrf, "X-Session-Context": me.sessionContext } }; } const root = () => process.env.PDF_STUB_ROOT!; const starts = () => fs.readdirSync(root()).filter(file => file.endsWith(".started")); function running() { return starts().filter(file => { try { process.kill(Number.parseInt(file), 0); return true; } catch { return false; } }).length; } test("authenticated bursts across variant, job and public routes share actual render capacity and recover", async ({ playwright }, info) => { const admin = await login(playwright, "e2e@example.test"); const accounts: Account[] = []; const pending: Array> = []; fs.writeFileSync(path.join(root(), "mode"), "hold"); let maximumActualRenders = 0; const sampler = setInterval(() => { maximumActualRenders = Math.max(maximumActualRenders, running()); }, 10); try { for (const label of ["a", "b", "c"]) { const email = `pdf-${label}-${Date.now()}@example.test`; expect((await admin.api.post(`${url}/users`, { headers: admin.headers, data: { email, password, roles: [] } })).status()).toBe(200); accounts.push(await login(playwright, email)); } const [a, b, c] = accounts; const aSecondSession = await login(playwright, a.email); accounts.push(aSecondSession); const variantIds: number[] = []; for (const owner of [a, b, c]) { const response = await owner.api.post(`${url}/cv/variants`, { headers: owner.headers, data: { name: "Synthetic PDF" } }); expect(response.status()).toBe(200); variantIds.push((await response.json()).id); } const secondVariant = await (await a.api.post(`${url}/cv/variants`, { headers: a.headers, data: { name: "Another A variant" } })).json(); const published = await (await a.api.put(`${url}/cv/variants/${variantIds[0]}/public`, { headers: a.headers, data: { isPublic: true } })).json(); const company = await (await c.api.post(`${url}/companies`, { headers: c.headers, data: { name: "Synthetic employer" } })).json(); const job = await (await c.api.post(`${url}/jobapplications`, { headers: c.headers, data: { companyId: company.id, jobTitle: "Engineer", status: "Saved" } })).json(); const variant = (owner: Account, id: number) => owner.api.post(`${url}/cv/variants/${id}/export-pdf`, { headers: owner.headers, data: {} }); const first = variant(a, variantIds[0]); pending.push(first); await expect.poll(() => starts().length).toBe(1); const sameOwner = await variant(aSecondSession, secondVariant.id); expect(sameOwner.status()).toBe(503); // Even with a spare global slot. expect((await sameOwner.json()).code).toBe("pdf_capacity_exceeded"); const second = variant(b, variantIds[1]); pending.push(second); await expect.poll(() => starts().length).toBe(2); const anonymous = await playwright.request.newContext(); accounts.push({ api: anonymous, headers: {}, email: "anonymous" }); const burst = await Promise.all([ ...Array.from({ length: 6 }, () => variant(aSecondSession, secondVariant.id)), variant(c, variantIds[2]), c.api.post(`${url}/jobapplications/${job.id}/export-tailored-cv-pdf`, { headers: c.headers, data: {} }), anonymous.get(`${url}/public-cv/${published.publicSlug}/pdf`), ]); for (const response of burst) { expect(response.status()).toBe(503); expect(response.headers()["retry-after"]).toBe("3"); expect(await response.json()).toMatchObject({ code: "pdf_capacity_exceeded" }); } expect(starts()).toHaveLength(2); maximumActualRenders = Math.max(maximumActualRenders, running()); expect(maximumActualRenders).toBe(2); // Admission never substitutes for B02 or resource ownership. expect((await c.api.post(`${url}/cv/variants/${variantIds[2]}/export-pdf`, { headers: { "X-CSRF-TOKEN": c.headers["X-CSRF-TOKEN"] }, data: {} })).status()).toBe(428); expect((await c.api.post(`${url}/cv/variants/${variantIds[0]}/export-pdf`, { headers: c.headers, data: {} })).status()).toBe(404); fs.writeFileSync(path.join(root(), "release"), "release"); for (const response of await Promise.all([first, second])) { expect(response.status()).toBe(200); expect((await response.body()).subarray(0, 5).toString()).toBe("%PDF-"); } await expect.poll(running).toBe(0); const recovered = await c.api.post(`${url}/jobapplications/${job.id}/export-tailored-cv-pdf`, { headers: c.headers, data: {} }); expect(recovered.status()).toBe(200); await expect.poll(running).toBe(0); await info.attach("bounded-admission-observations", { contentType: "application/json", body: JSON.stringify({ configured: 2, admittedDuringBurst: 2, rejected: burst.length + 1, queued: 0, maximumActualRenders, busyStatus: 503, retryAfterSeconds: 3, recoveryStatus: recovered.status() }, null, 2) }); } finally { fs.writeFileSync(path.join(root(), "release"), "release"); await Promise.allSettled(pending); clearInterval(sampler); await Promise.all([admin, ...accounts].map(account => account.api.dispose())); } });