Files
whale-town-end/src/gateway/auth/jwt_auth.guard.spec.ts
moyin 73e3e0153c refactor(auth): 重构认证模块架构 - 将Gateway层组件从Business层分离
范围:src/gateway/auth/, src/business/auth/, src/app.module.ts
涉及文件:
- 新增:src/gateway/auth/ 目录及所有文件
- 移动:Controller、Guard、Decorator、DTO从business层移至gateway层
- 修改:src/business/auth/index.ts(移除Gateway层组件导出)
- 修改:src/app.module.ts(使用AuthGatewayModule替代AuthModule)

主要改进:
- 明确Gateway层和Business层的职责边界
- Controller、Guard、Decorator属于Gateway层职责
- Business层专注于业务逻辑和服务
- 符合分层架构设计原则
2026-01-14 13:07:11 +08:00

165 lines
5.2 KiB
TypeScript

/**
* 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<LoginCoreService>;
let mockExecutionContext: jest.Mocked<ExecutionContext>;
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>(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();
});
});
});