diff --git a/src/business/login/login.service.spec.ts b/src/business/login/login.service.spec.ts index e1495e3..0103608 100644 --- a/src/business/login/login.service.spec.ts +++ b/src/business/login/login.service.spec.ts @@ -33,6 +33,9 @@ describe('LoginService', () => { sendPasswordResetCode: jest.fn(), resetPassword: jest.fn(), changePassword: jest.fn(), + verificationCodeLogin: jest.fn(), + sendLoginVerificationCode: jest.fn(), + debugVerificationCode: jest.fn(), }; const module: TestingModule = await Test.createTestingModule({ @@ -190,4 +193,81 @@ describe('LoginService', () => { expect(result.message).toBe('密码修改成功'); }); }); + + describe('verificationCodeLogin', () => { + it('should return success response for valid verification code login', 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).toBeDefined(); + expect(result.data?.is_new_user).toBe(false); + expect(result.message).toBe('验证码登录成功'); + }); + + it('should return error response for failed verification code login', async () => { + loginCoreService.verificationCodeLogin.mockRejectedValue( + new Error('验证码验证失败') + ); + + const result = await service.verificationCodeLogin({ + identifier: 'test@example.com', + verificationCode: '999999' + }); + + expect(result.success).toBe(false); + expect(result.message).toBe('验证码验证失败'); + expect(result.error_code).toBe('VERIFICATION_CODE_LOGIN_FAILED'); + }); + }); + + describe('sendLoginVerificationCode', () => { + it('should return test mode response with verification code', async () => { + loginCoreService.sendLoginVerificationCode.mockResolvedValue({ + code: '123456', + isTestMode: true + }); + + const result = await service.sendLoginVerificationCode('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); + expect(result.message).toContain('测试模式'); + }); + + it('should return success response for real email sending', async () => { + loginCoreService.sendLoginVerificationCode.mockResolvedValue({ + code: '123456', + isTestMode: false + }); + + const result = await service.sendLoginVerificationCode('test@example.com'); + + expect(result.success).toBe(true); + expect(result.data?.is_test_mode).toBe(false); + expect(result.message).toBe('验证码已发送,请查收'); + }); + + it('should return error response for failed sending', async () => { + loginCoreService.sendLoginVerificationCode.mockRejectedValue( + new Error('用户不存在') + ); + + const result = await service.sendLoginVerificationCode('nonexistent@example.com'); + + expect(result.success).toBe(false); + expect(result.message).toBe('用户不存在'); + expect(result.error_code).toBe('SEND_LOGIN_CODE_FAILED'); + }); + }); }); \ No newline at end of file diff --git a/src/core/login_core/login_core.service.spec.ts b/src/core/login_core/login_core.service.spec.ts index 0c95a79..ffa2f52 100644 --- a/src/core/login_core/login_core.service.spec.ts +++ b/src/core/login_core/login_core.service.spec.ts @@ -6,7 +6,7 @@ import { Test, TestingModule } from '@nestjs/testing'; import { LoginCoreService } from './login_core.service'; import { UsersService } from '../db/users/users.service'; import { EmailService } from '../utils/email/email.service'; -import { VerificationService } from '../utils/verification/verification.service'; +import { VerificationService, VerificationCodeType } from '../utils/verification/verification.service'; import { UnauthorizedException, ConflictException, NotFoundException, BadRequestException } from '@nestjs/common'; describe('LoginCoreService', () => { @@ -248,4 +248,132 @@ describe('LoginCoreService', () => { .rejects.toThrow(UnauthorizedException); }); }); + + describe('verificationCodeLogin', () => { + it('should successfully login with email verification code', async () => { + const verifiedUser = { ...mockUser, email_verified: true }; + usersService.findByEmail.mockResolvedValue(verifiedUser); + verificationService.verifyCode.mockResolvedValue(true); + + const result = await service.verificationCodeLogin({ + identifier: 'test@example.com', + verificationCode: '123456' + }); + + expect(result.user).toEqual(verifiedUser); + expect(result.isNewUser).toBe(false); + expect(verificationService.verifyCode).toHaveBeenCalledWith( + 'test@example.com', + VerificationCodeType.EMAIL_VERIFICATION, + '123456' + ); + }); + + it('should successfully login with phone verification code', async () => { + const phoneUser = { ...mockUser, phone: '+8613800138000' }; + usersService.findAll.mockResolvedValue([phoneUser]); + verificationService.verifyCode.mockResolvedValue(true); + + const result = await service.verificationCodeLogin({ + identifier: '+8613800138000', + verificationCode: '123456' + }); + + expect(result.user).toEqual(phoneUser); + expect(result.isNewUser).toBe(false); + expect(verificationService.verifyCode).toHaveBeenCalledWith( + '+8613800138000', + VerificationCodeType.SMS_VERIFICATION, + '123456' + ); + }); + + it('should reject unverified email user', async () => { + usersService.findByEmail.mockResolvedValue(mockUser); // email_verified: false + + await expect(service.verificationCodeLogin({ + identifier: 'test@example.com', + verificationCode: '123456' + })).rejects.toThrow('邮箱未验证,请先验证邮箱后再使用验证码登录'); + }); + + it('should reject non-existent user', async () => { + usersService.findByEmail.mockResolvedValue(null); + + await expect(service.verificationCodeLogin({ + identifier: 'nonexistent@example.com', + verificationCode: '123456' + })).rejects.toThrow('用户不存在,请先注册账户'); + }); + + it('should reject invalid verification code', async () => { + const verifiedUser = { ...mockUser, email_verified: true }; + usersService.findByEmail.mockResolvedValue(verifiedUser); + verificationService.verifyCode.mockResolvedValue(false); + + await expect(service.verificationCodeLogin({ + identifier: 'test@example.com', + verificationCode: '999999' + })).rejects.toThrow('验证码验证失败'); + }); + + it('should reject invalid identifier format', async () => { + await expect(service.verificationCodeLogin({ + identifier: 'invalid-identifier', + verificationCode: '123456' + })).rejects.toThrow('请提供有效的邮箱或手机号'); + }); + }); + + describe('sendLoginVerificationCode', () => { + it('should successfully send email login verification code', async () => { + const verifiedUser = { ...mockUser, email_verified: true }; + usersService.findByEmail.mockResolvedValue(verifiedUser); + verificationService.generateCode.mockResolvedValue('123456'); + emailService.sendVerificationCode.mockResolvedValue({ + success: true, + isTestMode: false + }); + + const result = await service.sendLoginVerificationCode('test@example.com'); + + expect(result.code).toBe('123456'); + expect(result.isTestMode).toBe(false); + expect(emailService.sendVerificationCode).toHaveBeenCalledWith({ + email: 'test@example.com', + code: '123456', + nickname: mockUser.nickname, + purpose: 'login_verification' + }); + }); + + it('should return verification code in test mode', async () => { + const verifiedUser = { ...mockUser, email_verified: true }; + usersService.findByEmail.mockResolvedValue(verifiedUser); + verificationService.generateCode.mockResolvedValue('123456'); + emailService.sendVerificationCode.mockResolvedValue({ + success: true, + isTestMode: true + }); + + const result = await service.sendLoginVerificationCode('test@example.com'); + + expect(result.code).toBe('123456'); + expect(result.isTestMode).toBe(true); + }); + + it('should reject unverified email', async () => { + usersService.findByEmail.mockResolvedValue(mockUser); // email_verified: false + + await expect(service.sendLoginVerificationCode('test@example.com')) + .rejects.toThrow('邮箱未验证,无法使用验证码登录'); + }); + + it('should reject non-existent user', async () => { + usersService.findByEmail.mockResolvedValue(null); + + await expect(service.sendLoginVerificationCode('nonexistent@example.com')) + .rejects.toThrow('用户不存在'); + }); + }); }); \ No newline at end of file