import { DEFAULT_FONTS, PondFonts } from '@dorfteich/shared'; import { PDFParse } from 'pdf-parse'; import { beforeAll, describe, expect, it, TestContext } from 'vitest'; import { AppConfig } from '../config/app-config.service'; import { GotenbergHttpRenderer } from './gotenberg.renderer'; import { buildPdfHtml } from './pdf-html'; /** * PDF export smoke check (issue #69, ADR 0009): renders a known page to PDF * through the real pinned Gotenberg and asserts the output is a PDF whose * extracted text carries the expected strings, with a sane page count. This * catches a broken renderer or a template regression that unit tests (which use * a fake renderer) cannot. Needs a reachable Gotenberg sidecar * (`gotenberg/gotenberg:8`); the test skips itself when none is configured, and * CI starts one and points `GOTENBERG_URL` at it. */ const GOTENBERG_URL = process.env.GOTENBERG_URL ?? 'http://localhost:3000'; const renderer = new GotenbergHttpRenderer({ env: { GOTENBERG_URL } } as unknown as AppConfig); let reachable = false; /** Extract the concatenated text and page count from PDF bytes. */ async function readPdf(pdf: Buffer): Promise<{ text: string; pages: number }> { const parser = new PDFParse({ data: new Uint8Array(pdf) }); try { const result = await parser.getText(); return { text: result.text, pages: result.total }; } finally { await parser.destroy(); } } describe('PDF export smoke (real Gotenberg, issue #69)', () => { beforeAll(async () => { reachable = await renderer.reachable().catch(() => false); }); it('renders a page to a PDF containing its text, with a sane page count', async (ctx: TestContext) => { if (!reachable) ctx.skip(); const html = buildPdfHtml({ title: 'Pond Fidelity Report', pondName: 'Fidelity Pond', // A forced page break so we can assert multi-page sanity; the markers are // distinctive strings we can look for in the extracted text. bodyHtml: '

The northern reeds have spread noticeably this season.

' + '
' + '

Recorded water temperature was fourteen degrees.

', fonts: DEFAULT_FONTS as PondFonts, // No inlined font faces — the render falls back to the system stack, which // still produces selectable text (fonts are covered by the #66/#67 tests). fontFaceCss: '', }); const pdf = await renderer.renderHtmlToPdf(html); expect(pdf.subarray(0, 5).toString('latin1')).toBe('%PDF-'); const { text, pages } = await readPdf(pdf); // Title + pond name (from the header) and both body markers survive to text. expect(text).toContain('Pond Fidelity Report'); expect(text).toContain('Fidelity Pond'); expect(text).toContain('northern reeds'); expect(text).toContain('water temperature'); // Page-count sanity: the forced break must produce a second page (≥ 2), and // this trivial document must not balloon (≤ 3) — a runaway render from a CSS // regression would blow well past that. expect(pages).toBeGreaterThanOrEqual(2); expect(pages).toBeLessThanOrEqual(3); }); });