feat(login): 添加验证码登录auth api #18
@@ -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');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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('用户不存在');
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user