From c7723110a0f189d1d66a977089e60ae585777b4d Mon Sep 17 00:00:00 2001 From: armorbreak001 Date: Tue, 14 Apr 2026 07:13:10 +0800 Subject: [PATCH] test: add unit tests for API middlewares (problem + logger) --- .../middlewares/logger.middleware.test.ts | 42 +++++++++++++ .../middlewares/problem.middleware.test.ts | 63 +++++++++++++++++++ 2 files changed, 105 insertions(+) create mode 100644 test/unit/middlewares/logger.middleware.test.ts create mode 100644 test/unit/middlewares/problem.middleware.test.ts diff --git a/test/unit/middlewares/logger.middleware.test.ts b/test/unit/middlewares/logger.middleware.test.ts new file mode 100644 index 000000000..eff9743f9 --- /dev/null +++ b/test/unit/middlewares/logger.middleware.test.ts @@ -0,0 +1,42 @@ +import { expect } from 'chai'; +import { loggerMiddleware } from '../../../src/apps/api/middlewares/logger.middleware'; + +describe('loggerMiddleware', () => { + let mockReq: any; + let mockRes: any; + let mockNext: () => void; + let nextCalled: boolean; + + beforeEach(() => { + nextCalled = false; + mockReq = { method: 'GET', originalUrl: '/api/v1/validate' }; + mockRes = { + on: (event: string, handler: () => void) => {}, + removeListener: () => {}, + emit: (event: string) => {}, + }; + mockNext = () => { nextCalled = true; }; + }); + + it('should call next() immediately', () => { + loggerMiddleware(mockReq, mockRes, mockNext); + expect(nextCalled).to.be.true; + }); + + it('should register finish event handler', () => { + let finishHandler: (() => void) | null = null; + mockRes.on = (event: string, handler: () => void) => { + if (event === 'finish') { finishHandler = handler; } + }; + loggerMiddleware(mockReq, mockRes, mockNext); + expect(nextCalled).to.be.true; + expect(finishHandler).to.be.a('function'); + }); + + it('should handle POST requests', () => { + mockReq.method = 'POST'; + mockReq.originalUrl = '/api/v1/generate'; + loggerMiddleware(mockReq, mockRes, mockNext); + expect(nextCalled).to.be.true; + }); +}); diff --git a/test/unit/middlewares/problem.middleware.test.ts b/test/unit/middlewares/problem.middleware.test.ts new file mode 100644 index 000000000..97a7dec4d --- /dev/null +++ b/test/unit/middlewares/problem.middleware.test.ts @@ -0,0 +1,63 @@ +import { expect } from 'chai'; +import type { Request, Response, NextFunction } from 'express'; +import { problemMiddleware } from '../../../src/apps/api/middlewares/problem.middleware'; +import { ProblemException } from '../../../src/apps/api/exceptions/problem.exception'; + +describe('problemMiddleware', () => { + let mockReq: Partial; + let mockRes: Partial; + let mockNext: NextFunction; + let nextCalledWith: any; + + beforeEach(() => { + nextCalledWith = null; + mockReq = { method: 'POST', path: '/api/v1/validate' }; + mockRes = { headersSent: false }; + mockNext = (err?: any) => { nextCalledWith = err; }; + }); + + it('should call next if headers have already been sent', () => { + (mockRes as any).headersSent = true; + const error = new ProblemException({ type: 'test-error', status: 500, title: 'Test Error' }); + problemMiddleware(error, mockReq as Request, mockRes as Response, mockNext); + expect(nextCalledWith).to.equal(error); + }); + + it('should respond with error status and JSON body for ProblemException', () => { + const error = new ProblemException({ type: 'invalid-request-body', status: 422, title: 'Invalid Request Body', detail: 'The request body is invalid' }); + let statusCode = 0; + let jsonBody: any = null; + (mockRes as any).status = (code: number) => { statusCode = code; return mockRes as Response; }; + (mockRes as any).json = (body: any) => { jsonBody = body; return mockRes as Response; }; + problemMiddleware(error, mockReq as Request, mockRes as Response, mockNext); + expect(statusCode).to.equal(422); + expect(jsonBody).to.be.an('object'); + expect(jsonBody.title).to.equal('Invalid Request Body'); + expect(nextCalledWith).to.be.null; + }); + + it('should default to status 500 when no status is provided', () => { + const error = new ProblemException({ type: 'internal-error', title: 'Internal Error' } as any); + let statusCode = 0; + (mockRes as any).status = (code: number) => { statusCode = code; return mockRes as Response; }; + (mockRes as any).json = () => mockRes as Response; + problemMiddleware(error, mockReq as Request, mockRes as Response, mockNext); + expect(statusCode).to.equal(500); + }); + + it('should default to "Internal server error" when no title is provided', () => { + const error = new ProblemException({ type: 'server-error', status: 500 } as any); + let jsonBody: any = null; + (mockRes as any).status = () => mockRes as Response; + (mockRes as any).json = (body: any) => { jsonBody = body; return mockRes as Response; }; + problemMiddleware(error, mockReq as Request, mockRes as Response, mockNext); + expect(jsonBody.title).to.equal('Internal server error'); + }); + + it('should call next when an unexpected error occurs during serialization', () => { + const error = new ProblemException({ type: 'serialization-error', status: 422, title: 'Test' }); + (error as any).get = () => { throw new Error('unexpected serialization error'); }; + problemMiddleware(error, mockReq as Request, mockRes as Response, mockNext); + expect(nextCalledWith).to.be.an.instanceOf(Error); + }); +});