import { describe, it, expect, vi, beforeEach } from "vitest"; import express from "express"; import request from "supertest"; let app: express.Express; beforeEach(async () => { vi.clearAllMocks(); vi.resetModules(); // Create a minimal test app that mimics the structure of the main app app = express(); // Add request ID middleware (used by error handler) app.use((req, _res, next) => { req.requestId = "test-request-id"; next(); }); // Add JSON parsing middleware app.use(express.json({ limit: "500kb" })); // Add test routes that can throw errors app.post("/v1/convert/html", (_req, _res, next) => { const err = new Error("Test API error"); next(err); }); app.get("/v1/test-error", (_req, _res, next) => { const err = new Error("Test V1 error"); next(err); }); app.get("/health/test-error", (_req, _res, next) => { const err = new Error("Test health error"); next(err); }); app.get("/non-api/test-error", (_req, _res, next) => { const err = new Error("Test non-API error"); next(err); }); // Import and add the global error handler from index.ts // We need to copy the exact error handler logic app.use((err: unknown, req: express.Request, res: express.Response, _next: express.NextFunction) => { const reqId = req.requestId || "unknown"; // Check if this is a JSON parse error from express.json() if (err instanceof SyntaxError && 'status' in err && (err as Record).status === 400 && 'body' in err) { if (!res.headersSent) { res.status(400).json({ error: "Invalid JSON in request body" }); } return; } if (!res.headersSent) { const isApi = req.path.startsWith("/v1/") || req.path.startsWith("/health"); if (isApi) { res.status(500).json({ error: "Internal server error" }); } else { res.status(500).send("Internal server error"); } } }); }); describe("global error handler", () => { it("returns 400 JSON response for invalid JSON body", async () => { const response = await request(app) .post("/v1/convert/html") .set("Content-Type", "application/json") .send("{ invalid json content") .expect(400); expect(response.body).toEqual({ error: "Invalid JSON in request body" }); expect(response.headers["content-type"]).toMatch(/application\/json/); }); it("returns 500 JSON response for errors on /v1/* API paths", async () => { const response = await request(app) .get("/v1/test-error") .expect(500); expect(response.body).toEqual({ error: "Internal server error" }); expect(response.headers["content-type"]).toMatch(/application\/json/); }); it("returns 500 JSON response for errors on /health API paths", async () => { const response = await request(app) .get("/health/test-error") .expect(500); expect(response.body).toEqual({ error: "Internal server error" }); expect(response.headers["content-type"]).toMatch(/application\/json/); }); it("returns 500 plain text response for errors on non-API paths", async () => { const response = await request(app) .get("/non-api/test-error") .expect(500); expect(response.text).toBe("Internal server error"); expect(response.headers["content-type"]).toMatch(/text\/html/); }); it("handles POST requests with valid JSON but route throws error (API path)", async () => { const response = await request(app) .post("/v1/convert/html") .set("Content-Type", "application/json") .send({ html: "

Test

" }) .expect(500); expect(response.body).toEqual({ error: "Internal server error" }); expect(response.headers["content-type"]).toMatch(/application\/json/); }); });