test: add billing and convert route tests
All checks were successful
Build & Deploy to Staging / Build & Deploy to Staging (push) Successful in 12m25s
All checks were successful
Build & Deploy to Staging / Build & Deploy to Staging (push) Successful in 12m25s
This commit is contained in:
parent
1fe3f3746a
commit
f0e9a79606
2 changed files with 478 additions and 0 deletions
184
src/__tests__/convert.test.ts
Normal file
184
src/__tests__/convert.test.ts
Normal file
|
|
@ -0,0 +1,184 @@
|
|||
import { describe, it, expect, vi, beforeEach } from "vitest";
|
||||
import express from "express";
|
||||
import request from "supertest";
|
||||
|
||||
// Mock dns before imports
|
||||
vi.mock("node:dns/promises", () => ({
|
||||
default: { lookup: vi.fn() },
|
||||
lookup: vi.fn(),
|
||||
}));
|
||||
|
||||
let app: express.Express;
|
||||
|
||||
beforeEach(async () => {
|
||||
vi.clearAllMocks();
|
||||
vi.resetModules();
|
||||
|
||||
const { renderPdf, renderUrlPdf } = await import("../services/browser.js");
|
||||
vi.mocked(renderPdf).mockResolvedValue(Buffer.from("%PDF-1.4 mock"));
|
||||
vi.mocked(renderUrlPdf).mockResolvedValue(Buffer.from("%PDF-1.4 mock url"));
|
||||
|
||||
const dns = await import("node:dns/promises");
|
||||
vi.mocked(dns.default.lookup).mockResolvedValue({ address: "93.184.216.34", family: 4 } as any);
|
||||
|
||||
const { convertRouter } = await import("../routes/convert.js");
|
||||
app = express();
|
||||
app.use(express.json({ limit: "500kb" }));
|
||||
app.use("/v1/convert", convertRouter);
|
||||
});
|
||||
|
||||
describe("POST /v1/convert/html", () => {
|
||||
it("returns 400 for missing html", async () => {
|
||||
const res = await request(app)
|
||||
.post("/v1/convert/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/convert/html")
|
||||
.set("content-type", "text/plain")
|
||||
.send("html=<h1>hi</h1>");
|
||||
expect(res.status).toBe(415);
|
||||
});
|
||||
|
||||
it("returns PDF on success", async () => {
|
||||
const res = await request(app)
|
||||
.post("/v1/convert/html")
|
||||
.set("content-type", "application/json")
|
||||
.send({ html: "<h1>Hello</h1>" });
|
||||
expect(res.status).toBe(200);
|
||||
expect(res.headers["content-type"]).toMatch(/application\/pdf/);
|
||||
});
|
||||
|
||||
it("returns 429 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/convert/html")
|
||||
.set("content-type", "application/json")
|
||||
.send({ html: "<h1>Hello</h1>" });
|
||||
expect(res.status).toBe(429);
|
||||
});
|
||||
|
||||
it("returns 500 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/convert/html")
|
||||
.set("content-type", "application/json")
|
||||
.send({ html: "<h1>Hello</h1>" });
|
||||
expect(res.status).toBe(500);
|
||||
expect(res.body.error).toMatch(/PDF_TIMEOUT/);
|
||||
});
|
||||
|
||||
it("wraps fragments (no <html tag) with wrapHtml", async () => {
|
||||
const { renderPdf } = await import("../services/browser.js");
|
||||
await request(app)
|
||||
.post("/v1/convert/html")
|
||||
.set("content-type", "application/json")
|
||||
.send({ html: "<h1>Fragment</h1>" });
|
||||
// wrapHtml should have been called; renderPdf receives wrapped HTML
|
||||
const calledHtml = vi.mocked(renderPdf).mock.calls[0][0];
|
||||
expect(calledHtml).toContain("<html");
|
||||
});
|
||||
|
||||
it("passes full HTML documents as-is", async () => {
|
||||
const { renderPdf } = await import("../services/browser.js");
|
||||
const fullDoc = "<html><body><h1>Full</h1></body></html>";
|
||||
await request(app)
|
||||
.post("/v1/convert/html")
|
||||
.set("content-type", "application/json")
|
||||
.send({ html: fullDoc });
|
||||
const calledHtml = vi.mocked(renderPdf).mock.calls[0][0];
|
||||
expect(calledHtml).toBe(fullDoc);
|
||||
});
|
||||
});
|
||||
|
||||
describe("POST /v1/convert/markdown", () => {
|
||||
it("returns 400 for missing markdown", async () => {
|
||||
const res = await request(app)
|
||||
.post("/v1/convert/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/convert/markdown")
|
||||
.set("content-type", "text/plain")
|
||||
.send("markdown=# hi");
|
||||
expect(res.status).toBe(415);
|
||||
});
|
||||
|
||||
it("returns PDF on success", async () => {
|
||||
const res = await request(app)
|
||||
.post("/v1/convert/markdown")
|
||||
.set("content-type", "application/json")
|
||||
.send({ markdown: "# Hello World" });
|
||||
expect(res.status).toBe(200);
|
||||
expect(res.headers["content-type"]).toMatch(/application\/pdf/);
|
||||
});
|
||||
});
|
||||
|
||||
describe("POST /v1/convert/url", () => {
|
||||
it("returns 400 for missing url", async () => {
|
||||
const res = await request(app)
|
||||
.post("/v1/convert/url")
|
||||
.set("content-type", "application/json")
|
||||
.send({});
|
||||
expect(res.status).toBe(400);
|
||||
});
|
||||
|
||||
it("returns 400 for invalid URL", async () => {
|
||||
const res = await request(app)
|
||||
.post("/v1/convert/url")
|
||||
.set("content-type", "application/json")
|
||||
.send({ url: "not a url" });
|
||||
expect(res.status).toBe(400);
|
||||
expect(res.body.error).toMatch(/Invalid URL/);
|
||||
});
|
||||
|
||||
it("returns 400 for non-http protocol", async () => {
|
||||
const res = await request(app)
|
||||
.post("/v1/convert/url")
|
||||
.set("content-type", "application/json")
|
||||
.send({ url: "ftp://example.com" });
|
||||
expect(res.status).toBe(400);
|
||||
expect(res.body.error).toMatch(/http\/https/);
|
||||
});
|
||||
|
||||
it("returns 400 for private IP", async () => {
|
||||
const dns = await import("node:dns/promises");
|
||||
vi.mocked(dns.default.lookup).mockResolvedValue({ address: "192.168.1.1", family: 4 } as any);
|
||||
const res = await request(app)
|
||||
.post("/v1/convert/url")
|
||||
.set("content-type", "application/json")
|
||||
.send({ url: "https://internal.example.com" });
|
||||
expect(res.status).toBe(400);
|
||||
expect(res.body.error).toMatch(/private/i);
|
||||
});
|
||||
|
||||
it("returns 400 for DNS failure", async () => {
|
||||
const dns = await import("node:dns/promises");
|
||||
vi.mocked(dns.default.lookup).mockRejectedValue(new Error("ENOTFOUND"));
|
||||
const res = await request(app)
|
||||
.post("/v1/convert/url")
|
||||
.set("content-type", "application/json")
|
||||
.send({ url: "https://nonexistent.example.com" });
|
||||
expect(res.status).toBe(400);
|
||||
expect(res.body.error).toMatch(/DNS/);
|
||||
});
|
||||
|
||||
it("returns PDF on success", async () => {
|
||||
const res = await request(app)
|
||||
.post("/v1/convert/url")
|
||||
.set("content-type", "application/json")
|
||||
.send({ url: "https://example.com" });
|
||||
expect(res.status).toBe(200);
|
||||
expect(res.headers["content-type"]).toMatch(/application\/pdf/);
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue