All checks were successful
Build & Deploy to Staging / Build & Deploy to Staging (push) Successful in 21m45s
223 lines
8.2 KiB
TypeScript
223 lines
8.2 KiB
TypeScript
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("Demo Branch Coverage", () => {
|
|
describe("injectWatermark fallback branch (line 19)", () => {
|
|
it("should append watermark when full HTML document doesn't contain </body> tag", async () => {
|
|
const { renderPdf } = await import("../services/browser.js");
|
|
// Send full HTML (with <html>) but without </body> to hit the fallback branch
|
|
const htmlWithoutClosingBody = `
|
|
<html>
|
|
<head><title>Test Page</title></head>
|
|
<body>
|
|
<h1>Hello</h1>
|
|
<p>Content here</p>
|
|
`;
|
|
|
|
const res = await request(app)
|
|
.post("/v1/demo/html")
|
|
.set("content-type", "application/json")
|
|
.send({ html: htmlWithoutClosingBody });
|
|
|
|
expect(res.status).toBe(200);
|
|
expect(res.headers["content-type"]).toMatch(/application\/pdf/);
|
|
|
|
// Verify watermark was appended (not replaced)
|
|
const calledHtml = vi.mocked(renderPdf).mock.calls[0][0];
|
|
|
|
// The fallback should append the watermark at the end
|
|
expect(calledHtml).toContain("Hello");
|
|
expect(calledHtml).toContain("Content here");
|
|
expect(calledHtml).toContain("DEMO");
|
|
expect(calledHtml).toContain("Generated by DocFast");
|
|
|
|
// Ensure the original HTML is preserved before the watermark
|
|
expect(calledHtml.indexOf("Hello")).toBeLessThan(calledHtml.indexOf("DEMO"));
|
|
|
|
// Ensure watermark is appended at the end (since there's no </body> to replace)
|
|
const lastBodyCloseIndex = calledHtml.lastIndexOf("</body>");
|
|
const watermarkIndex = calledHtml.indexOf("Generated by DocFast");
|
|
// If there's a </body> at the very end (from wrapping), the watermark should be before it
|
|
if (lastBodyCloseIndex > -1) {
|
|
expect(watermarkIndex).toBeLessThan(lastBodyCloseIndex);
|
|
}
|
|
});
|
|
|
|
it("should append watermark to plain HTML fragment without </body>", async () => {
|
|
const { renderPdf } = await import("../services/browser.js");
|
|
const res = await request(app)
|
|
.post("/v1/demo/html")
|
|
.set("content-type", "application/json")
|
|
.send({ html: "<div>Simple fragment</div>" });
|
|
|
|
expect(res.status).toBe(200);
|
|
|
|
const calledHtml = vi.mocked(renderPdf).mock.calls[0][0];
|
|
expect(calledHtml).toContain("<div>Simple fragment</div>");
|
|
expect(calledHtml).toContain("DEMO");
|
|
expect(calledHtml).toContain("position:fixed;bottom:0;left:0;right:0;");
|
|
});
|
|
|
|
it("should handle markdown that results in HTML without </body> and injects watermark", async () => {
|
|
const { renderPdf } = await import("../services/browser.js");
|
|
const res = await request(app)
|
|
.post("/v1/demo/markdown")
|
|
.set("content-type", "application/json")
|
|
.send({ markdown: "# Just a heading\n\nSome text" });
|
|
|
|
expect(res.status).toBe(200);
|
|
|
|
const calledHtml = vi.mocked(renderPdf).mock.calls[0][0];
|
|
// Should contain watermark
|
|
expect(calledHtml).toContain("DEMO");
|
|
expect(calledHtml).toContain("Generated by DocFast");
|
|
expect(calledHtml).toContain("Upgrade to Pro for clean PDFs");
|
|
});
|
|
|
|
it("should still work correctly when HTML contains </body> (replace branch)", async () => {
|
|
const { renderPdf } = await import("../services/browser.js");
|
|
const fullHtml = `
|
|
<html>
|
|
<head><title>Test</title></head>
|
|
<body>
|
|
<h1>Complete HTML</h1>
|
|
<p>With closing body tag</p>
|
|
</body>
|
|
</html>
|
|
`;
|
|
|
|
const res = await request(app)
|
|
.post("/v1/demo/html")
|
|
.set("content-type", "application/json")
|
|
.send({ html: fullHtml });
|
|
|
|
expect(res.status).toBe(200);
|
|
|
|
const calledHtml = vi.mocked(renderPdf).mock.calls[0][0];
|
|
// When </body> exists, watermark should be injected before it
|
|
expect(calledHtml).toContain("</body>");
|
|
expect(calledHtml).toContain("DEMO");
|
|
|
|
// The watermark should be between the content and closing </body>
|
|
const watermarkIndex = calledHtml.indexOf("Generated by DocFast");
|
|
const closingBodyIndex = calledHtml.indexOf("</body>");
|
|
expect(watermarkIndex).toBeGreaterThan(-1);
|
|
expect(closingBodyIndex).toBeGreaterThan(-1);
|
|
expect(watermarkIndex).toBeLessThan(closingBodyIndex);
|
|
});
|
|
|
|
it("should reject empty HTML input with 400 error", async () => {
|
|
const res = await request(app)
|
|
.post("/v1/demo/html")
|
|
.set("content-type", "application/json")
|
|
.send({ html: "" });
|
|
|
|
// Empty HTML is rejected by validation
|
|
expect(res.status).toBe(400);
|
|
expect(res.body.error).toContain("html");
|
|
});
|
|
|
|
it("should handle HTML with multiple </body> tags (uses first)</body>", async () => {
|
|
const { renderPdf } = await import("../services/browser.js");
|
|
const htmlWithMultipleBodies = `
|
|
<html>
|
|
<body>First body</body>
|
|
<body>Second body</body>
|
|
</html>
|
|
`;
|
|
|
|
const res = await request(app)
|
|
.post("/v1/demo/html")
|
|
.set("content-type", "application/json")
|
|
.send({ html: htmlWithMultipleBodies });
|
|
|
|
expect(res.status).toBe(200);
|
|
|
|
const calledHtml = vi.mocked(renderPdf).mock.calls[0][0];
|
|
// replace only replaces the first occurrence
|
|
expect(calledHtml).toContain("First body");
|
|
expect(calledHtml).toContain("DEMO");
|
|
expect(calledHtml).toContain("</body>");
|
|
});
|
|
});
|
|
|
|
describe("Watermark content verification", () => {
|
|
it("should include demo watermark with exact styling", async () => {
|
|
const { renderPdf } = await import("../services/browser.js");
|
|
const res = await request(app)
|
|
.post("/v1/demo/html")
|
|
.set("content-type", "application/json")
|
|
.send({ html: "<h1>Test</h1>" });
|
|
|
|
expect(res.status).toBe(200);
|
|
|
|
const calledHtml = vi.mocked(renderPdf).mock.calls[0][0];
|
|
// Verify watermark styling
|
|
expect(calledHtml).toContain("background:rgba(52,211,153,0.92);color:#0b0d11");
|
|
expect(calledHtml).toContain("z-index:999999");
|
|
expect(calledHtml).toContain("pointer-events:none");
|
|
});
|
|
|
|
it("should preserve user CSS when injecting watermark", async () => {
|
|
const { renderPdf } = await import("../services/browser.js");
|
|
const customCss = "body { background: blue; }";
|
|
|
|
const res = await request(app)
|
|
.post("/v1/demo/html")
|
|
.set("content-type", "application/json")
|
|
.send({ html: "<h1>Test</h1>", css: customCss });
|
|
|
|
expect(res.status).toBe(200);
|
|
|
|
const calledHtml = vi.mocked(renderPdf).mock.calls[0][0];
|
|
// Both watermark and user CSS should be present
|
|
expect(calledHtml).toContain("DEMO");
|
|
expect(calledHtml).toContain("background: blue");
|
|
});
|
|
});
|
|
|
|
describe("Branch coverage for attachment headers", () => {
|
|
it("should set Content-Disposition to attachment for HTML", async () => {
|
|
const res = await request(app)
|
|
.post("/v1/demo/html")
|
|
.set("content-type", "application/json")
|
|
.send({ html: "<h1>Hello</h1>" });
|
|
|
|
expect(res.status).toBe(200);
|
|
expect(res.headers["content-disposition"]).toMatch(/^attachment/);
|
|
expect(res.headers["content-disposition"]).toMatch(/filename="demo\.pdf"/);
|
|
});
|
|
|
|
it("should set Content-Disposition to attachment for markdown", 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"/);
|
|
});
|
|
});
|
|
});
|