import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; /** * Die Obergrenze gleichzeitiger PDF-Erzeugungen (#041). * * **Eine eigene Datei, weil hier `@/server/pdf` *nicht* gemockt werden darf.** * `tests/pdf.test.ts` ersetzt `seiteAlsPdf`, um den Route Handler zu fahren; * hier läuft genau diese Funktion echt, und nur der Browser darunter ist * gestellt. Sonst prüfte der Test seine eigene Attrappe. * * Geprüft wird das Akzeptanzkriterium „zwei gleichzeitige Erzeugungen laufen, * die dritte wird abgewiesen“ — und dass die Grenze sich wieder öffnet. Eine * Grenze, die nach einem Fehler klemmt, legt die PDF-Erzeugung bis zum * Neustart still, und zwar leise. */ /** Hält jede laufende Erzeugung an, bis der Test sie freigibt. */ let anhalten: Array<() => void> = []; let laufendeSeiten = 0; const seiteGestellt = { emulateMedia: async () => undefined, goto: async () => ({ status: () => 200 }), evaluate: async () => undefined, pdf: async () => { laufendeSeiten += 1; await new Promise((weiter) => anhalten.push(weiter)); return new Uint8Array(Buffer.from("%PDF-1.4\n%%EOF\n", "utf8")); }, }; const browserGestellt = { isConnected: () => true, newContext: async () => ({ newPage: async () => seiteGestellt, close: async () => undefined, }), close: async () => undefined, }; vi.mock("playwright-core", () => ({ chromium: { launch: async () => browserGestellt }, })); const { seiteAlsPdf, PdfFehler, pdfVorratSchliessen } = await import("@/server/pdf"); const VORHER = { pfad: process.env["CHROMIUM_PFAD"], max: process.env["PDF_MAX_GLEICHZEITIG"], }; beforeEach(() => { // Irgendeine Datei, die es gibt – `chromiumPfad` prüft nur, ob dort etwas liegt. process.env["CHROMIUM_PFAD"] = process.execPath; anhalten = []; laufendeSeiten = 0; }); afterEach(async () => { // Alles freigeben, sonst hängt ein Test am nächsten. for (const weiter of anhalten) weiter(); await pdfVorratSchliessen(); for (const [name, wert] of [ ["CHROMIUM_PFAD", VORHER.pfad], ["PDF_MAX_GLEICHZEITIG", VORHER.max], ] as const) { if (wert === undefined) delete process.env[name]; else process.env[name] = wert; } }); describe("Die Obergrenze gleichzeitiger PDF-Erzeugungen", () => { it("lässt so viele zu wie eingestellt und weist die nächste ab", async () => { process.env["PDF_MAX_GLEICHZEITIG"] = "2"; const erste = seiteAlsPdf("/drucken/lauf/a", null); const zweite = seiteAlsPdf("/drucken/lauf/b", null); // Beide müssen wirklich angefangen haben, bevor die dritte kommt. await vi.waitFor(() => expect(laufendeSeiten).toBe(2)); await expect(seiteAlsPdf("/drucken/lauf/c", null)).rejects.toThrow(PdfFehler); await expect(seiteAlsPdf("/drucken/lauf/c", null)).rejects.toThrow(/noch einmal versuchen/u); // Die dritte hat keinen Browser angefasst. expect(laufendeSeiten).toBe(2); for (const weiter of anhalten) weiter(); await expect(erste).resolves.toBeInstanceOf(Uint8Array); await expect(zweite).resolves.toBeInstanceOf(Uint8Array); }); it("gibt den Platz wieder frei, sobald eine fertig ist", async () => { process.env["PDF_MAX_GLEICHZEITIG"] = "1"; const erste = seiteAlsPdf("/drucken/lauf/a", null); await vi.waitFor(() => expect(laufendeSeiten).toBe(1)); await expect(seiteAlsPdf("/drucken/lauf/b", null)).rejects.toThrow(PdfFehler); anhalten.shift()?.(); await erste; // Und jetzt geht es wieder – die Grenze zählt herunter, nicht nur herauf. const zweite = seiteAlsPdf("/drucken/lauf/b", null); await vi.waitFor(() => expect(laufendeSeiten).toBe(2)); anhalten.pop()?.(); await expect(zweite).resolves.toBeInstanceOf(Uint8Array); }); it("gibt den Platz auch frei, wenn die Erzeugung scheitert", async () => { /* * **Der Fall, der eine Grenze still klemmen lässt.** Steht die Freigabe * nicht im `finally`, zählt ein Fehlschlag den Zähler nie zurück: nach * `PDF_MAX_GLEICHZEITIG` Fehlern entsteht bis zum Neustart kein PDF mehr, * und die Meldung spricht von Gleichzeitigkeit, obwohl niemand etwas tut. */ process.env["PDF_MAX_GLEICHZEITIG"] = "1"; const kaputt = vi.spyOn(seiteGestellt, "goto").mockResolvedValueOnce({ status: () => 500 }); await expect(seiteAlsPdf("/drucken/lauf/a", null)).rejects.toThrow(PdfFehler); kaputt.mockRestore(); const danach = seiteAlsPdf("/drucken/lauf/b", null); await vi.waitFor(() => expect(laufendeSeiten).toBe(1)); anhalten.pop()?.(); await expect(danach).resolves.toBeInstanceOf(Uint8Array); }); });