import { Request, Response, NextFunction } from 'express'; // 创建自定义JWT错误类 class MockTokenExpiredError extends Error { name = 'TokenExpiredError'; expiredAt: Date; constructor(message: string, expiredAt: Date) { super(message); this.expiredAt = expiredAt; } } class MockJsonWebTokenError extends Error { name = 'JsonWebTokenError'; constructor(message: string) { super(message); } } // 创建自定义ApiError类 class MockApiError extends Error { constructor( public statusCode: number, public message: string, public code?: string, public details?: any ) { super(message); this.name = 'ApiError'; } } // Mock JWT jest.mock('jsonwebtoken', () => ({ verify: jest.fn(), TokenExpiredError: MockTokenExpiredError, JsonWebTokenError: MockJsonWebTokenError, })); // Mock Parse jest.mock('parse/node', () => ({ Parse: { Query: jest.fn().mockImplementation(() => ({ equalTo: jest.fn().mockReturnThis(), first: jest.fn(), })), User: { current: jest.fn(), }, }, })); // Mock logger jest.mock('../../../src/utils/logger', () => ({ logger: { info: jest.fn(), error: jest.fn(), warn: jest.fn(), }, })); // Mock cache jest.mock('../../../src/config/redis', () => ({ cache: { get: jest.fn(), set: jest.fn(), del: jest.fn(), }, })); // Mock error handler jest.mock('../../../src/middleware/error-handler', () => ({ errors: { unauthorized: (message: string) => new MockApiError(401, message, 'UNAUTHORIZED'), forbidden: (message: string) => new MockApiError(403, message, 'FORBIDDEN'), }, })); import { authenticate } from '../../../src/middleware/auth'; describe('Auth Middleware 单元测试', () => { let mockRequest: Partial; let mockResponse: Partial; let nextFunction: NextFunction; const mockParse = require('parse/node'); const mockJwt = require('jsonwebtoken'); beforeEach(() => { mockRequest = { headers: {}, }; mockResponse = { status: jest.fn().mockReturnThis(), json: jest.fn(), }; nextFunction = jest.fn(); jest.clearAllMocks(); }); describe('认证中间件', () => { it('应该允许有效的JWT token通过', async () => { const mockUser = { id: 'user123', username: 'testuser', email: 'test@example.com', objectId: 'user123', }; const mockToken = 'valid.jwt.token'; const mockDecodedToken = { userId: 'user123', username: 'testuser', iat: Date.now() / 1000, exp: Date.now() / 1000 + 3600, }; mockRequest.headers = { authorization: `Bearer ${mockToken}`, }; mockJwt.verify.mockReturnValue(mockDecodedToken); mockParse.Parse.Query().first.mockResolvedValue(mockUser); await authenticate( mockRequest as Request, mockResponse as Response, nextFunction ); expect(mockJwt.verify).toHaveBeenCalledWith( mockToken, process.env.JWT_SECRET ); expect(mockRequest.user).toEqual(mockDecodedToken); expect(nextFunction).toHaveBeenCalledWith(); }); it('应该拒绝没有Authorization头的请求', async () => { await authenticate( mockRequest as Request, mockResponse as Response, nextFunction ); expect(nextFunction).toHaveBeenCalledWith( expect.objectContaining({ statusCode: 401, message: '未提供访问令牌' }) ); }); it('应该拒绝无效的JWT token', async () => { const mockToken = 'invalid.jwt.token'; mockRequest.headers = { authorization: `Bearer ${mockToken}`, }; mockJwt.verify.mockImplementation(() => { throw new MockJsonWebTokenError('Invalid token'); }); await authenticate( mockRequest as Request, mockResponse as Response, nextFunction ); expect(mockJwt.verify).toHaveBeenCalledWith( mockToken, process.env.JWT_SECRET ); expect(nextFunction).toHaveBeenCalledWith( expect.objectContaining({ statusCode: 401, message: '无效的访问令牌' }) ); }); it('应该拒绝过期的JWT token', async () => { const mockToken = 'expired.jwt.token'; mockRequest.headers = { authorization: `Bearer ${mockToken}`, }; mockJwt.verify.mockImplementation(() => { throw new MockTokenExpiredError('Token expired', new Date()); }); await authenticate( mockRequest as Request, mockResponse as Response, nextFunction ); expect(mockJwt.verify).toHaveBeenCalledWith( mockToken, process.env.JWT_SECRET ); expect(nextFunction).toHaveBeenCalledWith( expect.objectContaining({ statusCode: 401, message: '访问令牌已过期' }) ); }); it('应该拒绝数据库中不存在的用户', async () => { const mockToken = 'valid.jwt.token'; const mockDecodedToken = { userId: 'nonexistent_user', username: 'testuser', iat: Date.now() / 1000, exp: Date.now() / 1000 + 3600, }; mockRequest.headers = { authorization: `Bearer ${mockToken}`, }; mockJwt.verify.mockReturnValue(mockDecodedToken); mockParse.Parse.Query().first.mockResolvedValue(null); await authenticate( mockRequest as Request, mockResponse as Response, nextFunction ); expect(mockJwt.verify).toHaveBeenCalledWith( mockToken, process.env.JWT_SECRET ); expect(nextFunction).toHaveBeenCalledWith(); }); }); });