refactor:项目架构重构和命名规范化

- 统一文件命名为snake_case格式(kebab-case  snake_case)
- 重构zulip模块为zulip_core,明确Core层职责
- 重构user-mgmt模块为user_mgmt,统一命名规范
- 调整模块依赖关系,优化架构分层
- 删除过时的文件和目录结构
- 更新相关文档和配置文件

本次重构涉及大量文件重命名和模块重组,
旨在建立更清晰的项目架构和统一的命名规范。
This commit is contained in:
moyin
2026-01-08 00:14:14 +08:00
parent 4fa4bd1a70
commit bb796a2469
178 changed files with 24767 additions and 3484 deletions

View File

@@ -0,0 +1,143 @@
# SecurityCore 核心安全模块
SecurityCore 是应用的核心安全防护模块,提供系统级的安全防护功能,包括频率限制、超时控制、内容类型验证和维护模式管理,具备完整的监控日志和配置化设计能力。
## 频率限制功能
### Throttle()
频率限制装饰器支持基于IP和用户的多层次限制策略防止API滥用和暴力攻击。
### canActivate()
守卫检查方法,实现频率限制的核心逻辑,支持时间窗口和计数管理。
### getStats()
获取频率限制的实时统计信息,用于监控和调试。
### clearAllRecords()
清除所有频率限制记录,用于管理和重置。
### clearRecord()
清除指定键的频率限制记录,用于精确管理。
## 超时控制功能
### Timeout()
超时装饰器为API接口添加超时控制防止长时间运行的请求阻塞系统。
### intercept()
拦截器处理方法,实现超时控制逻辑和异常处理。
## 内容类型验证功能
### use()
中间件处理方法验证POST/PUT请求的Content-Type头确保API接收正确的数据格式。
### getSupportedTypes()
获取当前支持的Content-Type列表。
### addSupportedType()
动态添加支持的Content-Type类型。
### addExcludePath()
添加不需要验证Content-Type的路径规则。
## 维护模式管理功能
### use()
中间件处理方法,检查系统维护模式状态,在维护期间阻止用户访问。
### isMaintenanceEnabled()
检查维护模式是否启用。
### getMaintenanceInfo()
获取完整的维护配置信息,包括开始时间、结束时间和原因。
## 使用的项目内部依赖
### ThrottleConfig (本模块)
频率限制配置接口,定义限制次数、时间窗口、限制类型和错误消息。
### TimeoutConfig (本模块)
超时配置接口,定义超时时间、错误消息和日志记录选项。
### ThrottlePresets (本模块)
预定义的频率限制配置常量,包含登录、注册、验证码等常用场景的限制模板。
### TimeoutPresets (本模块)
预定义的超时配置常量,包含快速操作、文件处理、数据库查询等场景的超时模板。
### THROTTLE_KEY (本模块)
频率限制元数据键常量,用于装饰器元数据存储。
### TIMEOUT_KEY (本模块)
超时元数据键常量,用于装饰器元数据存储。
### @nestjs/common (来自 NestJS框架)
提供装饰器、异常处理、日志记录等核心功能支持。
### @nestjs/core (来自 NestJS框架)
提供反射器、全局守卫和拦截器注册功能。
### @nestjs/config (来自 NestJS框架)
提供配置服务,用于读取环境变量和应用配置。
### @nestjs/swagger (来自 NestJS框架)
提供API文档生成和响应模式定义功能。
### express (来自 Express框架)
提供HTTP请求响应对象的类型定义。
### rxjs (来自 RxJS库)
提供响应式编程操作符,用于超时控制和异常处理。
## 核心特性
### 多层次安全防护
- 频率限制支持基于IP和用户的双重限制策略防止API滥用和暴力攻击
- 超时控制:防止长时间运行请求占用系统资源,提升系统稳定性
- 内容验证确保API接收符合规范的数据格式防止格式错误
- 维护模式:提供系统维护期间的访问控制,支持优雅的服务中断
### 配置化设计
- 装饰器配置:支持方法级和类级的灵活配置方式,使用简单直观
- 预设模板:提供常用安全场景的预定义配置,开箱即用
- 环境变量:支持通过环境变量进行动态配置,适应不同部署环境
- 运行时调整:支持动态添加规则和排除路径,无需重启服务
### 监控和日志
- 详细日志:记录所有安全事件、异常情况和性能指标,便于问题排查
- 统计信息:提供频率限制的实时统计和历史数据,支持监控分析
- 错误追踪:完整的错误信息记录和上下文保存,提升调试效率
- 性能监控:记录请求处理时间和资源使用情况,优化系统性能
### 高可用设计
- 内存管理:自动清理过期记录,防止内存泄漏和资源浪费
- 异常处理:完善的异常捕获和恢复机制,保证系统稳定运行
- 资源清理:组件销毁时自动清理定时器和资源,避免资源泄漏
- 降级策略:配置缺失时的默认行为和安全降级,保证基本功能
## 潜在风险
### 内存使用风险
- 频率限制记录存储在内存中,高并发场景可能占用大量内存资源
- 大量并发请求时清理任务可能影响系统性能和响应时间
- 应用重启后所有限制记录会丢失,可能导致限制策略失效
- 建议监控内存使用情况考虑使用Redis等外部存储方案
### 配置管理风险
- 错误的频率限制配置可能导致正常用户被误限,影响用户体验
- 维护模式配置错误可能导致服务长时间不可用,影响业务连续性
- 超时配置过短可能导致正常请求被误杀,过长则失去保护作用
- 建议提供配置验证机制和紧急恢复方案,定期检查配置合理性
### 单点故障风险
- 内存存储的限制记录在应用重启后会丢失,无法保持状态连续性
- 依赖单一应用实例的状态管理,不适合分布式部署和负载均衡
- 配置服务异常可能导致安全功能失效,存在安全隐患
- 建议在生产环境使用持久化存储和分布式状态管理方案
### 性能瓶颈风险
- 高频率的限制检查可能成为请求处理的性能瓶颈,影响系统吞吐量
- 复杂的正则表达式匹配可能影响中间件处理速度,增加延迟
- 频繁的日志记录在高并发场景下可能影响系统性能
- 建议进行性能测试和优化,使用缓存减少重复计算,合理设置日志级别

