forked from datawhale/whale-town-end
* 新增完整的 API 状态码文档,并对测试模式进行特殊处理(`206 Partial Content`) * 重组 DTO 结构,引入 `app.dto.ts` 与 `error_response.dto.ts`,以实现统一、规范的响应格式 * 重构登录相关 DTO,优化命名与结构,提升可维护性 * 实现基于内存的用户服务(`users_memory.service.ts`),用于开发与测试环境 * 更新邮件服务,增强验证码生成逻辑,并支持测试模式自动识别 * 增强登录控制器与服务层的错误处理能力,统一响应行为 * 优化核心登录服务,强化参数校验并集成邮箱验证流程 * 新增 `@types/express` 依赖,提升 TypeScript 类型支持与开发体验 * 改进 `main.ts`,优化应用初始化流程与配置管理 * 在所有服务中统一错误处理机制,采用标准化的错误响应格式 * 实现测试模式(`206`)与生产环境邮件发送(`200`)之间的无缝切换
193 lines
5.7 KiB
TypeScript
193 lines
5.7 KiB
TypeScript
/**
|
|
* 登录业务服务测试
|
|
*/
|
|
|
|
import { Test, TestingModule } from '@nestjs/testing';
|
|
import { LoginService } from './login.service';
|
|
import { LoginCoreService } from '../../core/login_core/login_core.service';
|
|
|
|
describe('LoginService', () => {
|
|
let service: LoginService;
|
|
let loginCoreService: jest.Mocked<LoginCoreService>;
|
|
|
|
const mockUser = {
|
|
id: BigInt(1),
|
|
username: 'testuser',
|
|
email: 'test@example.com',
|
|
phone: '+8613800138000',
|
|
password_hash: '$2b$12$hashedpassword',
|
|
nickname: '测试用户',
|
|
github_id: null as string | null,
|
|
avatar_url: null as string | null,
|
|
role: 1,
|
|
email_verified: false,
|
|
created_at: new Date(),
|
|
updated_at: new Date()
|
|
};
|
|
|
|
beforeEach(async () => {
|
|
const mockLoginCoreService = {
|
|
login: jest.fn(),
|
|
register: jest.fn(),
|
|
githubOAuth: jest.fn(),
|
|
sendPasswordResetCode: jest.fn(),
|
|
resetPassword: jest.fn(),
|
|
changePassword: jest.fn(),
|
|
};
|
|
|
|
const module: TestingModule = await Test.createTestingModule({
|
|
providers: [
|
|
LoginService,
|
|
{
|
|
provide: LoginCoreService,
|
|
useValue: mockLoginCoreService,
|
|
},
|
|
],
|
|
}).compile();
|
|
|
|
service = module.get<LoginService>(LoginService);
|
|
loginCoreService = module.get(LoginCoreService);
|
|
});
|
|
|
|
it('should be defined', () => {
|
|
expect(service).toBeDefined();
|
|
});
|
|
|
|
describe('login', () => {
|
|
it('should return success response for valid login', async () => {
|
|
loginCoreService.login.mockResolvedValue({
|
|
user: mockUser,
|
|
isNewUser: false
|
|
});
|
|
|
|
const result = await service.login({
|
|
identifier: 'testuser',
|
|
password: 'password123'
|
|
});
|
|
|
|
expect(result.success).toBe(true);
|
|
expect(result.data?.user.username).toBe('testuser');
|
|
expect(result.data?.access_token).toBeDefined();
|
|
});
|
|
|
|
it('should return error response for failed login', async () => {
|
|
loginCoreService.login.mockRejectedValue(new Error('登录失败'));
|
|
|
|
const result = await service.login({
|
|
identifier: 'testuser',
|
|
password: 'wrongpassword'
|
|
});
|
|
|
|
expect(result.success).toBe(false);
|
|
expect(result.message).toBe('登录失败');
|
|
expect(result.error_code).toBe('LOGIN_FAILED');
|
|
});
|
|
});
|
|
|
|
describe('register', () => {
|
|
it('should return success response for valid registration', async () => {
|
|
loginCoreService.register.mockResolvedValue({
|
|
user: mockUser,
|
|
isNewUser: true
|
|
});
|
|
|
|
const result = await service.register({
|
|
username: 'testuser',
|
|
password: 'password123',
|
|
nickname: '测试用户'
|
|
});
|
|
|
|
expect(result.success).toBe(true);
|
|
expect(result.data?.user.username).toBe('testuser');
|
|
expect(result.data?.is_new_user).toBe(true);
|
|
});
|
|
|
|
it('should return error response for failed registration', async () => {
|
|
loginCoreService.register.mockRejectedValue(new Error('用户名已存在'));
|
|
|
|
const result = await service.register({
|
|
username: 'existinguser',
|
|
password: 'password123',
|
|
nickname: '测试用户'
|
|
});
|
|
|
|
expect(result.success).toBe(false);
|
|
expect(result.message).toBe('用户名已存在');
|
|
expect(result.error_code).toBe('REGISTER_FAILED');
|
|
});
|
|
});
|
|
|
|
describe('githubOAuth', () => {
|
|
it('should return success response for GitHub OAuth', async () => {
|
|
loginCoreService.githubOAuth.mockResolvedValue({
|
|
user: mockUser,
|
|
isNewUser: true
|
|
});
|
|
|
|
const result = await service.githubOAuth({
|
|
github_id: 'github123',
|
|
username: 'githubuser',
|
|
nickname: 'GitHub用户'
|
|
});
|
|
|
|
expect(result.success).toBe(true);
|
|
expect(result.data?.user.username).toBe('testuser');
|
|
expect(result.data?.is_new_user).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe('sendPasswordResetCode', () => {
|
|
it('should return test mode response with verification code', async () => {
|
|
loginCoreService.sendPasswordResetCode.mockResolvedValue({
|
|
code: '123456',
|
|
isTestMode: true
|
|
});
|
|
|
|
const result = await service.sendPasswordResetCode('test@example.com');
|
|
|
|
expect(result.success).toBe(false); // 测试模式下不算成功
|
|
expect(result.error_code).toBe('TEST_MODE_ONLY');
|
|
expect(result.data?.verification_code).toBe('123456');
|
|
expect(result.data?.is_test_mode).toBe(true);
|
|
});
|
|
|
|
it('should return success response for real email sending', async () => {
|
|
loginCoreService.sendPasswordResetCode.mockResolvedValue({
|
|
code: '123456',
|
|
isTestMode: false
|
|
});
|
|
|
|
const result = await service.sendPasswordResetCode('test@example.com');
|
|
|
|
expect(result.success).toBe(true);
|
|
expect(result.data?.is_test_mode).toBe(false);
|
|
expect(result.data?.verification_code).toBeUndefined(); // 真实模式下不返回验证码
|
|
});
|
|
});
|
|
|
|
describe('resetPassword', () => {
|
|
it('should return success response for password reset', async () => {
|
|
loginCoreService.resetPassword.mockResolvedValue(mockUser);
|
|
|
|
const result = await service.resetPassword({
|
|
identifier: 'test@example.com',
|
|
verificationCode: '123456',
|
|
newPassword: 'newpassword123'
|
|
});
|
|
|
|
expect(result.success).toBe(true);
|
|
expect(result.message).toBe('密码重置成功');
|
|
});
|
|
});
|
|
|
|
describe('changePassword', () => {
|
|
it('should return success response for password change', async () => {
|
|
loginCoreService.changePassword.mockResolvedValue(mockUser);
|
|
|
|
const result = await service.changePassword(BigInt(1), 'oldpassword', 'newpassword123');
|
|
|
|
expect(result.success).toBe(true);
|
|
expect(result.message).toBe('密码修改成功');
|
|
});
|
|
});
|
|
}); |