Files
jobtrackingapp/job-tracker-ui/pdf-tests/admission.spec.ts
T
cesnimda 0954876295 fix(security): enforce audited account, network and resource boundaries
Implement B01-B06 with regression coverage and retained validation evidence. Preserve the production ingress preflight; F04 runtime verification remains open.
2026-09-09 18:21:47 +02:00

91 lines
5.7 KiB
TypeScript

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<string, string>; email: string };
async function login(playwright: Playwright, email: string): Promise<Account> {
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<Promise<unknown>> = [];
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()));
}
});