import { describe, it, expect, vi, beforeEach } from "vitest"; import express from "express"; import request from "supertest"; let app: express.Express; beforeEach(async () => { vi.clearAllMocks(); const { renderPdf } = await import("../services/browser.js"); vi.mocked(renderPdf).mockResolvedValue(Buffer.from("%PDF-1.4 mock")); const { authMiddleware } = await import("../middleware/auth.js"); const { usageMiddleware } = await import("../middleware/usage.js"); const { templatesRouter } = await import("../routes/templates.js"); app = express(); app.use(express.json()); app.use("/v1/templates", authMiddleware, usageMiddleware, templatesRouter); }); describe("GET /v1/templates", () => { it("returns template list with id, name, description, fields", async () => { const res = await request(app) .get("/v1/templates") .set("Authorization", "Bearer test-key"); expect(res.status).toBe(200); expect(res.body.templates).toBeInstanceOf(Array); expect(res.body.templates.length).toBeGreaterThan(0); const invoice = res.body.templates.find((t: any) => t.id === "invoice"); expect(invoice).toBeDefined(); expect(invoice.name).toBe("Invoice"); expect(invoice.description).toBeTruthy(); expect(invoice.fields).toBeInstanceOf(Array); expect(invoice.fields.length).toBeGreaterThan(0); }); it("requires auth (401 without key)", async () => { const res = await request(app).get("/v1/templates"); expect(res.status).toBe(401); }); }); describe("POST /v1/templates/:id/render", () => { const validInvoiceData = { invoiceNumber: "INV-001", date: "2026-01-15", from: { name: "Acme Corp" }, to: { name: "Client Inc" }, items: [{ description: "Service", quantity: 1, unitPrice: 100 }], }; it("returns 404 for unknown template", async () => { const res = await request(app) .post("/v1/templates/nonexistent/render") .set("Authorization", "Bearer test-key") .send({ foo: "bar" }); expect(res.status).toBe(404); expect(res.body.error).toContain("not found"); }); it("returns 400 with missing required fields listed", async () => { const res = await request(app) .post("/v1/templates/invoice/render") .set("Authorization", "Bearer test-key") .send({ invoiceNumber: "INV-001" }); expect(res.status).toBe(400); expect(res.body.missing).toBeInstanceOf(Array); expect(res.body.missing).toContain("date"); expect(res.body.missing).toContain("from"); }); it("renders invoice successfully, returns PDF", async () => { const res = await request(app) .post("/v1/templates/invoice/render") .set("Authorization", "Bearer test-key") .send(validInvoiceData); expect(res.status).toBe(200); expect(res.headers["content-type"]).toMatch(/application\/pdf/); expect(res.headers["content-disposition"]).toContain("invoice.pdf"); }); it("accepts data in req.body.data wrapper", async () => { const res = await request(app) .post("/v1/templates/invoice/render") .set("Authorization", "Bearer test-key") .send({ data: validInvoiceData }); expect(res.status).toBe(200); expect(res.headers["content-type"]).toMatch(/application\/pdf/); }); it("passes _format, _margin to renderPdf", async () => { const { renderPdf } = await import("../services/browser.js"); await request(app) .post("/v1/templates/invoice/render") .set("Authorization", "Bearer test-key") .send({ ...validInvoiceData, _format: "Letter", _margin: { top: "20mm", bottom: "20mm" }, }); expect(vi.mocked(renderPdf)).toHaveBeenCalledWith( expect.any(String), expect.objectContaining({ format: "Letter", margin: { top: "20mm", bottom: "20mm" }, }) ); }); it("sanitizes _filename in Content-Disposition", async () => { const res = await request(app) .post("/v1/templates/invoice/render") .set("Authorization", "Bearer test-key") .send({ ...validInvoiceData, _filename: 'evil"file\nname.pdf' }); expect(res.status).toBe(200); const disposition = res.headers["content-disposition"]; // The quote and newline in the input should be sanitized to underscores expect(disposition).toContain("evil_file_name.pdf"); expect(disposition).not.toContain("\n"); }); it("returns 500 on render error", async () => { const { renderPdf } = await import("../services/browser.js"); vi.mocked(renderPdf).mockRejectedValue(new Error("Browser crashed")); const res = await request(app) .post("/v1/templates/invoice/render") .set("Authorization", "Bearer test-key") .send(validInvoiceData); expect(res.status).toBe(500); expect(res.body.error).toContain("failed"); }); });