import { describe, it, expect, vi, beforeEach } from "vitest"; import express from "express"; import request from "supertest"; vi.mock("../services/browser.js", () => ({ renderPdf: vi.fn(), renderUrlPdf: vi.fn(), })); let app: express.Express; beforeEach(async () => { vi.clearAllMocks(); vi.resetModules(); const { renderPdf } = await import("../services/browser.js"); vi.mocked(renderPdf).mockResolvedValue({ pdf: Buffer.from("%PDF-1.4 mock"), durationMs: 10 }); const { demoRouter } = await import("../routes/demo.js"); app = express(); app.use(express.json({ limit: "500kb" })); app.use("/v1/demo", demoRouter); }); describe("POST /v1/demo/html", () => { it("returns 400 for missing html", async () => { const res = await request(app) .post("/v1/demo/html") .set("content-type", "application/json") .send({}); expect(res.status).toBe(400); }); it("returns 415 for wrong content-type", async () => { const res = await request(app) .post("/v1/demo/html") .set("content-type", "text/plain") .send("html=

hi

"); expect(res.status).toBe(415); }); it("returns 503 on QUEUE_FULL", async () => { const { renderPdf } = await import("../services/browser.js"); vi.mocked(renderPdf).mockRejectedValue(new Error("QUEUE_FULL")); const res = await request(app) .post("/v1/demo/html") .set("content-type", "application/json") .send({ html: "

Hello

" }); expect(res.status).toBe(503); }); it("returns 504 on PDF_TIMEOUT", async () => { const { renderPdf } = await import("../services/browser.js"); vi.mocked(renderPdf).mockRejectedValue(new Error("PDF_TIMEOUT")); const res = await request(app) .post("/v1/demo/html") .set("content-type", "application/json") .send({ html: "

Hello

" }); expect(res.status).toBe(504); }); it("returns 500 on unexpected error", async () => { const { renderPdf } = await import("../services/browser.js"); vi.mocked(renderPdf).mockRejectedValue(new Error("something broke")); const res = await request(app) .post("/v1/demo/html") .set("content-type", "application/json") .send({ html: "

Hello

" }); expect(res.status).toBe(500); expect(res.body.error).toMatch(/PDF generation failed/); }); it("returns PDF with watermark on success", async () => { const { renderPdf } = await import("../services/browser.js"); const res = await request(app) .post("/v1/demo/html") .set("content-type", "application/json") .send({ html: "

Hello

" }); expect(res.status).toBe(200); expect(res.headers["content-type"]).toMatch(/application\/pdf/); // Verify watermark was injected into the HTML passed to renderPdf const calledHtml = vi.mocked(renderPdf).mock.calls[0][0]; expect(calledHtml).toContain("DEMO"); expect(calledHtml).toContain("docfast.dev"); }); it("returns 400 for invalid scale", async () => { const res = await request(app) .post("/v1/demo/html") .set("content-type", "application/json") .send({ html: "

Hello

", scale: 99 }); expect(res.status).toBe(400); expect(res.body.error).toMatch(/scale/); }); it("returns 400 for invalid format", async () => { const res = await request(app) .post("/v1/demo/html") .set("content-type", "application/json") .send({ html: "

Hello

", format: "INVALID" }); expect(res.status).toBe(400); expect(res.body.error).toMatch(/format/); }); it("returns 400 for non-boolean landscape", async () => { const res = await request(app) .post("/v1/demo/html") .set("content-type", "application/json") .send({ html: "

Hello

", landscape: "yes" }); expect(res.status).toBe(400); expect(res.body.error).toMatch(/landscape/); }); it("returns 400 for invalid margin", async () => { const res = await request(app) .post("/v1/demo/html") .set("content-type", "application/json") .send({ html: "

Hello

", margin: "10px" }); expect(res.status).toBe(400); expect(res.body.error).toMatch(/margin/); }); }); describe("POST /v1/demo/markdown", () => { it("returns 400 for missing markdown", async () => { const res = await request(app) .post("/v1/demo/markdown") .set("content-type", "application/json") .send({}); expect(res.status).toBe(400); }); it("returns 415 for wrong content-type", async () => { const res = await request(app) .post("/v1/demo/markdown") .set("content-type", "text/plain") .send("markdown=# hi"); expect(res.status).toBe(415); }); it("returns 503 on QUEUE_FULL", async () => { const { renderPdf } = await import("../services/browser.js"); vi.mocked(renderPdf).mockRejectedValue(new Error("QUEUE_FULL")); const res = await request(app) .post("/v1/demo/markdown") .set("content-type", "application/json") .send({ markdown: "# Hello" }); expect(res.status).toBe(503); }); it("returns 504 on PDF_TIMEOUT", async () => { const { renderPdf } = await import("../services/browser.js"); vi.mocked(renderPdf).mockRejectedValue(new Error("PDF_TIMEOUT")); const res = await request(app) .post("/v1/demo/markdown") .set("content-type", "application/json") .send({ markdown: "# Hello" }); expect(res.status).toBe(504); }); it("returns 500 on unexpected error", async () => { const { renderPdf } = await import("../services/browser.js"); vi.mocked(renderPdf).mockRejectedValue(new Error("something broke")); const res = await request(app) .post("/v1/demo/markdown") .set("content-type", "application/json") .send({ markdown: "# Hello" }); expect(res.status).toBe(500); expect(res.body.error).toMatch(/PDF generation failed/); }); it("returns PDF with watermark on success", async () => { const { renderPdf } = await import("../services/browser.js"); const res = await request(app) .post("/v1/demo/markdown") .set("content-type", "application/json") .send({ markdown: "# Hello World" }); expect(res.status).toBe(200); expect(res.headers["content-type"]).toMatch(/application\/pdf/); const calledHtml = vi.mocked(renderPdf).mock.calls[0][0]; expect(calledHtml).toContain("DEMO"); expect(calledHtml).toContain("docfast.dev"); }); it("returns 400 for invalid scale", async () => { const res = await request(app) .post("/v1/demo/markdown") .set("content-type", "application/json") .send({ markdown: "# Hello", scale: 99 }); expect(res.status).toBe(400); expect(res.body.error).toMatch(/scale/); }); it("returns 400 for invalid format", async () => { const res = await request(app) .post("/v1/demo/markdown") .set("content-type", "application/json") .send({ markdown: "# Hello", format: "INVALID" }); expect(res.status).toBe(400); expect(res.body.error).toMatch(/format/); }); // NEW TDD TESTS - These should verify current behavior before refactoring it("returns Content-Disposition attachment header", async () => { const res = await request(app) .post("/v1/demo/markdown") .set("content-type", "application/json") .send({ markdown: "# Hello" }); expect(res.status).toBe(200); expect(res.headers["content-disposition"]).toMatch(/attachment/); expect(res.headers["content-disposition"]).toMatch(/filename="demo\.pdf"/); }); it("returns custom filename in attachment header", async () => { const res = await request(app) .post("/v1/demo/markdown") .set("content-type", "application/json") .send({ markdown: "# Hello", filename: "custom.pdf" }); expect(res.status).toBe(200); expect(res.headers["content-disposition"]).toMatch(/attachment/); expect(res.headers["content-disposition"]).toMatch(/filename="custom\.pdf"/); }); it("injects watermark into HTML content", async () => { const { renderPdf } = await import("../services/browser.js"); const res = await request(app) .post("/v1/demo/markdown") .set("content-type", "application/json") .send({ markdown: "# Hello" }); expect(res.status).toBe(200); const calledHtml = vi.mocked(renderPdf).mock.calls[0][0]; expect(calledHtml).toContain("Generated by DocFast — docfast.dev"); expect(calledHtml).toContain("Upgrade to Pro for clean PDFs"); expect(calledHtml).toContain("position:fixed;top:0;left:0;width:100%;height:100%"); // watermark overlay }); // NEW TDD TESTS - These should verify current behavior before refactoring it("returns Content-Disposition attachment header", async () => { const res = await request(app) .post("/v1/demo/html") .set("content-type", "application/json") .send({ html: "

Hello

" }); expect(res.status).toBe(200); expect(res.headers["content-disposition"]).toMatch(/attachment/); expect(res.headers["content-disposition"]).toMatch(/filename="demo\.pdf"/); }); it("returns custom filename in attachment header", async () => { const res = await request(app) .post("/v1/demo/html") .set("content-type", "application/json") .send({ html: "

Hello

", filename: "custom.pdf" }); expect(res.status).toBe(200); expect(res.headers["content-disposition"]).toMatch(/attachment/); expect(res.headers["content-disposition"]).toMatch(/filename="custom\.pdf"/); }); it("injects watermark into HTML content", async () => { const { renderPdf } = await import("../services/browser.js"); const res = await request(app) .post("/v1/demo/html") .set("content-type", "application/json") .send({ html: "

Hello

" }); expect(res.status).toBe(200); const calledHtml = vi.mocked(renderPdf).mock.calls[0][0]; expect(calledHtml).toContain("Generated by DocFast — docfast.dev"); expect(calledHtml).toContain("Upgrade to Pro for clean PDFs"); expect(calledHtml).toContain("position:fixed;top:0;left:0;width:100%;height:100%"); // watermark overlay }); });