- 更新package.json和jest配置 - 更新main.ts启动配置 - 完善用户管理和数据库服务 - 更新安全核心模块 - 优化Zulip核心服务 配置改进: - 统一项目依赖管理 - 优化测试配置 - 完善服务模块化架构
120 lines
3.1 KiB
TypeScript
120 lines
3.1 KiB
TypeScript
/**
|
|
* ThrottleGuard 单元测试
|
|
*
|
|
* @author moyin
|
|
* @version 1.0.0
|
|
* @since 2026-01-07
|
|
*/
|
|
|
|
import { Test, TestingModule } from '@nestjs/testing';
|
|
import { ExecutionContext, HttpException, HttpStatus } from '@nestjs/common';
|
|
import { Reflector } from '@nestjs/core';
|
|
import { ThrottleGuard } from './throttle.guard';
|
|
import { ThrottleConfig } from './throttle.decorator';
|
|
|
|
describe('ThrottleGuard', () => {
|
|
let guard: ThrottleGuard;
|
|
let reflector: jest.Mocked<Reflector>;
|
|
|
|
beforeEach(async () => {
|
|
const mockReflector = {
|
|
get: jest.fn(),
|
|
};
|
|
|
|
const module: TestingModule = await Test.createTestingModule({
|
|
providers: [
|
|
ThrottleGuard,
|
|
{ provide: Reflector, useValue: mockReflector },
|
|
],
|
|
}).compile();
|
|
|
|
guard = module.get<ThrottleGuard>(ThrottleGuard);
|
|
reflector = module.get(Reflector);
|
|
});
|
|
|
|
afterEach(() => {
|
|
jest.clearAllMocks();
|
|
guard.clearAllRecords();
|
|
// 确保清理定时器
|
|
guard.onModuleDestroy();
|
|
});
|
|
|
|
describe('canActivate', () => {
|
|
it('should allow request when no throttle config is found', async () => {
|
|
// Arrange
|
|
reflector.get.mockReturnValue(null);
|
|
const mockContext = createMockContext();
|
|
|
|
// Act
|
|
const result = await guard.canActivate(mockContext);
|
|
|
|
// Assert
|
|
expect(result).toBe(true);
|
|
});
|
|
|
|
it('should allow first request within limit', async () => {
|
|
// Arrange
|
|
const config: ThrottleConfig = { limit: 5, ttl: 60 };
|
|
reflector.get.mockReturnValueOnce(config).mockReturnValueOnce(null);
|
|
const mockContext = createMockContext();
|
|
|
|
// Act
|
|
const result = await guard.canActivate(mockContext);
|
|
|
|
// Assert
|
|
expect(result).toBe(true);
|
|
});
|
|
|
|
it('should throw HttpException when limit exceeded', async () => {
|
|
// Arrange
|
|
const config: ThrottleConfig = { limit: 1, ttl: 60 };
|
|
reflector.get.mockReturnValue(config);
|
|
const mockContext = createMockContext();
|
|
|
|
// Act - first request should pass
|
|
await guard.canActivate(mockContext);
|
|
|
|
// Assert - second request should throw
|
|
await expect(guard.canActivate(mockContext)).rejects.toThrow(HttpException);
|
|
});
|
|
});
|
|
|
|
describe('getStats', () => {
|
|
it('should return empty stats initially', () => {
|
|
const stats = guard.getStats();
|
|
expect(stats.totalRecords).toBe(0);
|
|
});
|
|
});
|
|
|
|
describe('clearAllRecords', () => {
|
|
it('should clear all records', () => {
|
|
guard.clearAllRecords();
|
|
const stats = guard.getStats();
|
|
expect(stats.totalRecords).toBe(0);
|
|
});
|
|
});
|
|
|
|
describe('onModuleDestroy', () => {
|
|
it('should cleanup resources', () => {
|
|
expect(() => guard.onModuleDestroy()).not.toThrow();
|
|
});
|
|
});
|
|
|
|
function createMockContext(): ExecutionContext {
|
|
const mockRequest = {
|
|
ip: '127.0.0.1',
|
|
method: 'POST',
|
|
url: '/api/test',
|
|
route: { path: '/api/test' },
|
|
get: jest.fn(),
|
|
};
|
|
|
|
return {
|
|
switchToHttp: jest.fn().mockReturnValue({
|
|
getRequest: jest.fn().mockReturnValue(mockRequest),
|
|
}),
|
|
getHandler: jest.fn(),
|
|
getClass: jest.fn(),
|
|
} as any;
|
|
}
|
|
}); |