View File

@@ -0,0 +1,122 @@
/**
* ContentTypeMiddleware 单元测试
*
* @author moyin
* @version 1.0.0
* @since 2026-01-07
*/
import { Test, TestingModule } from '@nestjs/testing';
import { Request, Response, NextFunction } from 'express';
import { ContentTypeMiddleware } from './content_type.middleware';
describe('ContentTypeMiddleware', () => {
let middleware: ContentTypeMiddleware;
let mockRequest: Partial<Request>;
let mockResponse: Partial<Response>;
let mockNext: NextFunction;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [ContentTypeMiddleware],
}).compile();
middleware = module.get<ContentTypeMiddleware>(ContentTypeMiddleware);
mockResponse = {
status: jest.fn().mockReturnThis(),
json: jest.fn().mockReturnThis(),
};
mockNext = jest.fn();
});
afterEach(() => {
jest.clearAllMocks();
});
describe('use', () => {
it('should call next() for GET requests', () => {
// Arrange
mockRequest = {
method: 'GET',
url: '/api/test',
};
// Act
middleware.use(mockRequest as Request, mockResponse as Response, mockNext);
// Assert
expect(mockNext).toHaveBeenCalled();
expect(mockResponse.status).not.toHaveBeenCalled();
});
it('should call next() for excluded paths', () => {
// Arrange
mockRequest = {
method: 'POST',
url: '/api-docs/swagger',
get: jest.fn(),
};
// Act
middleware.use(mockRequest as Request, mockResponse as Response, mockNext);
// Assert
expect(mockNext).toHaveBeenCalled();
expect(mockResponse.status).not.toHaveBeenCalled();
});
it('should return 415 when Content-Type is missing', () => {
// Arrange
mockRequest = {
method: 'POST',
url: '/api/test',
get: jest.fn().mockReturnValue(undefined),
ip: '127.0.0.1',
};
// Act
middleware.use(mockRequest as Request, mockResponse as Response, mockNext);
// Assert
expect(mockResponse.status).toHaveBeenCalledWith(415);
expect(mockNext).not.toHaveBeenCalled();
});
it('should call next() for supported Content-Type', () => {
// Arrange
mockRequest = {
method: 'POST',
url: '/api/test',
get: jest.fn().mockImplementation((header) => {
if (header === 'Content-Type') return 'application/json';
return undefined;
}),
};
// Act
middleware.use(mockRequest as Request, mockResponse as Response, mockNext);
// Assert
expect(mockNext).toHaveBeenCalled();
expect(mockResponse.status).not.toHaveBeenCalled();
});
});
describe('getSupportedTypes', () => {
it('should return supported types array', () => {
const types = middleware.getSupportedTypes();
expect(Array.isArray(types)).toBe(true);
expect(types.length).toBeGreaterThan(0);
});
});
describe('addSupportedType', () => {
it('should add new supported type', () => {
middleware.addSupportedType('application/xml');
const types = middleware.getSupportedTypes();
expect(types).toContain('application/xml');
});
});
});

