docfast/src/__tests__/global-error-handler.test.ts

125 lines
No EOL
3.8 KiB
TypeScript

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<string, unknown>).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: "<h1>Test</h1>" })
.expect(500);
expect(response.body).toEqual({
error: "Internal server error"
});
expect(response.headers["content-type"]).toMatch(/application\/json/);
});
});