Files
hfb_sys/backend/internal/modules/auth/sms_resolver_test.go
T
yml2213 dc13b5dc82 feat(distribution): 支持分站独立短信验证码通道
- 新增分销站点独立短信服务商配置(阿里云),登录验证码按 site_code/domain
  解析专属通道,未启用或解析失败时回退主站默认通道
- 密钥经 crypto.Encryptor 加密落库,模型字段 json 序列化隔离,DTO 仅回传掩码
- provider 白名单校验,mock 仅在非生产环境放行;启用时强制校验必填凭据
- 配置读写/测试接口限管理员权限,测试发送增加 60s 频控(Redis,缺省进程内)
- 解密失败记告警并优雅回退主站,避免把密文当明文密钥使用
- 两套前端新增短信配置入口、必填与手机号校验、发送冷却
- 新增迁移 000071 及加解密、白名单、频控、解析回退等单测
2026-09-16 14:06:01 +08:00

100 lines
2.9 KiB
Go

package auth
import (
"context"
"testing"
"time"
smsprovider "hfb_sys/backend/internal/integrations/sms"
"github.com/redis/go-redis/v9"
"go.uber.org/zap"
)
type recordProvider struct {
sentPhones []string
}
func (p *recordProvider) SendLoginCode(ctx context.Context, phone string, code string) error {
p.sentPhones = append(p.sentPhones, phone)
return nil
}
type mockResolver struct {
customProvider smsprovider.Provider
calledSites []string
}
func (m *mockResolver) ResolveSMSProvider(ctx context.Context, siteCode, domain string) (smsprovider.Provider, error) {
m.calledSites = append(m.calledSites, siteCode)
if siteCode == "site_custom" {
return m.customProvider, nil
}
return nil, nil
}
func TestSendSMSCode_SiteResolutionAndFallback(t *testing.T) {
rdb := redis.NewClient(&redis.Options{
Addr: "127.0.0.1:16379",
DB: 14,
})
ctx := t.Context()
if err := rdb.Ping(ctx).Err(); err != nil {
t.Skipf("跳过测试:未检测到测试 Redis: %v", err)
}
t.Cleanup(func() {
_ = rdb.FlushDB(context.Background()).Err()
_ = rdb.Close()
})
_ = rdb.FlushDB(ctx)
defaultProv := &recordProvider{}
customProv := &recordProvider{}
resolver := &mockResolver{customProvider: customProv}
svc := NewService(nil, rdb, nil, defaultProv, zap.NewNop())
svc.SetSiteSMSResolver(resolver)
// 1. 无分站标识 -> 使用默认主站 provider
if err := rdb.Set(ctx, "captcha:auth:sms:cap1", "ABCD", time.Minute).Err(); err != nil {
t.Fatal(err)
}
phone1 := "13800138001"
err := svc.SendSMSCode(ctx, phone1, "cap1", "ABCD")
if err != nil {
t.Fatalf("SendSMSCode failed: %v", err)
}
if len(defaultProv.sentPhones) != 1 || defaultProv.sentPhones[0] != phone1 {
t.Fatalf("expected default provider to send, got %v", defaultProv.sentPhones)
}
if len(customProv.sentPhones) != 0 {
t.Fatalf("custom provider should not be called without site")
}
// 2. 携带自定义短信分站标识 -> 使用 custom provider
if err := rdb.Set(ctx, "captcha:auth:sms:cap2", "EFGH", time.Minute).Err(); err != nil {
t.Fatal(err)
}
phone2 := "13800138002"
err = svc.SendSMSCode(ctx, phone2, "cap2", "EFGH", "site_custom")
if err != nil {
t.Fatalf("SendSMSCode with custom site failed: %v", err)
}
if len(customProv.sentPhones) != 1 || customProv.sentPhones[0] != phone2 {
t.Fatalf("expected custom provider to send, got %v", customProv.sentPhones)
}
// 3. 携带未配置短信分站标识 -> 优雅降级到 default provider
if err := rdb.Set(ctx, "captcha:auth:sms:cap3", "JKLM", time.Minute).Err(); err != nil {
t.Fatal(err)
}
phone3 := "13800138003"
err = svc.SendSMSCode(ctx, phone3, "cap3", "JKLM", "site_fallback")
if err != nil {
t.Fatalf("SendSMSCode with fallback site failed: %v", err)
}
if len(defaultProv.sentPhones) != 2 || defaultProv.sentPhones[1] != phone3 {
t.Fatalf("expected default provider to handle fallback, got %v", defaultProv.sentPhones)
}
}