import { describe, it, expect, vi, beforeEach } from "vitest"; import express from "express"; import request from "supertest"; // Mock browser service to return { pdf, durationMs } vi.mock("../services/browser.js", () => ({ renderPdf: vi.fn(), renderUrlPdf: vi.fn(), })); describe("PDF render timing", () => { describe("browser service return type", () => { it("renderPdf returns { pdf, durationMs }", async () => { // Reset modules to get unmocked version for this test vi.restoreAllMocks(); vi.resetModules(); // We can't easily test the real renderPdf without a browser, // so we test the shape via the mock contract and integration tests below }); }); describe("convert routes set X-Render-Time header", () => { let app: express.Express; beforeEach(async () => { vi.clearAllMocks(); vi.resetModules(); const { renderPdf, renderUrlPdf } = await import("../services/browser.js"); vi.mocked(renderPdf).mockResolvedValue({ pdf: Buffer.from("%PDF-1.4 mock"), durationMs: 42 } as any); vi.mocked(renderUrlPdf).mockResolvedValue({ pdf: Buffer.from("%PDF-1.4 mock"), durationMs: 55 } as any); const { convertRouter } = await import("../routes/convert.js"); app = express(); app.use(express.json({ limit: "500kb" })); app.use((req, _res, next) => { (req as any).apiKey = { id: "test-key", name: "test", tier: "free", active: true }; (req as any).keyRow = { id: "test-key", name: "test", tier: "free", active: true, owner_email: "test@test.com" }; next(); }); app.use("/v1/convert", convertRouter); }); it("POST /v1/convert/html sets X-Render-Time header", async () => { const res = await request(app) .post("/v1/convert/html") .set("content-type", "application/json") .send({ html: "

Hello

" }); expect(res.status).toBe(200); expect(res.headers["x-render-time"]).toBe("42"); }); it("POST /v1/convert/markdown sets X-Render-Time header", async () => { const res = await request(app) .post("/v1/convert/markdown") .set("content-type", "application/json") .send({ markdown: "# Hello" }); expect(res.status).toBe(200); expect(res.headers["x-render-time"]).toBe("42"); }); }); describe("demo routes set X-Render-Time header", () => { 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: 99 } as any); const { demoRouter } = await import("../routes/demo.js"); app = express(); app.use(express.json({ limit: "500kb" })); app.use("/v1/demo", demoRouter); }); it("POST /v1/demo/html sets X-Render-Time 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["x-render-time"]).toBe("99"); }); it("POST /v1/demo/markdown sets X-Render-Time 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["x-render-time"]).toBe("99"); }); }); describe("templates route destructures correctly", () => { // Templates route doesn't need X-Render-Time header per task, // but must correctly destructure { pdf, durationMs } 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: 30 } as any); const dbMod = await import("../services/db.js"); if (vi.isMockFunction(dbMod.default?.prepare)) { // db is already mocked elsewhere } }); it("templates route still returns valid PDF after refactor", async () => { // This is covered by existing templates-route tests; // we just verify the mock shape works const { renderPdf } = await import("../services/browser.js"); const result = await vi.mocked(renderPdf)("

test

"); expect(result).toHaveProperty("pdf"); expect(result).toHaveProperty("durationMs"); expect((result as any).durationMs).toBe(30); }); }); });