test:添加验证码冷却时间清除功能测试
为新增的验证码冷却时间清除功能添加全面的测试用例: 验证服务测试: - 测试成功清除冷却时间 - 测试清除不存在的冷却时间 - 测试Redis操作错误处理 - 测试不同类型和标识符的冷却时间清除 登录核心服务测试: - 测试注册成功后自动清除冷却时间 - 测试密码重置成功后自动清除冷却时间 - 测试验证码登录成功后自动清除冷却时间 - 测试冷却时间清除失败的优雅处理
This commit is contained in:
@@ -51,6 +51,7 @@ describe('LoginCoreService', () => {
|
||||
const mockVerificationService = {
|
||||
generateCode: jest.fn(),
|
||||
verifyCode: jest.fn(),
|
||||
clearCooldown: jest.fn(),
|
||||
};
|
||||
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
@@ -146,6 +147,69 @@ describe('LoginCoreService', () => {
|
||||
expect(result.isNewUser).toBe(true);
|
||||
});
|
||||
|
||||
it('should register with email and clear cooldown', async () => {
|
||||
const email = 'test@example.com';
|
||||
usersService.findByUsername.mockResolvedValue(null);
|
||||
usersService.findByEmail.mockResolvedValue(null);
|
||||
usersService.create.mockResolvedValue({ ...mockUser, email });
|
||||
verificationService.verifyCode.mockResolvedValue(true);
|
||||
verificationService.clearCooldown.mockResolvedValue(undefined);
|
||||
emailService.sendWelcomeEmail.mockResolvedValue(undefined);
|
||||
jest.spyOn(service as any, 'hashPassword').mockResolvedValue('hashedpassword');
|
||||
jest.spyOn(service as any, 'validatePasswordStrength').mockImplementation(() => {});
|
||||
|
||||
const result = await service.register({
|
||||
username: 'testuser',
|
||||
password: 'password123',
|
||||
nickname: '测试用户',
|
||||
email,
|
||||
email_verification_code: '123456'
|
||||
});
|
||||
|
||||
expect(result.user.email).toBe(email);
|
||||
expect(result.isNewUser).toBe(true);
|
||||
expect(verificationService.clearCooldown).toHaveBeenCalledWith(
|
||||
email,
|
||||
VerificationCodeType.EMAIL_VERIFICATION
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle cooldown clearing failure gracefully', async () => {
|
||||
const email = 'test@example.com';
|
||||
usersService.findByUsername.mockResolvedValue(null);
|
||||
usersService.findByEmail.mockResolvedValue(null);
|
||||
usersService.create.mockResolvedValue({ ...mockUser, email });
|
||||
verificationService.verifyCode.mockResolvedValue(true);
|
||||
verificationService.clearCooldown.mockRejectedValue(new Error('Redis error'));
|
||||
emailService.sendWelcomeEmail.mockResolvedValue(undefined);
|
||||
jest.spyOn(service as any, 'hashPassword').mockResolvedValue('hashedpassword');
|
||||
jest.spyOn(service as any, 'validatePasswordStrength').mockImplementation(() => {});
|
||||
|
||||
// Mock console.warn to avoid test output noise
|
||||
const consoleSpy = jest.spyOn(console, 'warn').mockImplementation(() => {});
|
||||
|
||||
const result = await service.register({
|
||||
username: 'testuser',
|
||||
password: 'password123',
|
||||
nickname: '测试用户',
|
||||
email,
|
||||
email_verification_code: '123456'
|
||||
});
|
||||
|
||||
expect(result.user.email).toBe(email);
|
||||
expect(result.isNewUser).toBe(true);
|
||||
expect(verificationService.clearCooldown).toHaveBeenCalledWith(
|
||||
email,
|
||||
VerificationCodeType.EMAIL_VERIFICATION
|
||||
);
|
||||
expect(consoleSpy).toHaveBeenCalledWith(
|
||||
`清除验证码冷却时间失败: ${email}`,
|
||||
expect.any(Error)
|
||||
);
|
||||
|
||||
consoleSpy.mockRestore();
|
||||
});
|
||||
|
||||
it('should validate password strength', async () => {
|
||||
jest.spyOn(service as any, 'validatePasswordStrength').mockImplementation(() => {
|
||||
throw new BadRequestException('密码长度至少8位');
|
||||
@@ -231,6 +295,59 @@ describe('LoginCoreService', () => {
|
||||
expect(result).toEqual(mockUser);
|
||||
});
|
||||
|
||||
it('should reset password and clear cooldown', async () => {
|
||||
const identifier = 'test@example.com';
|
||||
verificationService.verifyCode.mockResolvedValue(true);
|
||||
verificationService.clearCooldown.mockResolvedValue(undefined);
|
||||
usersService.findByEmail.mockResolvedValue(mockUser);
|
||||
usersService.update.mockResolvedValue(mockUser);
|
||||
jest.spyOn(service as any, 'hashPassword').mockResolvedValue('newhashedpassword');
|
||||
jest.spyOn(service as any, 'validatePasswordStrength').mockImplementation(() => {});
|
||||
|
||||
const result = await service.resetPassword({
|
||||
identifier,
|
||||
verificationCode: '123456',
|
||||
newPassword: 'newpassword123'
|
||||
});
|
||||
|
||||
expect(result).toEqual(mockUser);
|
||||
expect(verificationService.clearCooldown).toHaveBeenCalledWith(
|
||||
identifier,
|
||||
VerificationCodeType.PASSWORD_RESET
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle cooldown clearing failure gracefully during password reset', async () => {
|
||||
const identifier = 'test@example.com';
|
||||
verificationService.verifyCode.mockResolvedValue(true);
|
||||
verificationService.clearCooldown.mockRejectedValue(new Error('Redis error'));
|
||||
usersService.findByEmail.mockResolvedValue(mockUser);
|
||||
usersService.update.mockResolvedValue(mockUser);
|
||||
jest.spyOn(service as any, 'hashPassword').mockResolvedValue('newhashedpassword');
|
||||
jest.spyOn(service as any, 'validatePasswordStrength').mockImplementation(() => {});
|
||||
|
||||
// Mock console.warn to avoid test output noise
|
||||
const consoleSpy = jest.spyOn(console, 'warn').mockImplementation(() => {});
|
||||
|
||||
const result = await service.resetPassword({
|
||||
identifier,
|
||||
verificationCode: '123456',
|
||||
newPassword: 'newpassword123'
|
||||
});
|
||||
|
||||
expect(result).toEqual(mockUser);
|
||||
expect(verificationService.clearCooldown).toHaveBeenCalledWith(
|
||||
identifier,
|
||||
VerificationCodeType.PASSWORD_RESET
|
||||
);
|
||||
expect(consoleSpy).toHaveBeenCalledWith(
|
||||
`清除验证码冷却时间失败: ${identifier}`,
|
||||
expect.any(Error)
|
||||
);
|
||||
|
||||
consoleSpy.mockRestore();
|
||||
});
|
||||
|
||||
it('should throw BadRequestException for invalid verification code', async () => {
|
||||
verificationService.verifyCode.mockResolvedValue(false);
|
||||
|
||||
@@ -336,82 +453,75 @@ describe('LoginCoreService', () => {
|
||||
);
|
||||
});
|
||||
|
||||
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('verificationCodeLogin', () => {
|
||||
it('should successfully login with email verification code', async () => {
|
||||
it('should successfully login with email verification code and clear cooldown', async () => {
|
||||
const identifier = 'test@example.com';
|
||||
const verifiedUser = { ...mockUser, email_verified: true };
|
||||
usersService.findByEmail.mockResolvedValue(verifiedUser);
|
||||
verificationService.verifyCode.mockResolvedValue(true);
|
||||
verificationService.clearCooldown.mockResolvedValue(undefined);
|
||||
|
||||
const result = await service.verificationCodeLogin({
|
||||
identifier: 'test@example.com',
|
||||
identifier,
|
||||
verificationCode: '123456'
|
||||
});
|
||||
|
||||
expect(result.user).toEqual(verifiedUser);
|
||||
expect(result.isNewUser).toBe(false);
|
||||
expect(verificationService.verifyCode).toHaveBeenCalledWith(
|
||||
'test@example.com',
|
||||
VerificationCodeType.EMAIL_VERIFICATION,
|
||||
'123456'
|
||||
expect(verificationService.clearCooldown).toHaveBeenCalledWith(
|
||||
identifier,
|
||||
VerificationCodeType.EMAIL_VERIFICATION
|
||||
);
|
||||
});
|
||||
|
||||
it('should successfully login with phone verification code and clear cooldown', async () => {
|
||||
const identifier = '+8613800138000';
|
||||
const phoneUser = { ...mockUser, phone: identifier };
|
||||
usersService.findAll.mockResolvedValue([phoneUser]);
|
||||
verificationService.verifyCode.mockResolvedValue(true);
|
||||
verificationService.clearCooldown.mockResolvedValue(undefined);
|
||||
|
||||
const result = await service.verificationCodeLogin({
|
||||
identifier,
|
||||
verificationCode: '123456'
|
||||
});
|
||||
|
||||
expect(result.user).toEqual(phoneUser);
|
||||
expect(result.isNewUser).toBe(false);
|
||||
expect(verificationService.clearCooldown).toHaveBeenCalledWith(
|
||||
identifier,
|
||||
VerificationCodeType.SMS_VERIFICATION
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle cooldown clearing failure gracefully during verification code login', async () => {
|
||||
const identifier = 'test@example.com';
|
||||
const verifiedUser = { ...mockUser, email_verified: true };
|
||||
usersService.findByEmail.mockResolvedValue(verifiedUser);
|
||||
verificationService.verifyCode.mockResolvedValue(true);
|
||||
verificationService.clearCooldown.mockRejectedValue(new Error('Redis error'));
|
||||
|
||||
// Mock console.warn to avoid test output noise
|
||||
const consoleSpy = jest.spyOn(console, 'warn').mockImplementation(() => {});
|
||||
|
||||
const result = await service.verificationCodeLogin({
|
||||
identifier,
|
||||
verificationCode: '123456'
|
||||
});
|
||||
|
||||
expect(result.user).toEqual(verifiedUser);
|
||||
expect(result.isNewUser).toBe(false);
|
||||
expect(verificationService.clearCooldown).toHaveBeenCalledWith(
|
||||
identifier,
|
||||
VerificationCodeType.EMAIL_VERIFICATION
|
||||
);
|
||||
expect(consoleSpy).toHaveBeenCalledWith(
|
||||
`清除验证码冷却时间失败: ${identifier}`,
|
||||
expect.any(Error)
|
||||
);
|
||||
|
||||
consoleSpy.mockRestore();
|
||||
});
|
||||
|
||||
it('should successfully login with phone verification code', async () => {
|
||||
const phoneUser = { ...mockUser, phone: '+8613800138000' };
|
||||
usersService.findAll.mockResolvedValue([phoneUser]);
|
||||
|
||||
@@ -587,4 +587,69 @@ describe('VerificationService', () => {
|
||||
expect(code).toMatch(/^\d{6}$/);
|
||||
});
|
||||
});
|
||||
|
||||
describe('clearCooldown', () => {
|
||||
it('应该成功清除验证码冷却时间', async () => {
|
||||
const email = 'test@example.com';
|
||||
const type = VerificationCodeType.EMAIL_VERIFICATION;
|
||||
|
||||
mockRedis.del.mockResolvedValue(true);
|
||||
|
||||
await service.clearCooldown(email, type);
|
||||
|
||||
expect(mockRedis.del).toHaveBeenCalledWith(`verification_cooldown:${type}:${email}`);
|
||||
});
|
||||
|
||||
it('应该处理清除不存在的冷却时间', async () => {
|
||||
const email = 'test@example.com';
|
||||
const type = VerificationCodeType.EMAIL_VERIFICATION;
|
||||
|
||||
mockRedis.del.mockResolvedValue(false);
|
||||
|
||||
await service.clearCooldown(email, type);
|
||||
|
||||
expect(mockRedis.del).toHaveBeenCalledWith(`verification_cooldown:${type}:${email}`);
|
||||
});
|
||||
|
||||
it('应该处理Redis删除操作错误', async () => {
|
||||
const email = 'test@example.com';
|
||||
const type = VerificationCodeType.EMAIL_VERIFICATION;
|
||||
|
||||
mockRedis.del.mockRejectedValue(new Error('Redis delete failed'));
|
||||
|
||||
await expect(service.clearCooldown(email, type)).rejects.toThrow('Redis delete failed');
|
||||
});
|
||||
|
||||
it('应该为不同类型的验证码清除对应的冷却时间', async () => {
|
||||
const email = 'test@example.com';
|
||||
const types = [
|
||||
VerificationCodeType.EMAIL_VERIFICATION,
|
||||
VerificationCodeType.PASSWORD_RESET,
|
||||
VerificationCodeType.SMS_VERIFICATION,
|
||||
];
|
||||
|
||||
mockRedis.del.mockResolvedValue(true);
|
||||
|
||||
for (const type of types) {
|
||||
await service.clearCooldown(email, type);
|
||||
expect(mockRedis.del).toHaveBeenCalledWith(`verification_cooldown:${type}:${email}`);
|
||||
}
|
||||
|
||||
expect(mockRedis.del).toHaveBeenCalledTimes(types.length);
|
||||
});
|
||||
|
||||
it('应该为不同标识符清除对应的冷却时间', async () => {
|
||||
const identifiers = ['test1@example.com', 'test2@example.com', '+8613800138000'];
|
||||
const type = VerificationCodeType.EMAIL_VERIFICATION;
|
||||
|
||||
mockRedis.del.mockResolvedValue(true);
|
||||
|
||||
for (const identifier of identifiers) {
|
||||
await service.clearCooldown(identifier, type);
|
||||
expect(mockRedis.del).toHaveBeenCalledWith(`verification_cooldown:${type}:${identifier}`);
|
||||
}
|
||||
|
||||
expect(mockRedis.del).toHaveBeenCalledTimes(identifiers.length);
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user