Implemented a production-ready TypeScript/Express.js service to receive and log MailGun webhook events (delivered, failed, opened, clicked, etc.). Key features: - Webhook endpoint (POST /webhook) with comprehensive event logging - Full TypeScript type definitions for all MailGun event types - Prometheus metrics integration for monitoring - Health check endpoint (GET /ping) - Comprehensive Jest test suite with 87.76% coverage - Docker containerization with build scripts Removed template/example code: - All SQL/MSSQL dependencies and related code - Example auth router and middleware - PRTG metrics support (simplified to Prometheus only) - Unused middleware (CORS, IP whitelist, request parsing/validation) - Template documentation (kept only MailGun webhook API spec) The service is clean, minimal, and focused solely on receiving and logging MailGun webhook events to the console. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
120 lines
4.6 KiB
TypeScript
120 lines
4.6 KiB
TypeScript
import { errorRouter } from '../../src/routes/errorRouter';
|
|
import createError from "http-errors";
|
|
import { mockHttpContext } from "../helpers/mockHttpContext";
|
|
import { logWarn, resetAllLoggerMocks } from '../__mocks__/logger';
|
|
|
|
// Mock the logger module
|
|
jest.mock('../../src/lib/logger', () => require('../__mocks__/logger'));
|
|
|
|
describe("errorRouter", () => {
|
|
|
|
beforeEach(() => {
|
|
resetAllLoggerMocks();
|
|
});
|
|
|
|
test("should return string message 'page not found' in case of 404 error", async () => {
|
|
const err = createError(404)
|
|
const {req,res,next} = mockHttpContext();
|
|
|
|
await errorRouter(err, req, res, next);
|
|
|
|
expect(res.status).toHaveBeenCalledWith(404);
|
|
expect(res.setHeader).toHaveBeenCalledWith('Content-Type', "text/html");
|
|
expect(res.end).toHaveBeenCalledWith("page not found");
|
|
});
|
|
|
|
test("should log page not found warning in case of 404 error", async () => {
|
|
const err = createError(404)
|
|
const reqPath = "/some-path/";
|
|
const {req,res,next} = mockHttpContext({ reqPath });
|
|
|
|
await errorRouter(err, req, res, next);
|
|
|
|
expect(logWarn).toHaveBeenCalledWith(`page not found GET ${reqPath}`);
|
|
expect(logWarn).toHaveBeenCalledWith(`${err.name}:page ${reqPath} not found`);
|
|
});
|
|
|
|
test("should not send headers again if they are already sent", async () => {
|
|
const err = createError(404)
|
|
const {req,res,next} = mockHttpContext({ headersSent:true, writableEnded:true });
|
|
|
|
await errorRouter(err, req, res, next);
|
|
|
|
expect(res.status).not.toHaveBeenCalled();
|
|
expect(res.setHeader).not.toHaveBeenCalled();
|
|
expect(res.end).not.toHaveBeenCalled();
|
|
});
|
|
|
|
test("should call [end] method if it has NOT been called yet", async () => {
|
|
const err = createError(404)
|
|
const {req,res,next} = mockHttpContext({ headersSent:true, writableEnded:false });
|
|
|
|
await errorRouter(err, req, res, next);
|
|
|
|
expect(res.status).not.toHaveBeenCalled();
|
|
expect(res.setHeader).not.toHaveBeenCalled();
|
|
expect(res.end).toHaveBeenCalled();
|
|
});
|
|
|
|
test("should stop Prometheus Timer", async () => {
|
|
const err = createError(404)
|
|
const {req,res,next} = mockHttpContext({ headersSent:true, writableEnded:false });
|
|
|
|
await errorRouter(err, req, res, next);
|
|
|
|
expect(res.locals.stopPrometheusTimer).toHaveBeenCalled();
|
|
});
|
|
|
|
test("should return string message 'internal server error' in case of 500 error", async () => {
|
|
const err = createError(500)
|
|
const {req,res,next} = mockHttpContext();
|
|
|
|
await errorRouter(err, req, res, next);
|
|
|
|
expect(res.status).toHaveBeenCalledWith(500);
|
|
expect(res.setHeader).toHaveBeenCalledWith('Content-Type', "text/html");
|
|
expect(res.end).toHaveBeenCalledWith("internal server error");
|
|
});
|
|
|
|
test("should return string message 'bad request' and log error in case of 400 error", async () => {
|
|
const errorMessage = "mock error text 1";
|
|
const err = createError(400, errorMessage);
|
|
const {req,res,next} = mockHttpContext();
|
|
|
|
await errorRouter(err, req, res, next);
|
|
|
|
expect(res.status).toHaveBeenCalledWith(400);
|
|
expect(res.setHeader).toHaveBeenCalledWith('Content-Type', "text/html");
|
|
expect(res.end).toHaveBeenCalledWith("bad request");
|
|
|
|
expect(logWarn).toHaveBeenCalledWith(`${err.name}:${errorMessage}`);
|
|
});
|
|
|
|
test("should return string message 'unauthorized' and log error in case of 401 error", async () => {
|
|
const errorMessage = "mock error text 2";
|
|
const err = createError(401, errorMessage)
|
|
const {req,res,next} = mockHttpContext();
|
|
|
|
await errorRouter(err, req, res, next);
|
|
|
|
expect(res.status).toHaveBeenCalledWith(401);
|
|
expect(res.setHeader).toHaveBeenCalledWith('Content-Type', "text/html");
|
|
expect(res.end).toHaveBeenCalledWith("unauthorized");
|
|
|
|
expect(logWarn).toHaveBeenCalledWith(`${err.name}:${errorMessage}`);
|
|
});
|
|
|
|
test("should return string message 'forbidden' and log error in case of 403 error", async () => {
|
|
const errorMessage = "mock error text 3";
|
|
const err = createError(403, errorMessage);
|
|
const {req,res,next} = mockHttpContext();
|
|
|
|
await errorRouter(err, req, res, next);
|
|
|
|
expect(res.status).toHaveBeenCalledWith(403);
|
|
expect(res.setHeader).toHaveBeenCalledWith('Content-Type', "text/html");
|
|
expect(res.end).toHaveBeenCalledWith("forbidden");
|
|
|
|
expect(logWarn).toHaveBeenCalledWith(`${err.name}:${errorMessage}`);
|
|
});
|
|
}); |