125 lines
No EOL
3.8 KiB
TypeScript
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/);
|
|
});
|
|
}); |