import { describe, expect, test } from "bun:test"; import { existsSync } from "node:fs"; import { chromium } from "playwright-core"; import type { InvoiceTemplateConfig } from "../db/schema"; import { buildInvoiceHtmlDocument, getInvoicePdfRenderOptions, renderInvoiceHtmlPdf, } from "./render-html-pdf"; import { DEFAULT_HTML_TEMPLATE_CONFIG } from "./templates-html"; import type { InvoiceTemplateData } from "./templates-html/types"; // Real Chromium is only available when a prior `playwright install chromium` // has run. Browser-dependent assertions are gated on this so CI never // downloads a browser or hits the network to pass. function chromiumAvailable(): boolean { try { const path = chromium.executablePath(); return typeof path === "string" && path.length > 0 && existsSync(path); } catch { return false; } } const available = chromiumAvailable(); const invoice: InvoiceTemplateData = { invoiceNumber: "INV-2602", issueDate: "2026-06-01", dueDate: "2026-06-15", periodStart: "2026-05-01", periodEnd: "2026-05-31", clientName: "Acme Corp", clientEmail: "ap@acme.example", clientAddress: "1 Market St\nSan Francisco, CA", subtotal: 250000, taxRate: 850, taxAmount: 21250, total: 271250, currency: "USD", notes: "Thanks for the great collaboration.", paymentNotes: "Wire to account 1234567890.", lineItems: [ { id: "li-1", description: "Engineering - 12.5 hours", quantity: 750, rate: 20000, amount: 250000, projectName: "Platform", projectColor: "#22d3ee", }, ], }; const config: InvoiceTemplateConfig = { ...DEFAULT_HTML_TEMPLATE_CONFIG, companyName: "Studio Scribe", companyEmail: "billing@scribe.example", }; describe("invoice PDF render options", () => { test("uses zero margins only for the full-bleed mandala template", () => { expect(getInvoicePdfRenderOptions("mandala")).toEqual({ paperSize: "Letter", printBackground: true, margin: "0", }); expect(getInvoicePdfRenderOptions("minimal")).toEqual({ paperSize: "Letter", printBackground: true, }); expect(getInvoicePdfRenderOptions("does-not-exist", "a4")).toEqual({ paperSize: "A4", printBackground: true, }); }); }); describe("buildInvoiceHtmlDocument (pure, no browser)", () => { test("emits a full self-contained document with print CSS and root vars", async () => { const html = await buildInvoiceHtmlDocument({ invoice, config }); expect(html.startsWith("")).toBe(true); // Template markup is inlined. expect(html).toContain("scribe-invoice"); expect(html).toContain("INV-2602"); // Shared print base + @page rule are present in
. expect(html).toContain("@page"); expect(html).toContain("print-color-adjust"); // configToCssVars palette is placed on :root. expect(html).toContain("--inv-primary"); }); test("defaults to the minimal template and honors mandala selection", async () => { const minimal = await buildInvoiceHtmlDocument({ invoice, config }); expect(minimal).toContain("t-minimal"); const mandala = await buildInvoiceHtmlDocument({ invoice, config, templateName: "mandala", }); expect(mandala).toContain("t-mandala"); }); test("passes a data: URI logo through unchanged", async () => { const dataLogo = "data:image/png;base64,AAAA"; const html = await buildInvoiceHtmlDocument({ invoice, config: { ...config, logo: dataLogo, showLogo: true }, }); // Document builds without throwing; data URIs are never fetched. expect(html.startsWith("")).toBe(true); }); test("never fetches a remote (http/https) logo URL and omits it (SSRF guard)", async () => { const originalFetch = globalThis.fetch; let fetchCalls = 0; globalThis.fetch = (async (...args: Parameters