package email import "testing" // 热重载的核心安全属性:后台填错配置时,Reload 必须「返回 error + 保留旧客户端 + 不 panic」, // 绝不能让线上服务失去发信能力。(OnInit 同样不 panic,缺配置返回 error 由调用方降级。) func TestReloadBadConfigKeepsOldClient(t *testing.T) { if err := OnInit(map[string]interface{}{ "EmailType": 1, "FromEmail": "a@example.com", "FromName": "Old", "Password": "pw", }); err != nil { t.Fatalf("OnInit 失败: %v", err) } old := get() if old == nil { t.Fatal("OnInit 后客户端不应为 nil") } cases := []struct { name string cfg map[string]interface{} }{ {"FromEmail 是裸域名(Resend 会 422 拒绝)", map[string]interface{}{ "EmailType": 3, "FromEmail": "mail.example.com", "Password": "re_x"}}, {"Password 为空", map[string]interface{}{ "EmailType": 1, "FromEmail": "a@example.com"}}, {"FromEmail 为空", map[string]interface{}{ "EmailType": 1, "Password": "pw"}}, } for _, c := range cases { t.Run(c.name, func(t *testing.T) { if err := Reload(c.cfg); err == nil { t.Fatal("非法配置必须返回 error") } if get() != old { t.Fatal("重载失败后必须保留旧客户端") } }) } } // 合法配置应当整体热替换客户端,且能在 SMTP ↔ Resend 两种不同具体类型间切换。 func TestReloadSwapsClient(t *testing.T) { if err := OnInit(map[string]interface{}{ "EmailType": 1, "FromEmail": "a@example.com", "Password": "pw", }); err != nil { t.Fatalf("OnInit 失败: %v", err) } old := get() if _, ok := old.(*Email); !ok { t.Fatalf("EmailType=1 应为 *Email,实得 %T", old) } if err := Reload(map[string]interface{}{ "EmailType": 3, "FromEmail": "noreply@mail.example.com", "FromName": "New", "Password": "re_x", }); err != nil { t.Fatalf("合法配置重载应成功: %v", err) } cur := get() if cur == old { t.Fatal("重载成功后应替换为新客户端") } r, ok := cur.(*Resend) if !ok { t.Fatalf("EmailType=3 应为 *Resend,实得 %T", cur) } if r.ApiKey != "re_x" || r.From != "noreply@mail.example.com" || r.Name != "New" { t.Fatalf("新客户端未取到新配置: %+v", r) } }