diff --git a/src/__tests__/demo.test.ts b/src/__tests__/demo.test.ts index e613bfe..295f90c 100644 --- a/src/__tests__/demo.test.ts +++ b/src/__tests__/demo.test.ts @@ -83,6 +83,42 @@ describe("POST /v1/demo/html", () => { 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", () => { @@ -145,4 +181,22 @@ describe("POST /v1/demo/markdown", () => { 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/); + }); }); diff --git a/src/routes/demo.ts b/src/routes/demo.ts index fb8a094..8e5a28f 100644 --- a/src/routes/demo.ts +++ b/src/routes/demo.ts @@ -3,6 +3,8 @@ import rateLimit from "express-rate-limit"; import { renderPdf } from "../services/browser.js"; import { markdownToHtml, wrapHtml } from "../services/markdown.js"; import logger from "../services/logger.js"; +import { sanitizeFilename } from "../utils/sanitize.js"; +import { validatePdfOptions } from "../utils/pdf-options.js"; const router = Router(); @@ -42,10 +44,6 @@ interface DemoBody { filename?: string; } -function sanitizeFilename(name: string): string { - return name.replace(/[\x00-\x1f"\\\r\n]/g, "").trim() || "document.pdf"; -} - /** * @openapi * /v1/demo/html: @@ -114,6 +112,12 @@ router.post("/html", async (req: Request & { acquirePdfSlot?: () => Promise Promise< return; } + const validation = validatePdfOptions(body); + if (!validation.valid) { + res.status(400).json({ error: validation.error }); + return; + } + if (req.acquirePdfSlot) { await req.acquirePdfSlot(); slotAcquired = true;