diff --git a/src/__tests__/sanitize.test.ts b/src/__tests__/sanitize.test.ts new file mode 100644 index 0000000..550ecd0 --- /dev/null +++ b/src/__tests__/sanitize.test.ts @@ -0,0 +1,24 @@ +import { describe, it, expect } from "vitest"; +import { sanitizeFilename } from "../utils/sanitize.js"; + +describe("sanitizeFilename", () => { + it("passes normal filename through", () => { + expect(sanitizeFilename("report.pdf")).toBe("report.pdf"); + }); + it("replaces control characters", () => { + expect(sanitizeFilename("file\x00name.pdf")).toBe("file_name.pdf"); + }); + it("replaces quotes", () => { + expect(sanitizeFilename('file"name.pdf')).toBe("file_name.pdf"); + }); + it("returns default for empty string", () => { + expect(sanitizeFilename("")).toBe("document.pdf"); + }); + it("truncates to 200 characters", () => { + const long = "a".repeat(250) + ".pdf"; + expect(sanitizeFilename(long).length).toBeLessThanOrEqual(200); + }); + it("supports custom default name", () => { + expect(sanitizeFilename("", "invoice.pdf")).toBe("invoice.pdf"); + }); +}); diff --git a/src/__tests__/templates.test.ts b/src/__tests__/templates.test.ts new file mode 100644 index 0000000..52ca00b --- /dev/null +++ b/src/__tests__/templates.test.ts @@ -0,0 +1,57 @@ +import { describe, it, expect } from "vitest"; +import { renderTemplate, templates } from "../services/templates.js"; + +// Access esc via rendering — test that HTML entities are escaped in output +describe("Template rendering", () => { + it("throws for unknown template", () => { + expect(() => renderTemplate("nonexistent", {})).toThrow("not found"); + }); + + it("invoice renders with correct totals", () => { + const html = renderTemplate("invoice", { + invoiceNumber: "INV-001", + date: "2026-01-01", + from: { name: "Seller" }, + to: { name: "Buyer" }, + items: [{ description: "Widget", quantity: 2, unitPrice: 10, taxRate: 20 }], + }); + expect(html).toContain("INV-001"); + expect(html).toContain("€20.00"); // subtotal: 2*10 + expect(html).toContain("€4.00"); // tax: 20*0.2 + expect(html).toContain("€24.00"); // total + }); + + it("receipt renders with correct total", () => { + const html = renderTemplate("receipt", { + receiptNumber: "R-001", + date: "2026-01-01", + from: { name: "Shop" }, + items: [{ description: "Item A", amount: 15 }, { description: "Item B", amount: 25 }], + }); + expect(html).toContain("R-001"); + expect(html).toContain("€40.00"); + }); + + it("defaults currency to €", () => { + const html = renderTemplate("invoice", { + invoiceNumber: "X", date: "2026-01-01", + from: { name: "A" }, to: { name: "B" }, + items: [{ description: "Test", quantity: 1, unitPrice: 5 }], + }); + expect(html).toContain("€5.00"); + }); + + it("escapes HTML entities including single quotes", () => { + const html = renderTemplate("invoice", { + invoiceNumber: '', + date: "2026-01-01", + from: { name: "O'Brien & Co" }, + to: { name: "Bob" }, + items: [{ description: "Test", quantity: 1, unitPrice: 1 }], + }); + expect(html).not.toContain("