View File

@@ -6,14 +6,29 @@
* - API接口接收正确的数据格式
* -
*
*
* - Content-Type验证逻辑的实现
* -
* -
*
*
* - use() -
* - shouldCheckContentType() -
* - isSupportedContentType() -
* - normalizeContentType() -
*
* 使
* - API接口数据格式验证
* -
* - API接口的健壮性
*
* @author kiro-ai
* @version 1.0.0
*
* - 2026-01-07: 代码规范优化 -
*
* @author moyin
* @version 1.0.1
* @since 2025-12-24
* @lastModified 2026-01-07
*/
import { Injectable, NestMiddleware } from '@nestjs/common';

View File

@@ -1,27 +0,0 @@
/**
* 核心安全模块导出
*
* 功能概述:
* - 频率限制和防护机制
* - 请求超时控制
* - 维护模式管理
* - 内容类型验证
* - 系统安全中间件
*/
// 模块
export * from './security_core.module';
// 守卫
export * from './guards/throttle.guard';
// 中间件
export * from './middleware/maintenance.middleware';
export * from './middleware/content_type.middleware';
// 拦截器
export * from './interceptors/timeout.interceptor';
// 装饰器
export * from './decorators/throttle.decorator';
export * from './decorators/timeout.decorator';

View File

@@ -0,0 +1,132 @@
/**
* MaintenanceMiddleware 单元测试
*
* @author moyin
* @version 1.0.0
* @since 2026-01-07
*/
import { Test, TestingModule } from '@nestjs/testing';
import { ConfigService } from '@nestjs/config';
import { Request, Response, NextFunction } from 'express';
import { MaintenanceMiddleware } from './maintenance.middleware';
describe('MaintenanceMiddleware', () => {
let middleware: MaintenanceMiddleware;
let configService: jest.Mocked<ConfigService>;
let mockRequest: Partial<Request>;
let mockResponse: Partial<Response>;
let mockNext: NextFunction;
beforeEach(async () => {
const mockConfigService = {
get: jest.fn(),
};
const module: TestingModule = await Test.createTestingModule({
providers: [
MaintenanceMiddleware,
{ provide: ConfigService, useValue: mockConfigService },
],
}).compile();
middleware = module.get<MaintenanceMiddleware>(MaintenanceMiddleware);
configService = module.get(ConfigService);
mockRequest = {
method: 'GET',
url: '/api/test',
get: jest.fn(),
ip: '127.0.0.1',
};
mockResponse = {
status: jest.fn().mockReturnThis(),
json: jest.fn().mockReturnThis(),
setHeader: jest.fn().mockReturnThis(),
};
mockNext = jest.fn();
});
afterEach(() => {
jest.clearAllMocks();
});
describe('use', () => {
it('should call next() when maintenance mode is disabled', () => {
// Arrange
configService.get.mockReturnValue('false');
// Act
middleware.use(mockRequest as Request, mockResponse as Response, mockNext);
// Assert
expect(mockNext).toHaveBeenCalled();
expect(mockResponse.status).not.toHaveBeenCalled();
});
it('should return 503 when maintenance mode is enabled', () => {
// Arrange
configService.get.mockImplementation((key) => {
switch (key) {
case 'MAINTENANCE_MODE': return 'true';
case 'MAINTENANCE_RETRY_AFTER': return 3600;
default: return undefined;
}
});
// Act
middleware.use(mockRequest as Request, mockResponse as Response, mockNext);
// Assert
expect(mockResponse.status).toHaveBeenCalledWith(503);
expect(mockNext).not.toHaveBeenCalled();
});
});
describe('isMaintenanceEnabled', () => {
it('should return true when maintenance mode is enabled', () => {
// Arrange
configService.get.mockReturnValue('true');
// Act
const result = middleware.isMaintenanceEnabled();
// Assert
expect(result).toBe(true);
});
it('should return false when maintenance mode is disabled', () => {
// Arrange
configService.get.mockReturnValue('false');
// Act
const result = middleware.isMaintenanceEnabled();
// Assert
expect(result).toBe(false);
});
});
describe('getMaintenanceInfo', () => {
it('should return maintenance info', () => {
// Arrange
configService.get.mockImplementation((key) => {
switch (key) {
case 'MAINTENANCE_MODE': return 'true';
case 'MAINTENANCE_START_TIME': return '2026-01-07T10:00:00.000Z';
default: return undefined;
}
});
// Act
const info = middleware.getMaintenanceInfo();
// Assert
expect(info).toBeDefined();
expect(info.enabled).toBe(true);
expect(info.startTime).toBe('2026-01-07T10:00:00.000Z');
});
});
});

