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,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;
}
});