/** * JwtAuthGuard 单元测试 * * 功能描述: * - 测试JWT认证守卫的令牌验证功能 * - 验证用户信息提取和注入 * - 测试认证失败的异常处理 * * 最近修改: * - 2026-01-14: 架构重构 - 从business层移动到gateway层 (修改者: moyin) * - 2026-01-12: 代码规范优化 - 创建缺失的守卫测试文件 (修改者: moyin) * * @author moyin * @version 1.1.0 * @since 2026-01-12 * @lastModified 2026-01-14 */ import { Test, TestingModule } from '@nestjs/testing'; import { ExecutionContext, UnauthorizedException } from '@nestjs/common'; import { JwtAuthGuard } from './jwt_auth.guard'; import { LoginCoreService } from '../../core/login_core/login_core.service'; describe('JwtAuthGuard', () => { let guard: JwtAuthGuard; let loginCoreService: jest.Mocked; let mockExecutionContext: jest.Mocked; let mockRequest: any; beforeEach(async () => { const mockLoginCoreService = { verifyToken: jest.fn(), }; const module: TestingModule = await Test.createTestingModule({ providers: [ JwtAuthGuard, { provide: LoginCoreService, useValue: mockLoginCoreService, }, ], }).compile(); guard = module.get(JwtAuthGuard); loginCoreService = module.get(LoginCoreService); // Mock request object mockRequest = { headers: {}, user: undefined, }; // Mock execution context mockExecutionContext = { switchToHttp: jest.fn().mockReturnValue({ getRequest: jest.fn().mockReturnValue(mockRequest), }), } as any; }); it('should be defined', () => { expect(guard).toBeDefined(); }); describe('canActivate', () => { it('should allow access with valid JWT token', async () => { const mockPayload = { sub: '1', username: 'testuser', role: 1, type: 'access' as const, iat: Math.floor(Date.now() / 1000), exp: Math.floor(Date.now() / 1000) + 3600, }; mockRequest.headers.authorization = 'Bearer valid_jwt_token'; loginCoreService.verifyToken.mockResolvedValue(mockPayload); const result = await guard.canActivate(mockExecutionContext); expect(result).toBe(true); expect(mockRequest.user).toEqual(mockPayload); expect(loginCoreService.verifyToken).toHaveBeenCalledWith('valid_jwt_token', 'access'); }); it('should deny access when authorization header is missing', async () => { mockRequest.headers.authorization = undefined; await expect(guard.canActivate(mockExecutionContext)) .rejects.toThrow(UnauthorizedException); expect(loginCoreService.verifyToken).not.toHaveBeenCalled(); }); it('should deny access when token format is invalid', async () => { mockRequest.headers.authorization = 'InvalidFormat token'; await expect(guard.canActivate(mockExecutionContext)) .rejects.toThrow(UnauthorizedException); expect(loginCoreService.verifyToken).not.toHaveBeenCalled(); }); it('should deny access when token is not Bearer type', async () => { mockRequest.headers.authorization = 'Basic dXNlcjpwYXNz'; await expect(guard.canActivate(mockExecutionContext)) .rejects.toThrow(UnauthorizedException); expect(loginCoreService.verifyToken).not.toHaveBeenCalled(); }); it('should deny access when JWT token verification fails', async () => { mockRequest.headers.authorization = 'Bearer invalid_jwt_token'; loginCoreService.verifyToken.mockRejectedValue(new Error('Token expired')); await expect(guard.canActivate(mockExecutionContext)) .rejects.toThrow(UnauthorizedException); expect(loginCoreService.verifyToken).toHaveBeenCalledWith('invalid_jwt_token', 'access'); }); it('should extract token correctly from Authorization header', async () => { const mockPayload = { sub: '1', username: 'testuser', role: 1, type: 'access' as const, iat: Math.floor(Date.now() / 1000), exp: Math.floor(Date.now() / 1000) + 3600, }; mockRequest.headers.authorization = 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.test.token'; loginCoreService.verifyToken.mockResolvedValue(mockPayload); const result = await guard.canActivate(mockExecutionContext); expect(result).toBe(true); expect(loginCoreService.verifyToken).toHaveBeenCalledWith( 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.test.token', 'access' ); }); it('should handle empty token after Bearer', async () => { mockRequest.headers.authorization = 'Bearer '; await expect(guard.canActivate(mockExecutionContext)) .rejects.toThrow(UnauthorizedException); expect(loginCoreService.verifyToken).not.toHaveBeenCalled(); }); it('should handle authorization header with only Bearer', async () => { mockRequest.headers.authorization = 'Bearer'; await expect(guard.canActivate(mockExecutionContext)) .rejects.toThrow(UnauthorizedException); expect(loginCoreService.verifyToken).not.toHaveBeenCalled(); }); }); });