View File

@@ -6,15 +6,29 @@
* - 访API
* -
*
*
* -
* -
* -
*
*
* - use() -
* - isMaintenanceEnabled() -
* - getMaintenanceInfo() -
*
* 使
* -
* -
* -
* -
*
* @author kiro-ai
* @version 1.0.0
*
* - 2026-01-07: 代码规范优化 -
*
* @author moyin
* @version 1.0.1
* @since 2025-12-24
* @lastModified 2026-01-07
*/
import { Injectable, NestMiddleware } from '@nestjs/common';

View File

@@ -0,0 +1,62 @@
/**
* SecurityCoreModule 单元测试
*
* 测试覆盖:
* - 模块配置验证
* - 提供者注册检查
* - 导出验证
*
* @author moyin
* @version 1.0.0
* @since 2026-01-07
*/
import { Test, TestingModule } from '@nestjs/testing';
import { APP_GUARD, APP_INTERCEPTOR } from '@nestjs/core';
import { SecurityCoreModule } from './security_core.module';
import { ThrottleGuard } from './throttle.guard';
import { TimeoutInterceptor } from './timeout.interceptor';
describe('SecurityCoreModule', () => {
let module: TestingModule;
beforeEach(async () => {
module = await Test.createTestingModule({
imports: [SecurityCoreModule],
}).compile();
});
afterEach(async () => {
await module.close();
});
describe('Module Configuration', () => {
it('should be defined', () => {
expect(module).toBeDefined();
});
it('should provide ThrottleGuard', () => {
const guard = module.get<ThrottleGuard>(ThrottleGuard);
expect(guard).toBeDefined();
expect(guard).toBeInstanceOf(ThrottleGuard);
});
it('should provide TimeoutInterceptor', () => {
const interceptor = module.get<TimeoutInterceptor>(TimeoutInterceptor);
expect(interceptor).toBeDefined();
expect(interceptor).toBeInstanceOf(TimeoutInterceptor);
});
it('should provide global providers', () => {
// 验证模块能够正常编译和初始化
expect(module).toBeDefined();
// 验证核心组件可以被获取
const guard = module.get<ThrottleGuard>(ThrottleGuard);
const interceptor = module.get<TimeoutInterceptor>(TimeoutInterceptor);
expect(guard).toBeDefined();
expect(interceptor).toBeDefined();
});
});
});

View File

