diff --git a/src/server.test.ts b/src/server.test.ts index ae1fe45..faf69a3 100644 --- a/src/server.test.ts +++ b/src/server.test.ts @@ -103,4 +103,17 @@ describe("API endpoints", () => { assert.equal(response.statusCode, 404); }); + + it("returns a generic 500 response for unexpected error", async () => { + const response = await app.inject({ + method: "GET", + url: "/test-error", + }); + + assert.equal(response.statusCode, 500); + assert.deepEqual(response.json(), { + error: "Internal Server Error", + }); + assert.equal(response.body.includes("Sensitive internal detail"), false); + }); }); diff --git a/src/server.ts b/src/server.ts index 2587f6f..1f2df2b 100644 --- a/src/server.ts +++ b/src/server.ts @@ -1,4 +1,4 @@ -import Fastify from "fastify"; +import Fastify, { type FastifyError } from "fastify"; import { z } from "zod"; const echoSchema = z.object({ @@ -10,6 +10,20 @@ export const buildApp = () => { logger: true, }); + app.setErrorHandler((error: FastifyError, request, reply) => { + request.log.error(error); + + const candidateStatusCode = error.statusCode ?? 500; + const statusCode = + candidateStatusCode >= 400 && candidateStatusCode < 500 + ? candidateStatusCode + : 500; + + return reply.code(statusCode).send({ + error: statusCode === 500 ? "Internal Server Error" : "Request Error", + }); + }); + app.get("/health", async () => ({ status: "ok", })); @@ -35,6 +49,10 @@ export const buildApp = () => { }; }); + app.get("/test-error", async () => { + throw new Error("Sensitive internal detail"); + }); + return app; };