/** * RegisterService 单元测试 * * 功能描述: * - 测试用户注册相关的业务逻辑 * - 验证邮箱验证功能 * - 测试Zulip账号集成 * * 最近修改: * - 2026-01-15: 代码规范优化 - 清理未使用的变量apiKeySecurityService (修改者: moyin) * - 2026-01-12: 代码分离 - 从login.service.spec.ts中分离注册相关测试 * * @author moyin * @version 1.0.1 * @since 2026-01-12 * @lastModified 2026-01-15 */ import { Test, TestingModule } from '@nestjs/testing'; import { RegisterService } from './register.service'; import { LoginCoreService } from '../../core/login_core/login_core.service'; import { ZulipAccountService } from '../../core/zulip_core/services/zulip_account.service'; import { ApiKeySecurityService } from '../../core/zulip_core/services/api_key_security.service'; import { AccountProfileService } from './account_profile.service'; import { InvitationCodesService } from '../invitation/invitation_codes.service'; describe('RegisterService', () => { let service: RegisterService; let loginCoreService: jest.Mocked; let zulipAccountService: jest.Mocked; let invitationCodesService: jest.Mocked; const mockUser = { id: BigInt(1), username: 'testuser', nickname: 'Test User', email: 'test@example.com', phone: null, avatar_url: null, role: 1, created_at: new Date(), updated_at: new Date(), password_hash: 'hashed_password', github_id: null, is_active: true, last_login_at: null, email_verified: false, phone_verified: false, }; beforeEach(async () => { const mockLoginCoreService = { register: jest.fn(), sendEmailVerification: jest.fn(), verifyEmailCode: jest.fn(), resendEmailVerification: jest.fn(), deleteUser: 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 mockAccountProfileService = { ensureProfile: jest.fn().mockResolvedValue({}), sendWelcomeEmailAfterInitialCharacterCreation: jest.fn().mockResolvedValue({}), formatAccountProfileAsync: jest.fn().mockResolvedValue({ profile: {} }), }; const mockInvitationCodesService = { reserve: jest.fn().mockResolvedValue({ id: BigInt(10) }), release: jest.fn().mockResolvedValue(undefined), recordUsage: jest.fn().mockResolvedValue(undefined), validate: jest.fn().mockResolvedValue(undefined), }; const module: TestingModule = await Test.createTestingModule({ providers: [ RegisterService, { provide: LoginCoreService, useValue: mockLoginCoreService, }, { provide: ZulipAccountService, useValue: mockZulipAccountService, }, { provide: 'ZulipAccountsService', useValue: mockZulipAccountsService, }, { provide: ApiKeySecurityService, useValue: mockApiKeySecurityService, }, { provide: AccountProfileService, useValue: mockAccountProfileService, }, { provide: InvitationCodesService, useValue: mockInvitationCodesService, }, ], }).compile(); service = module.get(RegisterService); loginCoreService = module.get(LoginCoreService); zulipAccountService = module.get(ZulipAccountService); invitationCodesService = module.get(InvitationCodesService); // 设置默认的mock返回值 const mockTokenPair = { access_token: 'mock_access_token', refresh_token: 'mock_refresh_token', expires_in: 3600, token_type: 'Bearer', }; loginCoreService.generateTokenPair.mockResolvedValue(mockTokenPair); zulipAccountService.initializeAdminClient.mockResolvedValue(true); zulipAccountService.createZulipAccount.mockResolvedValue({ success: true, userId: 123, email: 'test@example.com', apiKey: 'mock_api_key', isExistingUser: false }); }); it('should be defined', () => { expect(service).toBeDefined(); }); describe('register', () => { it('should handle user registration successfully', async () => { loginCoreService.register.mockResolvedValue({ user: mockUser, isNewUser: true }); const result = await service.register({ invitation_code: 'WT-TEST-CODE-0001', username: 'testuser', password: 'password123', nickname: 'Test User', email: 'test@example.com' }); expect(result.success).toBe(true); expect(result.data?.user.username).toBe('testuser'); expect(result.data?.is_new_user).toBe(true); expect(loginCoreService.register).toHaveBeenCalled(); expect(invitationCodesService.reserve).toHaveBeenCalledWith('WT-TEST-CODE-0001'); expect(invitationCodesService.recordUsage).toHaveBeenCalledWith(BigInt(10), BigInt(1), 'test@example.com'); }); it('should handle registration failure', async () => { loginCoreService.register.mockRejectedValue(new Error('Registration failed')); const result = await service.register({ invitation_code: 'WT-TEST-CODE-0001', username: 'testuser', password: 'password123', nickname: 'Test User', email: 'test@example.com' }); expect(result.success).toBe(false); expect(result.message).toContain('Registration failed'); expect(invitationCodesService.release).toHaveBeenCalledWith(BigInt(10)); }); it('should keep registration successful when invitation usage logging fails', async () => { loginCoreService.register.mockResolvedValue({ user: mockUser, isNewUser: true }); invitationCodesService.recordUsage.mockRejectedValue(new Error('Usage audit unavailable')); const result = await service.register({ invitation_code: 'WT-TEST-CODE-0001', username: 'testuser', password: 'password123', nickname: 'Test User', email: 'test@example.com' }); expect(result.success).toBe(true); expect(invitationCodesService.release).not.toHaveBeenCalled(); }); }); describe('sendEmailVerification', () => { it('should handle sendEmailVerification in test mode', async () => { loginCoreService.sendEmailVerification.mockResolvedValue({ code: '123456', isTestMode: true }); const result = await service.sendEmailVerification('test@example.com', 'WT-TEST-CODE-0001'); 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.sendEmailVerification).toHaveBeenCalledWith('test@example.com'); }); it('should handle sendEmailVerification in production mode', async () => { loginCoreService.sendEmailVerification.mockResolvedValue({ code: '123456', isTestMode: false }); const result = await service.sendEmailVerification('test@example.com', 'WT-TEST-CODE-0001'); expect(result.success).toBe(true); expect(result.data?.is_test_mode).toBe(false); expect(loginCoreService.sendEmailVerification).toHaveBeenCalledWith('test@example.com'); }); }); describe('verifyEmailCode', () => { it('should handle verifyEmailCode successfully', async () => { loginCoreService.verifyEmailCode.mockResolvedValue(true); const result = await service.verifyEmailCode('test@example.com', '123456'); expect(result.success).toBe(true); expect(result.message).toBe('邮箱验证成功'); expect(loginCoreService.verifyEmailCode).toHaveBeenCalledWith('test@example.com', '123456'); }); it('should handle invalid verification code', async () => { loginCoreService.verifyEmailCode.mockResolvedValue(false); const result = await service.verifyEmailCode('test@example.com', '123456'); expect(result.success).toBe(false); expect(result.message).toBe('验证码错误'); }); }); describe('resendEmailVerification', () => { it('should handle resendEmailVerification successfully', async () => { loginCoreService.resendEmailVerification.mockResolvedValue({ code: '654321', isTestMode: false }); const result = await service.resendEmailVerification('test@example.com'); expect(result.success).toBe(true); expect(result.data?.is_test_mode).toBe(false); expect(loginCoreService.resendEmailVerification).toHaveBeenCalledWith('test@example.com'); }); }); });