/** * 登录业务服务测试 * * 功能描述: * - 测试登录相关的业务逻辑 * - 测试业务层与核心层的集成 * - 测试各种异常情况处理 * * 注意: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; let zulipAccountService: jest.Mocked; let zulipAccountsService: jest.Mocked; let apiKeySecurityService: jest.Mocked; 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); 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'); }); }); });