import { VerificationCodeService } from '../../../src/services/verification/verification-code.service'; // Mock cache jest.mock('../../../src/config/redis', () => ({ cache: { get: jest.fn(), set: jest.fn(), del: jest.fn(), exists: jest.fn(), }, })); // Mock logger jest.mock('../../../src/utils/logger', () => ({ logger: { info: jest.fn(), error: jest.fn(), warn: jest.fn(), }, })); describe('VerificationCodeService 单元测试', () => { let verificationService: VerificationCodeService; const mockCache = require('../../../src/config/redis').cache; beforeEach(() => { verificationService = new VerificationCodeService(); jest.clearAllMocks(); }); describe('generateAndSendCode', () => { it('应该成功生成并发送验证码', async () => { const phone = '13800138000'; const type = 'register'; // Mock频率检查通过 mockCache.exists.mockResolvedValue(false); // Mock存储成功 mockCache.set.mockResolvedValue(true); const result = await verificationService.generateAndSendCode(phone, type); expect(mockCache.exists).toHaveBeenCalledWith(`rate_limit:${type}:${phone}`); expect(mockCache.set).toHaveBeenCalledWith( `verification:${type}:${phone}`, expect.objectContaining({ code: expect.stringMatching(/^\d{6}$/), createdAt: expect.any(Number), attempts: 0, type, }), 300 // 5分钟 ); expect(mockCache.set).toHaveBeenCalledWith( `rate_limit:${type}:${phone}`, '1', 60 // 60秒 ); expect(result).toMatch(/^\d{6}$/); }); it('应该拒绝频繁发送验证码', async () => { const phone = '13800138000'; const type = 'register'; // Mock频率检查不通过 mockCache.exists.mockResolvedValue(true); await expect(verificationService.generateAndSendCode(phone, type)) .rejects.toThrow('验证码发送过于频繁,请稍后重试'); expect(mockCache.exists).toHaveBeenCalledWith(`rate_limit:${type}:${phone}`); expect(mockCache.set).not.toHaveBeenCalledWith( `verification:${type}:${phone}`, expect.any(Object), expect.any(Number) ); }); }); describe('verifyCode', () => { it('应该成功验证正确的验证码', async () => { const phone = '13800138000'; const code = '123456'; const type = 'register'; // Mock存储的验证码数据 const codeData = { code: '123456', createdAt: Date.now() - 10000, // 10秒前创建 attempts: 0, type, }; mockCache.get.mockResolvedValue(codeData); mockCache.del.mockResolvedValue(true); const result = await verificationService.verifyCode(phone, code, type); expect(mockCache.get).toHaveBeenCalledWith(`verification:${type}:${phone}`); expect(mockCache.del).toHaveBeenCalledWith(`verification:${type}:${phone}`); expect(result).toBe(true); }); it('应该拒绝错误的验证码', async () => { const phone = '13800138000'; const code = '123456'; const type = 'register'; // Mock存储的验证码数据(不同的验证码) const codeData = { code: '654321', createdAt: Date.now() - 10000, // 10秒前创建 attempts: 0, type, }; mockCache.get.mockResolvedValue(codeData); mockCache.set.mockResolvedValue(true); const result = await verificationService.verifyCode(phone, code, type); expect(mockCache.get).toHaveBeenCalledWith(`verification:${type}:${phone}`); expect(mockCache.set).toHaveBeenCalledWith( `verification:${type}:${phone}`, expect.objectContaining({ code: '654321', attempts: 1, // 尝试次数增加 }), 300 ); expect(result).toBe(false); }); it('应该拒绝过期的验证码', async () => { const phone = '13800138000'; const code = '123456'; const type = 'register'; // Mock没有找到验证码数据(已过期) mockCache.get.mockResolvedValue(null); await expect(verificationService.verifyCode(phone, code, type)) .rejects.toThrow('验证码已过期,请重新获取'); expect(mockCache.get).toHaveBeenCalledWith(`verification:${type}:${phone}`); }); it('应该拒绝尝试次数过多的验证码', async () => { const phone = '13800138000'; const code = '123456'; const type = 'register'; // Mock存储的验证码数据(尝试次数已达上限) const codeData = { code: '654321', createdAt: Date.now() - 10000, // 10秒前创建 attempts: 5, // 已达到最大尝试次数 type, }; mockCache.get.mockResolvedValue(codeData); mockCache.del.mockResolvedValue(true); await expect(verificationService.verifyCode(phone, code, type)) .rejects.toThrow('验证码尝试次数过多,请重新获取'); expect(mockCache.get).toHaveBeenCalledWith(`verification:${type}:${phone}`); expect(mockCache.del).toHaveBeenCalledWith(`verification:${type}:${phone}`); }); }); describe('checkRateLimit', () => { it('应该允许在限制时间外发送验证码', async () => { const phone = '13800138000'; const type = 'register'; // Mock频率限制键不存在 mockCache.exists.mockResolvedValue(false); const result = await verificationService.checkRateLimit(phone, type); expect(mockCache.exists).toHaveBeenCalledWith(`rate_limit:${type}:${phone}`); expect(result).toBe(true); }); it('应该拒绝在限制时间内发送验证码', async () => { const phone = '13800138000'; const type = 'register'; // Mock频率限制键存在 mockCache.exists.mockResolvedValue(true); const result = await verificationService.checkRateLimit(phone, type); expect(mockCache.exists).toHaveBeenCalledWith(`rate_limit:${type}:${phone}`); expect(result).toBe(false); }); }); describe('getStoredCode', () => { it('应该返回存储的验证码数据', async () => { const phone = '13800138000'; const type = 'register'; const codeData = { code: '123456', createdAt: Date.now() - 10000, attempts: 0, type, }; mockCache.get.mockResolvedValue(codeData); const result = await verificationService.getStoredCode(phone, type); expect(mockCache.get).toHaveBeenCalledWith(`verification:${type}:${phone}`); expect(result).toEqual(codeData); }); it('应该返回null当验证码不存在', async () => { const phone = '13800138000'; const type = 'register'; // Mock没有找到验证码数据 mockCache.get.mockResolvedValue(null); const result = await verificationService.getStoredCode(phone, type); expect(mockCache.get).toHaveBeenCalledWith(`verification:${type}:${phone}`); expect(result).toBeNull(); }); }); describe('deleteCode', () => { it('应该成功删除验证码', async () => { const phone = '13800138000'; const type = 'register'; mockCache.del.mockResolvedValue(true); await verificationService.deleteCode(phone, type); expect(mockCache.del).toHaveBeenCalledWith(`verification:${type}:${phone}`); }); }); });