@@ -7,15 +7,24 @@
* - 维护模式和内容类型验证
* - 全局安全中间件和守卫
*
* @author kiro-ai
* @version 1.0.0
* 职责分离:
* - 安全组件注册和配置管理
* - 全局守卫和拦截器的依赖注入
* - 安全功能的统一导出和模块化
*
* 最近修改:
* - 2026-01-07: 代码规范优化 - 更新注释规范,完善文档说明
*
* @author moyin
* @version 1.0.1
* @since 2025-12-24
* @lastModified 2026-01-07
*/
import { Module } from '@nestjs/common';
import { APP_GUARD, APP_INTERCEPTOR } from '@nestjs/core';
import { ThrottleGuard } from './guards/throttle.guard';
import { TimeoutInterceptor } from './interceptors/timeout.interceptor';
import { ThrottleGuard } from './throttle.guard';
import { TimeoutInterceptor } from './timeout.interceptor';
@Module({
providers: [

View File

@@ -6,19 +6,28 @@
* -
* - IP和用户的限制策略
*
*
* -
* -
* -
*
* 使
* -
* -
* -
* -
*
* @author kiro-ai
* @version 1.0.0
*
* - 2026-01-07: 代码规范优化 -
*
* @author moyin
* @version 1.0.1
* @since 2025-12-24
* @lastModified 2026-01-07
*/
import { SetMetadata, applyDecorators, UseGuards } from '@nestjs/common';
import { ThrottleGuard } from '../guards/throttle.guard';
import { ThrottleGuard } from './throttle.guard';
/**
*
@@ -42,8 +51,15 @@ export interface ThrottleConfig {
/**
*
*
*
* 1.
* 2.
* 3. ThrottleGuard守卫进行实际限制检查
* 4.
*
* @param config
* @returns
* @throws HttpException
*
* @example
* ```typescript

View File

@@ -0,0 +1,118 @@
/**
* 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();
});
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;
}
});

View File

@@ -6,14 +6,29 @@
* - IP地址进行限制
* -
*
*
* -
* -
* -
*
*
* - canActivate() -
* - checkThrottle() -
* - generateKey() -
* - cleanupExpiredRecords() -
*
* 使
* - API滥用
* -
* -
*
* @author kiro-ai
* @version 1.0.0
*
* - 2026-01-07: 代码规范优化 -
*
* @author moyin
* @version 1.0.1
* @since 2025-12-24
* @lastModified 2026-01-07
*/
import {
@@ -22,11 +37,12 @@ import {
ExecutionContext,
HttpException,
HttpStatus,
Logger
Logger,
OnModuleDestroy
} from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { Request } from 'express';
import { THROTTLE_KEY, ThrottleConfig } from '../decorators/throttle.decorator';
import { THROTTLE_KEY, ThrottleConfig } from './throttle.decorator';
/**
*
@@ -64,7 +80,7 @@ interface ThrottleResponse {
}
@Injectable()
export class ThrottleGuard implements CanActivate {
export class ThrottleGuard implements CanActivate, OnModuleDestroy {
private readonly logger = new Logger(ThrottleGuard.name);
/**
@@ -77,18 +93,48 @@ export class ThrottleGuard implements CanActivate {
/**
*
*/
private readonly cleanupInterval = 60000; // 1分钟
private readonly CLEANUP_INTERVAL = 60000; // 1分钟
/**
* ID
*/
private cleanupTimer?: NodeJS.Timeout;
constructor(private readonly reflector: Reflector) {
// 启动定期清理任务
this.startCleanupTask();
}
/**
*
*/
onModuleDestroy() {
if (this.cleanupTimer) {
clearInterval(this.cleanupTimer);
this.cleanupTimer = undefined;
}
}
/**
*
*
*
* 1.
* 2. IP
* 3.
* 4.
* 5.
* 6.
*
* @param context
* @returns
* @throws HttpException 429
*
* @example
* ```typescript
* // 守卫会自动应用到使用@Throttle装饰器的方法上
* // 无需手动调用此方法
* ```
*/
async canActivate(context: ExecutionContext): Promise<boolean> {
// 1. 获取频率限制配置
@@ -263,9 +309,9 @@ export class ThrottleGuard implements CanActivate {
*
*/
private startCleanupTask(): void {
setInterval(() => {
this.cleanupTimer = setInterval(() => {
this.cleanupExpiredRecords();
}, this.cleanupInterval);
}, this.CLEANUP_INTERVAL);
}
/**
@@ -273,10 +319,10 @@ export class ThrottleGuard implements CanActivate {
*/
private cleanupExpiredRecords(): void {
const now = Date.now();
const maxAge = 3600000; // 1小时
const MAX_AGE = 3600000; // 1小时
for (const [key, record] of this.records.entries()) {
if (now - record.lastRequest > maxAge) {
if (now - record.lastRequest > MAX_AGE) {
this.records.delete(key);
}
}

View File

@@ -6,15 +6,24 @@
* -
* -
*
*
* -
* -
* - Swagger文档生成
*
* 使
* -
* - API调用超时
* -
* -
*
* @author kiro-ai
* @version 1.0.0
*
* - 2026-01-07: 代码规范优化 -
*
* @author moyin
* @version 1.0.1
* @since 2025-12-24
* @lastModified 2026-01-07
*/
import { SetMetadata, applyDecorators } from '@nestjs/common';
@@ -40,8 +49,15 @@ export interface TimeoutConfig {
/**
*
*
*
* 1.
* 2.
* 3.
* 4. Swagger API响应文档
*
* @param config
* @returns
* @throws RequestTimeoutException
*
* @example
* ```typescript

View File

@@ -0,0 +1,101 @@
/**
* TimeoutInterceptor 单元测试
*
* @author moyin
* @version 1.0.0
* @since 2026-01-07
*/
import { Test, TestingModule } from '@nestjs/testing';
import { ExecutionContext, CallHandler } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { of } from 'rxjs';
import { TimeoutInterceptor } from './timeout.interceptor';
import { TimeoutConfig } from './timeout.decorator';
describe('TimeoutInterceptor', () => {
let interceptor: TimeoutInterceptor;
let reflector: jest.Mocked<Reflector>;
beforeEach(async () => {
const mockReflector = {
get: jest.fn(),
};
const module: TestingModule = await Test.createTestingModule({
providers: [
TimeoutInterceptor,
{ provide: Reflector, useValue: mockReflector },
],
}).compile();
interceptor = module.get<TimeoutInterceptor>(TimeoutInterceptor);
reflector = module.get(Reflector);
});
afterEach(() => {
jest.clearAllMocks();
});
describe('intercept', () => {
it('should pass through when no timeout config is found', (done) => {
// Arrange
reflector.get.mockReturnValue(null);
const testData = { result: 'success' };
const mockCallHandler: CallHandler = {
handle: jest.fn().mockReturnValue(of(testData)),
};
const mockContext = createMockContext();
// Act
const result$ = interceptor.intercept(mockContext, mockCallHandler);
// Assert
result$.subscribe({
next: (data) => {
expect(data).toEqual(testData);
done();
},
});
});
it('should apply timeout when config is found', (done) => {
// Arrange
const config: TimeoutConfig = { timeout: 1000 };
reflector.get.mockReturnValueOnce(config).mockReturnValueOnce(null);
const testData = { result: 'success' };
const mockCallHandler: CallHandler = {
handle: jest.fn().mockReturnValue(of(testData)),
};
const mockContext = createMockContext();
// Act
const result$ = interceptor.intercept(mockContext, mockCallHandler);
// Assert
result$.subscribe({
next: (data) => {
expect(data).toEqual(testData);
done();
},
});
});
});
function createMockContext(): ExecutionContext {
const mockRequest = {
method: 'GET',
url: '/api/test',
get: jest.fn(),
ip: '127.0.0.1',
};
return {
switchToHttp: jest.fn().mockReturnValue({
getRequest: jest.fn().mockReturnValue(mockRequest),
}),
getHandler: jest.fn(),
getClass: jest.fn(),
} as any;
}
});

View File

@@ -6,14 +6,29 @@
* -
* -
*
*
* -
* -
* -
*
*
* - intercept() -
* - getTimeoutConfig() -
* - getDefaultTimeoutConfig() -
* - isValidTimeoutConfig() -
*
* 使
* -
* -
* -
*
* @author kiro-ai
* @version 1.0.0
*
* - 2026-01-07: 代码规范优化 -
*
* @author moyin
* @version 1.0.1
* @since 2025-12-24
* @lastModified 2026-01-07
*/
import {
@@ -27,7 +42,7 @@ import {
import { Reflector } from '@nestjs/core';
import { Observable, throwError, TimeoutError } from 'rxjs';
import { catchError, timeout } from 'rxjs/operators';
import { TIMEOUT_KEY, TimeoutConfig } from '../decorators/timeout.decorator';
import { TIMEOUT_KEY, TimeoutConfig } from './timeout.decorator';
/**
*