Files
whale-town-end/src/business/auth/login.service.spec.ts
moyin 267f1b2263 style(auth):优化auth模块代码规范和测试覆盖
范围:src/business/auth/
- 统一命名规范和注释格式
- 完善文件头部注释和修改记录
- 分离登录和注册业务逻辑到独立服务
- 添加缺失的测试文件(JWT守卫、控制器测试)
- 清理未使用的测试文件
- 优化代码结构和依赖关系
2026-01-12 18:04:33 +08:00

296 lines
9.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 登录业务服务测试
*
* 功能描述:
* - 测试登录相关的业务逻辑
* - 测试业务层与核心层的集成
* - 测试各种异常情况处理
*
* 注意JWT相关功能已移至Core层此测试专注于Business层逻辑
*
* @author moyin
* @version 1.0.1
* @since 2025-01-06
* @lastModified 2026-01-07
*/
import { Test, TestingModule } from '@nestjs/testing';
import { LoginService } from './login.service';
import { LoginCoreService } from '../../core/login_core/login_core.service';
import { ZulipAccountService } from '../../core/zulip_core/services/zulip_account.service';
import { ZulipAccountsService } from '../../core/db/zulip_accounts/zulip_accounts.service';
import { ApiKeySecurityService } from '../../core/zulip_core/services/api_key_security.service';
import { UserStatus } from '../../core/db/users/user_status.enum';
describe('LoginService', () => {
let service: LoginService;
let loginCoreService: jest.Mocked<LoginCoreService>;
let zulipAccountService: jest.Mocked<ZulipAccountService>;
let zulipAccountsService: jest.Mocked<ZulipAccountsService>;
let apiKeySecurityService: jest.Mocked<ApiKeySecurityService>;
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,
status: UserStatus.ACTIVE,
email_verified: true,
created_at: new Date(),
updated_at: new Date(),
};
const mockTokenPair = {
access_token: 'mock_access_token',
refresh_token: 'mock_refresh_token',
expires_in: 604800,
token_type: 'Bearer'
};
beforeEach(async () => {
// Mock environment variables for Zulip
process.env.ZULIP_SERVER_URL = 'https://test.zulipchat.com';
process.env.ZULIP_BOT_EMAIL = 'test-bot@test.zulipchat.com';
process.env.ZULIP_BOT_API_KEY = 'test_api_key_12345';
const mockLoginCoreService = {
login: jest.fn(),
githubOAuth: jest.fn(),
sendPasswordResetCode: jest.fn(),
resetPassword: jest.fn(),
changePassword: jest.fn(),
verificationCodeLogin: jest.fn(),
sendLoginVerificationCode: jest.fn(),
debugVerificationCode: jest.fn(),
refreshAccessToken: jest.fn(),
generateTokenPair: jest.fn(),
};
const mockZulipAccountService = {
initializeAdminClient: jest.fn(),
createZulipAccount: jest.fn(),
linkGameAccount: jest.fn(),
};
const mockZulipAccountsService = {
findByGameUserId: jest.fn(),
create: jest.fn(),
deleteByGameUserId: jest.fn(),
};
const mockApiKeySecurityService = {
storeApiKey: jest.fn(),
};
const module: TestingModule = await Test.createTestingModule({
providers: [
LoginService,
{
provide: LoginCoreService,
useValue: mockLoginCoreService,
},
{
provide: ZulipAccountService,
useValue: mockZulipAccountService,
},
{
provide: 'ZulipAccountsService',
useValue: mockZulipAccountsService,
},
{
provide: ApiKeySecurityService,
useValue: mockApiKeySecurityService,
},
],
}).compile();
service = module.get<LoginService>(LoginService);
loginCoreService = module.get(LoginCoreService);
zulipAccountService = module.get(ZulipAccountService);
zulipAccountsService = module.get('ZulipAccountsService');
apiKeySecurityService = module.get(ApiKeySecurityService);
// Setup default mocks
loginCoreService.generateTokenPair.mockResolvedValue(mockTokenPair);
zulipAccountService.initializeAdminClient.mockResolvedValue(true);
zulipAccountService.createZulipAccount.mockResolvedValue({
success: true,
userId: 123,
email: 'test@example.com',
apiKey: 'mock_api_key'
});
zulipAccountsService.findByGameUserId.mockResolvedValue(null);
zulipAccountsService.create.mockResolvedValue({} as any);
apiKeySecurityService.storeApiKey.mockResolvedValue(undefined);
});
afterEach(() => {
jest.clearAllMocks();
});
it('should be defined', () => {
expect(service).toBeDefined();
});
describe('login', () => {
it('should login successfully and return JWT tokens', 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).toBe(mockTokenPair.access_token);
expect(result.data?.refresh_token).toBe(mockTokenPair.refresh_token);
expect(loginCoreService.login).toHaveBeenCalledWith({
identifier: 'testuser',
password: 'password123'
});
expect(loginCoreService.generateTokenPair).toHaveBeenCalledWith(mockUser);
});
it('should handle login failure', 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('githubOAuth', () => {
it('should handle GitHub OAuth successfully', async () => {
loginCoreService.githubOAuth.mockResolvedValue({
user: mockUser,
isNewUser: false
});
const result = await service.githubOAuth({
github_id: '12345',
username: 'githubuser',
nickname: 'GitHub用户',
email: 'github@example.com'
});
expect(result.success).toBe(true);
expect(result.data?.user.username).toBe('testuser');
expect(result.data?.access_token).toBe(mockTokenPair.access_token);
expect(loginCoreService.githubOAuth).toHaveBeenCalled();
expect(loginCoreService.generateTokenPair).toHaveBeenCalledWith(mockUser);
});
});
describe('sendPasswordResetCode', () => {
it('should handle sendPasswordResetCode in test mode', async () => {
loginCoreService.sendPasswordResetCode.mockResolvedValue({
code: '123456',
isTestMode: true
});
const result = await service.sendPasswordResetCode('test@example.com');
expect(result.success).toBe(false); // Test mode returns false
expect(result.data?.verification_code).toBe('123456');
expect(result.data?.is_test_mode).toBe(true);
expect(loginCoreService.sendPasswordResetCode).toHaveBeenCalledWith('test@example.com');
});
});
describe('resetPassword', () => {
it('should handle resetPassword successfully', async () => {
loginCoreService.resetPassword.mockResolvedValue(undefined);
const result = await service.resetPassword({
identifier: 'test@example.com',
verificationCode: '123456',
newPassword: 'newpassword123'
});
expect(result.success).toBe(true);
expect(result.message).toBe('密码重置成功');
expect(loginCoreService.resetPassword).toHaveBeenCalled();
});
});
describe('changePassword', () => {
it('should handle changePassword successfully', async () => {
loginCoreService.changePassword.mockResolvedValue(undefined);
const result = await service.changePassword(BigInt(1), 'oldpassword', 'newpassword');
expect(result.success).toBe(true);
expect(result.message).toBe('密码修改成功');
expect(loginCoreService.changePassword).toHaveBeenCalledWith(BigInt(1), 'oldpassword', 'newpassword');
});
});
describe('verificationCodeLogin', () => {
it('should handle verificationCodeLogin successfully', async () => {
loginCoreService.verificationCodeLogin.mockResolvedValue({
user: mockUser,
isNewUser: false
});
const result = await service.verificationCodeLogin({
identifier: 'test@example.com',
verificationCode: '123456'
});
expect(result.success).toBe(true);
expect(result.data?.user.username).toBe('testuser');
expect(result.data?.access_token).toBe(mockTokenPair.access_token);
expect(loginCoreService.verificationCodeLogin).toHaveBeenCalled();
expect(loginCoreService.generateTokenPair).toHaveBeenCalledWith(mockUser);
});
});
describe('sendLoginVerificationCode', () => {
it('should handle sendLoginVerificationCode successfully', async () => {
loginCoreService.sendLoginVerificationCode.mockResolvedValue({
code: '123456',
isTestMode: true
});
const result = await service.sendLoginVerificationCode('test@example.com');
expect(result.success).toBe(false); // Test mode returns false
expect(result.data?.verification_code).toBe('123456');
expect(result.data?.is_test_mode).toBe(true);
expect(loginCoreService.sendLoginVerificationCode).toHaveBeenCalledWith('test@example.com');
});
});
describe('debugVerificationCode', () => {
it('should handle debugVerificationCode successfully', async () => {
const mockDebugInfo = {
email: 'test@example.com',
hasCode: true,
codeExpiry: new Date()
};
loginCoreService.debugVerificationCode.mockResolvedValue(mockDebugInfo);
const result = await service.debugVerificationCode('test@example.com');
expect(result.success).toBe(true);
expect(result.data).toEqual(mockDebugInfo);
expect(loginCoreService.debugVerificationCode).toHaveBeenCalledWith('test@example.com');
});
});
});