Files
XCDesktop/api/middlewares/__tests__/errorHandler.test.ts

200 lines
5.6 KiB
TypeScript
Raw Normal View History

2026-03-08 01:34:54 +08:00
import { describe, it, expect, vi, beforeEach } from 'vitest'
import type { Request, Response, NextFunction } from 'express'
const { mockIsAppError, mockIsNodeError, mockLoggerError, MockAppError, MockValidationError } = vi.hoisted(() => {
const mockFn = () => ({})
return {
mockIsAppError: vi.fn(),
mockIsNodeError: vi.fn(),
mockLoggerError: vi.fn(),
MockAppError: class MockAppError extends Error {
statusCode: number
details?: Record<string, unknown>
code: string
constructor(
code: string,
message: string,
statusCode: number = 500,
details?: Record<string, unknown>
) {
super(message)
this.name = 'MockAppError'
this.code = code
this.statusCode = statusCode
this.details = details
}
},
MockValidationError: class MockValidationError extends Error {
statusCode: number
details?: Record<string, unknown>
code: string
constructor(message: string, details?: Record<string, unknown>) {
super(message)
this.name = 'MockValidationError'
this.code = 'VALIDATION_ERROR'
this.statusCode = 400
this.details = details
}
},
}
})
vi.mock('@shared/errors', () => ({
isAppError: mockIsAppError,
isNodeError: mockIsNodeError,
AppError: MockAppError,
ValidationError: MockValidationError,
}))
vi.mock('@/api/utils/logger', () => ({
logger: {
error: mockLoggerError,
},
}))
import { errorHandler } from '../errorHandler'
describe('errorHandler', () => {
let mockReq: Request
let mockRes: Response
let mockNext: NextFunction
beforeEach(() => {
vi.clearAllMocks()
mockReq = {} as Request
mockRes = {
status: vi.fn().mockReturnThis(),
json: vi.fn().mockReturnThis(),
} as unknown as Response
mockNext = vi.fn()
mockIsAppError.mockReturnValue(false)
mockIsNodeError.mockReturnValue(false)
})
describe('AppError 处理', () => {
it('AppError 应发送自定义状态码和错误码', () => {
const appError = new MockAppError('VALIDATION_ERROR', '验证失败', 400, { field: 'name' })
mockIsAppError.mockReturnValue(true)
errorHandler(appError, mockReq, mockRes, mockNext)
expect(mockRes.status).toHaveBeenCalledWith(400)
expect(mockRes.json).toHaveBeenCalledWith({
success: false,
error: {
code: 'VALIDATION_ERROR',
message: '验证失败',
details: { field: 'name' },
},
})
})
it('ValidationError 应发送正确的错误信息', () => {
const validationError = new MockValidationError('字段不能为空', { field: 'email' })
mockIsAppError.mockReturnValue(true)
errorHandler(validationError, mockReq, mockRes, mockNext)
expect(mockRes.status).toHaveBeenCalledWith(400)
expect(mockRes.json).toHaveBeenCalledWith({
success: false,
error: {
code: 'VALIDATION_ERROR',
message: '字段不能为空',
details: { field: 'email' },
},
})
})
it('AppError 在生产环境不应包含 details', () => {
const originalEnv = process.env.NODE_ENV
process.env.NODE_ENV = 'production'
try {
const appError = new MockAppError('VALIDATION_ERROR', '验证失败', 400, { field: 'name' })
mockIsAppError.mockReturnValue(true)
errorHandler(appError, mockReq, mockRes, mockNext)
expect(mockRes.json).toHaveBeenCalledWith({
success: false,
error: {
code: 'VALIDATION_ERROR',
message: '验证失败',
details: undefined,
},
})
} finally {
process.env.NODE_ENV = originalEnv
}
})
})
describe('Node.js 系统错误处理', () => {
it('Node.js 系统错误应包含 stack 信息(非生产环境)', () => {
const nodeError = new Error('系统错误') as NodeJS.ErrnoException
nodeError.code = 'ENOENT'
nodeError.stack = 'Error: 系统错误\n at Test.<anonymous>'
mockIsAppError.mockReturnValue(false)
mockIsNodeError.mockReturnValue(true)
errorHandler(nodeError, mockReq, mockRes, mockNext)
expect(mockRes.status).toHaveBeenCalledWith(500)
expect(mockRes.json).toHaveBeenCalledWith({
success: false,
error: {
code: 'INTERNAL_ERROR',
message: '系统错误',
details: {
stack: nodeError.stack,
nodeErrorCode: 'ENOENT',
},
},
})
})
it('普通 Error 应包含 stack 信息(非生产环境)', () => {
const error = new Error('普通错误')
error.stack = 'Error: 普通错误\n at Test.<anonymous>'
errorHandler(error, mockReq, mockRes, mockNext)
expect(mockRes.json).toHaveBeenCalledWith({
success: false,
error: {
code: 'INTERNAL_ERROR',
message: '普通错误',
details: {
stack: error.stack,
},
},
})
})
it('在生产环境不包含敏感信息', () => {
const originalEnv = process.env.NODE_ENV
process.env.NODE_ENV = 'production'
try {
const error = new Error('错误信息')
error.stack = 'Error: 错误信息\n at Test.<anonymous>'
errorHandler(error, mockReq, mockRes, mockNext)
expect(mockRes.json).toHaveBeenCalledWith({
success: false,
error: {
code: 'INTERNAL_ERROR',
message: '错误信息',
details: undefined,
},
})
} finally {
process.env.NODE_ENV = originalEnv
}
})
})
})