Files
whale-town-end/src/business/login/login.service.spec.ts
angjustinl 26ea5ac815 feat(sql, auth, email, dto):重构邮箱验证流程,引入基于内存的用户服务,并改进 API 响应处理
* 新增完整的 API 状态码文档,并对测试模式进行特殊处理(`206 Partial Content`)
* 重组 DTO 结构,引入 `app.dto.ts` 与 `error_response.dto.ts`,以实现统一、规范的响应格式
* 重构登录相关 DTO,优化命名与结构,提升可维护性
* 实现基于内存的用户服务(`users_memory.service.ts`),用于开发与测试环境
* 更新邮件服务,增强验证码生成逻辑,并支持测试模式自动识别
* 增强登录控制器与服务层的错误处理能力,统一响应行为
* 优化核心登录服务,强化参数校验并集成邮箱验证流程
* 新增 `@types/express` 依赖,提升 TypeScript 类型支持与开发体验
* 改进 `main.ts`,优化应用初始化流程与配置管理
* 在所有服务中统一错误处理机制,采用标准化的错误响应格式
* 实现测试模式(`206`)与生产环境邮件发送(`200`)之间的无缝切换
2025-12-18 00:17:43 +08:00

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('密码修改成功');
});
});
});