docs: 完善API文档,添加验证码登录功能说明
- 新增验证码登录接口文档 (POST /auth/verification-code-login) - 新增发送登录验证码接口文档 (POST /auth/send-login-verification-code) - 更新接口列表和数量统计 (21个 -> 23个接口) - 添加验证码登录测试场景和cURL示例 - 完善错误码说明和响应格式 - 确保文档与当前实现完全一致
This commit is contained in:
155
src/business/auth/services/login.service.spec.ts
Normal file
155
src/business/auth/services/login.service.spec.ts
Normal file
@@ -0,0 +1,155 @@
|
||||
/**
|
||||
* 登录业务服务测试
|
||||
*/
|
||||
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { LoginService } from './login.service';
|
||||
import { LoginCoreService } from '../../../core/login_core/login_core.service';
|
||||
|
||||
describe('LoginService', () => {
|
||||
let service: LoginService;
|
||||
let loginCoreService: jest.Mocked<LoginCoreService>;
|
||||
|
||||
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,
|
||||
email_verified: false,
|
||||
status: 'active' as any,
|
||||
created_at: new Date(),
|
||||
updated_at: new Date()
|
||||
};
|
||||
|
||||
beforeEach(async () => {
|
||||
const mockLoginCoreService = {
|
||||
login: jest.fn(),
|
||||
register: jest.fn(),
|
||||
githubOAuth: jest.fn(),
|
||||
sendPasswordResetCode: jest.fn(),
|
||||
resetPassword: jest.fn(),
|
||||
changePassword: jest.fn(),
|
||||
sendEmailVerification: jest.fn(),
|
||||
verifyEmailCode: jest.fn(),
|
||||
resendEmailVerification: jest.fn(),
|
||||
verificationCodeLogin: jest.fn(),
|
||||
sendLoginVerificationCode: jest.fn(),
|
||||
debugVerificationCode: jest.fn(),
|
||||
};
|
||||
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [
|
||||
LoginService,
|
||||
{
|
||||
provide: LoginCoreService,
|
||||
useValue: mockLoginCoreService,
|
||||
},
|
||||
],
|
||||
}).compile();
|
||||
|
||||
service = module.get<LoginService>(LoginService);
|
||||
loginCoreService = module.get(LoginCoreService);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(service).toBeDefined();
|
||||
});
|
||||
|
||||
describe('login', () => {
|
||||
it('should login successfully', 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).toBeDefined();
|
||||
});
|
||||
|
||||
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.error_code).toBe('LOGIN_FAILED');
|
||||
});
|
||||
});
|
||||
|
||||
describe('register', () => {
|
||||
it('should register successfully', async () => {
|
||||
loginCoreService.register.mockResolvedValue({
|
||||
user: mockUser,
|
||||
isNewUser: true
|
||||
});
|
||||
|
||||
const result = await service.register({
|
||||
username: 'testuser',
|
||||
password: 'password123',
|
||||
nickname: '测试用户'
|
||||
});
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.data?.user.username).toBe('testuser');
|
||||
expect(result.data?.is_new_user).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('verificationCodeLogin', () => {
|
||||
it('should login with verification code 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.email).toBe('test@example.com');
|
||||
});
|
||||
|
||||
it('should handle verification code login failure', async () => {
|
||||
loginCoreService.verificationCodeLogin.mockRejectedValue(new Error('验证码错误'));
|
||||
|
||||
const result = await service.verificationCodeLogin({
|
||||
identifier: 'test@example.com',
|
||||
verificationCode: '999999'
|
||||
});
|
||||
|
||||
expect(result.success).toBe(false);
|
||||
expect(result.error_code).toBe('VERIFICATION_CODE_LOGIN_FAILED');
|
||||
});
|
||||
});
|
||||
|
||||
describe('sendLoginVerificationCode', () => {
|
||||
it('should send login verification code successfully', async () => {
|
||||
loginCoreService.sendLoginVerificationCode.mockResolvedValue({
|
||||
code: '123456',
|
||||
isTestMode: true
|
||||
});
|
||||
|
||||
const result = await service.sendLoginVerificationCode('test@example.com');
|
||||
|
||||
expect(result.success).toBe(false); // 测试模式下返回false
|
||||
expect(result.data?.verification_code).toBe('123456');
|
||||
expect(result.error_code).toBe('TEST_MODE_ONLY');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,273 +0,0 @@
|
||||
/**
|
||||
* 登录业务服务测试
|
||||
*/
|
||||
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { LoginService } from './login.service';
|
||||
import { LoginCoreService } from '../../core/login_core/login_core.service';
|
||||
|
||||
describe('LoginService', () => {
|
||||
let service: LoginService;
|
||||
let loginCoreService: jest.Mocked<LoginCoreService>;
|
||||
|
||||
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,
|
||||
email_verified: false,
|
||||
created_at: new Date(),
|
||||
updated_at: new Date()
|
||||
};
|
||||
|
||||
beforeEach(async () => {
|
||||
const mockLoginCoreService = {
|
||||
login: jest.fn(),
|
||||
register: jest.fn(),
|
||||
githubOAuth: jest.fn(),
|
||||
sendPasswordResetCode: jest.fn(),
|
||||
resetPassword: jest.fn(),
|
||||
changePassword: jest.fn(),
|
||||
verificationCodeLogin: jest.fn(),
|
||||
sendLoginVerificationCode: jest.fn(),
|
||||
debugVerificationCode: jest.fn(),
|
||||
};
|
||||
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [
|
||||
LoginService,
|
||||
{
|
||||
provide: LoginCoreService,
|
||||
useValue: mockLoginCoreService,
|
||||
},
|
||||
],
|
||||
}).compile();
|
||||
|
||||
service = module.get<LoginService>(LoginService);
|
||||
loginCoreService = module.get(LoginCoreService);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(service).toBeDefined();
|
||||
});
|
||||
|
||||
describe('login', () => {
|
||||
it('should return success response for valid login', 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).toBeDefined();
|
||||
});
|
||||
|
||||
it('should return error response for failed login', 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('register', () => {
|
||||
it('should return success response for valid registration', async () => {
|
||||
loginCoreService.register.mockResolvedValue({
|
||||
user: mockUser,
|
||||
isNewUser: true
|
||||
});
|
||||
|
||||
const result = await service.register({
|
||||
username: 'testuser',
|
||||
password: 'password123',
|
||||
nickname: '测试用户'
|
||||
});
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.data?.user.username).toBe('testuser');
|
||||
expect(result.data?.is_new_user).toBe(true);
|
||||
});
|
||||
|
||||
it('should return error response for failed registration', async () => {
|
||||
loginCoreService.register.mockRejectedValue(new Error('用户名已存在'));
|
||||
|
||||
const result = await service.register({
|
||||
username: 'existinguser',
|
||||
password: 'password123',
|
||||
nickname: '测试用户'
|
||||
});
|
||||
|
||||
expect(result.success).toBe(false);
|
||||
expect(result.message).toBe('用户名已存在');
|
||||
expect(result.error_code).toBe('REGISTER_FAILED');
|
||||
});
|
||||
});
|
||||
|
||||
describe('githubOAuth', () => {
|
||||
it('should return success response for GitHub OAuth', async () => {
|
||||
loginCoreService.githubOAuth.mockResolvedValue({
|
||||
user: mockUser,
|
||||
isNewUser: true
|
||||
});
|
||||
|
||||
const result = await service.githubOAuth({
|
||||
github_id: 'github123',
|
||||
username: 'githubuser',
|
||||
nickname: 'GitHub用户'
|
||||
});
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.data?.user.username).toBe('testuser');
|
||||
expect(result.data?.is_new_user).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('sendPasswordResetCode', () => {
|
||||
it('should return test mode response with verification code', async () => {
|
||||
loginCoreService.sendPasswordResetCode.mockResolvedValue({
|
||||
code: '123456',
|
||||
isTestMode: true
|
||||
});
|
||||
|
||||
const result = await service.sendPasswordResetCode('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);
|
||||
});
|
||||
|
||||
it('should return success response for real email sending', async () => {
|
||||
loginCoreService.sendPasswordResetCode.mockResolvedValue({
|
||||
code: '123456',
|
||||
isTestMode: false
|
||||
});
|
||||
|
||||
const result = await service.sendPasswordResetCode('test@example.com');
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.data?.is_test_mode).toBe(false);
|
||||
expect(result.data?.verification_code).toBeUndefined(); // 真实模式下不返回验证码
|
||||
});
|
||||
});
|
||||
|
||||
describe('resetPassword', () => {
|
||||
it('should return success response for password reset', async () => {
|
||||
loginCoreService.resetPassword.mockResolvedValue(mockUser);
|
||||
|
||||
const result = await service.resetPassword({
|
||||
identifier: 'test@example.com',
|
||||
verificationCode: '123456',
|
||||
newPassword: 'newpassword123'
|
||||
});
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.message).toBe('密码重置成功');
|
||||
});
|
||||
});
|
||||
|
||||
describe('changePassword', () => {
|
||||
it('should return success response for password change', async () => {
|
||||
loginCoreService.changePassword.mockResolvedValue(mockUser);
|
||||
|
||||
const result = await service.changePassword(BigInt(1), 'oldpassword', 'newpassword123');
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
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');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -231,7 +231,7 @@ describe('Users Entity, DTO and Service Tests', () => {
|
||||
it('应该在用户名重复时抛出ConflictException', async () => {
|
||||
mockRepository.findOne.mockResolvedValue(mockUser); // 模拟用户名已存在
|
||||
|
||||
await expect(service.create(createUserDto)).rejects.toThrow(ConflictException);
|
||||
await expect(service.createWithDuplicateCheck(createUserDto)).rejects.toThrow(ConflictException);
|
||||
expect(mockRepository.save).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
|
||||
@@ -264,82 +264,6 @@ describe('LoginCoreService', () => {
|
||||
});
|
||||
});
|
||||
|
||||
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 };
|
||||
@@ -468,55 +392,79 @@ describe('LoginCoreService', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('sendLoginVerificationCode', () => {
|
||||
it('should successfully send email login verification code', async () => {
|
||||
describe('verificationCodeLogin', () => {
|
||||
it('should successfully login with email verification code', async () => {
|
||||
const verifiedUser = { ...mockUser, email_verified: true };
|
||||
usersService.findByEmail.mockResolvedValue(verifiedUser);
|
||||
verificationService.generateCode.mockResolvedValue('123456');
|
||||
emailService.sendVerificationCode.mockResolvedValue({
|
||||
success: true,
|
||||
isTestMode: false
|
||||
verificationService.verifyCode.mockResolvedValue(true);
|
||||
|
||||
const result = await service.verificationCodeLogin({
|
||||
identifier: 'test@example.com',
|
||||
verificationCode: '123456'
|
||||
});
|
||||
|
||||
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'
|
||||
});
|
||||
expect(result.user).toEqual(verifiedUser);
|
||||
expect(result.isNewUser).toBe(false);
|
||||
expect(verificationService.verifyCode).toHaveBeenCalledWith(
|
||||
'test@example.com',
|
||||
VerificationCodeType.EMAIL_VERIFICATION,
|
||||
'123456'
|
||||
);
|
||||
});
|
||||
|
||||
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
|
||||
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'
|
||||
});
|
||||
|
||||
const result = await service.sendLoginVerificationCode('test@example.com');
|
||||
|
||||
expect(result.code).toBe('123456');
|
||||
expect(result.isTestMode).toBe(true);
|
||||
expect(result.user).toEqual(phoneUser);
|
||||
expect(result.isNewUser).toBe(false);
|
||||
expect(verificationService.verifyCode).toHaveBeenCalledWith(
|
||||
'+8613800138000',
|
||||
VerificationCodeType.SMS_VERIFICATION,
|
||||
'123456'
|
||||
);
|
||||
});
|
||||
|
||||
it('should reject unverified email', async () => {
|
||||
it('should reject unverified email user', async () => {
|
||||
usersService.findByEmail.mockResolvedValue(mockUser); // email_verified: false
|
||||
|
||||
await expect(service.sendLoginVerificationCode('test@example.com'))
|
||||
.rejects.toThrow('邮箱未验证,无法使用验证码登录');
|
||||
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.sendLoginVerificationCode('nonexistent@example.com'))
|
||||
.rejects.toThrow('用户不存在');
|
||||
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('请提供有效的邮箱或手机号');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -757,7 +757,7 @@ export class LoginCoreService {
|
||||
email: identifier,
|
||||
code: verificationCode,
|
||||
nickname: user.nickname,
|
||||
purpose: 'password_reset'
|
||||
purpose: 'login_verification'
|
||||
});
|
||||
|
||||
if (!result.success) {
|
||||
|
||||
@@ -47,7 +47,7 @@ export interface VerificationEmailOptions {
|
||||
/** 用户昵称 */
|
||||
nickname?: string;
|
||||
/** 验证码用途 */
|
||||
purpose: 'email_verification' | 'password_reset';
|
||||
purpose: 'email_verification' | 'password_reset' | 'login_verification';
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -167,9 +167,15 @@ export class EmailService {
|
||||
if (purpose === 'email_verification') {
|
||||
subject = '【Whale Town】邮箱验证码';
|
||||
template = this.getEmailVerificationTemplate(code, nickname);
|
||||
} else {
|
||||
} else if (purpose === 'password_reset') {
|
||||
subject = '【Whale Town】密码重置验证码';
|
||||
template = this.getPasswordResetTemplate(code, nickname);
|
||||
} else if (purpose === 'login_verification') {
|
||||
subject = '【Whale Town】登录验证码';
|
||||
template = this.getPasswordResetTemplate(code, nickname); // 复用密码重置模板
|
||||
} else {
|
||||
subject = '【Whale Town】验证码';
|
||||
template = this.getEmailVerificationTemplate(code, nickname);
|
||||
}
|
||||
|
||||
return await this.sendEmail({
|
||||
|
||||
Reference in New Issue
Block a user