package services import ( "context" "errors" "platform/pkg/testutil" "strconv" "testing" "time" "github.com/alicebob/miniredis/v2" ) func Test_verifierService_SendSms(t *testing.T) { type args struct { ctx context.Context phone string purpose VerifierSmsPurpose } tests := []struct { name string args args setup func(mr *miniredis.Miniredis) wantErr bool wantErrType error }{ { name: "正常发送成功(无旧验证码)", args: args{ ctx: context.Background(), phone: "13812345678", purpose: VerifierSmsPurposeLogin, }, setup: func(mr *miniredis.Miniredis) {}, wantErr: false, }, { name: "正常发送成功(有旧验证码)", args: args{ ctx: context.Background(), phone: "13812345679", purpose: VerifierSmsPurposeLogin, }, setup: func(mr *miniredis.Miniredis) { key := smsKey("13812345679", VerifierSmsPurposeLogin) mr.Set(key, "123456") mr.SetTTL(key, 10*time.Minute) }, wantErr: false, }, { name: "发送频率过快", args: args{ ctx: context.Background(), phone: "13812345680", purpose: VerifierSmsPurposeLogin, }, setup: func(mr *miniredis.Miniredis) { key := smsKey("13812345680", VerifierSmsPurposeLogin) + ":lock" mr.Set(key, "") mr.SetTTL(key, 1*time.Minute) }, wantErr: true, wantErrType: VerifierServiceSendLimitErr(0), }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { // 设置 Redis 测试环境 mr := testutil.SetupRedisTest(t) defer mr.Close() // 执行测试前的设置 if tt.setup != nil { tt.setup(mr) } s := &verifierService{} err := s.SendSms(tt.args.ctx, tt.args.phone, tt.args.purpose) // 验证错误 if (err != nil) != tt.wantErr { t.Errorf("SendSms() error = %v, wantErr %v", err, tt.wantErr) return } // 验证错误类型 if tt.wantErr && tt.wantErrType != nil { var verifierServiceSendLimitErr VerifierServiceSendLimitErr if errors.As(err, &verifierServiceSendLimitErr) { var verifierServiceSendLimitErr VerifierServiceSendLimitErr if !errors.As(tt.wantErrType, &verifierServiceSendLimitErr) { t.Errorf("SendSms() error type = %T, wantErrType %T", err, tt.wantErrType) } } } // 验证 Redis 中的记录 if !tt.wantErr { key := smsKey(tt.args.phone, tt.args.purpose) keyLock := key + ":lock" // 验证码应存在 val, err := mr.Get(key) if err != nil { t.Errorf("验证码应存在但不存在: %v", err) } // 限速锁应存在 _, err = mr.Get(keyLock) if err != nil { t.Errorf("限速锁应存在但不存在: %v", err) } // 验证码应为6位数字 code, err := strconv.Atoi(val) if err != nil || code < 100000 || code > 999999 { t.Errorf("验证码应为6位数字: %v", val) } } }) } } func Test_verifierService_VerifySms(t *testing.T) { type args struct { ctx context.Context phone string code string } tests := []struct { name string args args setup func(mr *miniredis.Miniredis) wantErr bool wantErrType error }{ { name: "验证码正确", args: args{ ctx: context.Background(), phone: "13812345678", code: "123456", }, setup: func(mr *miniredis.Miniredis) { key := smsKey("13812345678", VerifierSmsPurposeLogin) keyLock := key + ":lock" mr.Set(key, "123456") mr.SetTTL(key, 10*time.Minute) mr.Set(keyLock, "") mr.SetTTL(keyLock, 1*time.Minute) }, wantErr: false, }, { name: "验证码错误", args: args{ ctx: context.Background(), phone: "13812345679", code: "654321", }, setup: func(mr *miniredis.Miniredis) { key := smsKey("13812345679", VerifierSmsPurposeLogin) keyLock := key + ":lock" mr.Set(key, "123456") mr.SetTTL(key, 10*time.Minute) mr.Set(keyLock, "") mr.SetTTL(keyLock, 1*time.Minute) }, wantErr: true, wantErrType: ErrVerifierServiceInvalid, }, { name: "验证码过期", args: args{ ctx: context.Background(), phone: "13812345680", code: "123456", }, setup: func(mr *miniredis.Miniredis) { // 不设置验证码,模拟过期情况 }, wantErr: true, wantErrType: ErrVerifierServiceInvalid, }, { name: "手机号错误", args: args{ ctx: context.Background(), phone: "13812345681", code: "123456", }, setup: func(mr *miniredis.Miniredis) { // 设置一个不同手机号的验证码 key := smsKey("13800000000", VerifierSmsPurposeLogin) mr.Set(key, "123456") mr.SetTTL(key, 10*time.Minute) }, wantErr: true, wantErrType: ErrVerifierServiceInvalid, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { // 设置 Redis 测试环境 mr := testutil.SetupRedisTest(t) defer mr.Close() // 执行测试前的设置 if tt.setup != nil { tt.setup(mr) } s := &verifierService{} err := s.VerifySms(tt.args.ctx, tt.args.phone, tt.args.code) if (err != nil) != tt.wantErr { t.Errorf("VerifySms() error = %v, wantErr %v", err, tt.wantErr) return } // 检查错误类型 if tt.wantErr && tt.wantErrType != nil && !errors.Is(err, tt.wantErrType) { t.Errorf("VerifySms() error = %v, wantErrType %v", err, tt.wantErrType) return } // 验证成功后 Redis 中应该没有该记录 if err == nil { key := smsKey(tt.args.phone, VerifierSmsPurposeLogin) keyLock := key + ":lock" _, redisErr := mr.Get(key) if redisErr == nil { t.Errorf("验证码验证成功后应删除,但仍存在") } _, redisErr = mr.Get(keyLock) if redisErr == nil { t.Errorf("限速锁验证成功后应删除,但仍存在") } } }) } }