更新
This commit is contained in:
+486
@@ -0,0 +1,486 @@
|
||||
package mailx_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
mailx "code.yun.ink/pkg/mailx"
|
||||
)
|
||||
|
||||
func TestNewManagerEmpty(t *testing.T) {
|
||||
m := mailx.NewManager()
|
||||
if _, err := m.Sender(""); err == nil {
|
||||
t.Fatal("Sender() on empty manager should error")
|
||||
}
|
||||
if err := m.Send(context.Background(), mailx.NewMessage().To("a@b.com").Subject("s").Build()); err == nil {
|
||||
t.Fatal("Send() on empty manager should error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRegisterSetsDefault(t *testing.T) {
|
||||
m := mailx.NewManager()
|
||||
if err := m.Register(newMockSender("a")); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := m.Register(newMockSender("b")); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
s, err := m.Sender("")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if s.Name() != "a" {
|
||||
t.Errorf("default sender = %q, want a", s.Name())
|
||||
}
|
||||
}
|
||||
|
||||
func TestRegisterDuplicate(t *testing.T) {
|
||||
m := mailx.NewManager()
|
||||
_ = m.Register(newMockSender("a"))
|
||||
err := m.Register(newMockSender("a"))
|
||||
if err == nil || !strings.Contains(err.Error(), "already registered") {
|
||||
t.Fatalf("err = %v, want already registered", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRegisterNil(t *testing.T) {
|
||||
m := mailx.NewManager()
|
||||
if err := m.Register(nil); err == nil {
|
||||
t.Fatal("Register(nil) should error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRegisterEmptyName(t *testing.T) {
|
||||
m := mailx.NewManager()
|
||||
if err := m.Register(newMockSender("")); err == nil {
|
||||
t.Fatal("Register(empty name) should error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnregister(t *testing.T) {
|
||||
m := mailx.NewManager()
|
||||
_ = m.Register(newMockSender("a"))
|
||||
_ = m.Register(newMockSender("b"))
|
||||
|
||||
m.Unregister("a")
|
||||
s, err := m.Sender("")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if s.Name() != "b" {
|
||||
t.Errorf("fallback default = %q, want b", s.Name())
|
||||
}
|
||||
|
||||
// 注销最后一个后不再有默认通道
|
||||
m.Unregister("b")
|
||||
if _, err := m.Sender(""); err == nil {
|
||||
t.Fatal("expected error after unregistering all senders")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetDefault(t *testing.T) {
|
||||
m := mailx.NewManager()
|
||||
_ = m.Register(newMockSender("a"))
|
||||
_ = m.Register(newMockSender("b"))
|
||||
|
||||
if err := m.SetDefault("b"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
s, _ := m.Sender("")
|
||||
if s.Name() != "b" {
|
||||
t.Errorf("default = %q, want b", s.Name())
|
||||
}
|
||||
|
||||
if err := m.SetDefault("nope"); err == nil {
|
||||
t.Fatal("SetDefault(unknown) should error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSenderLookup(t *testing.T) {
|
||||
m := mailx.NewManager()
|
||||
_ = m.Register(newMockSender("a"))
|
||||
|
||||
if _, err := m.Sender("a"); err != nil {
|
||||
t.Fatalf("Sender(a) = %v", err)
|
||||
}
|
||||
if _, err := m.Sender("nope"); err == nil {
|
||||
t.Fatal("Sender(unknown) should error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSend(t *testing.T) {
|
||||
m := mailx.NewManager()
|
||||
mock := newMockSender("mock")
|
||||
_ = m.Register(mock)
|
||||
|
||||
msg := mailx.NewMessage().To("a@b.com").Subject("s").Body("b").Build()
|
||||
if err := m.Send(context.Background(), msg); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if mock.count() != 1 {
|
||||
t.Fatalf("send count = %d, want 1", mock.count())
|
||||
}
|
||||
}
|
||||
|
||||
func TestSendWithRoutesByName(t *testing.T) {
|
||||
m := mailx.NewManager()
|
||||
a := newMockSender("a")
|
||||
b := newMockSender("b")
|
||||
_ = m.Register(a)
|
||||
_ = m.Register(b)
|
||||
|
||||
msg := mailx.NewMessage().To("x@y.com").Subject("s").Build()
|
||||
if err := m.SendWith(context.Background(), "b", msg); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if a.count() != 0 || b.count() != 1 {
|
||||
t.Fatalf("a=%d b=%d, want a=0 b=1", a.count(), b.count())
|
||||
}
|
||||
|
||||
if err := m.SendWith(context.Background(), "nope", msg); err == nil {
|
||||
t.Fatal("SendWith(unknown) should error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSendBy(t *testing.T) {
|
||||
m := mailx.NewManager()
|
||||
mock := newMockSender("temp")
|
||||
|
||||
msg := mailx.NewMessage().To("x@y.com").Subject("s").Build()
|
||||
if err := m.SendBy(context.Background(), mock, msg); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if mock.count() != 1 {
|
||||
t.Fatalf("count = %d, want 1", mock.count())
|
||||
}
|
||||
|
||||
if err := m.SendBy(context.Background(), nil, msg); err == nil {
|
||||
t.Fatal("SendBy(nil) should error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSendValidation(t *testing.T) {
|
||||
m := mailx.NewManager()
|
||||
_ = m.Register(newMockSender("mock"))
|
||||
|
||||
if err := m.Send(context.Background(), nil); err == nil {
|
||||
t.Fatal("Send(nil) should error")
|
||||
}
|
||||
if err := m.Send(context.Background(), &mailx.Message{Subject: "s"}); err == nil {
|
||||
t.Fatal("message without recipients should error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSendErrorPropagated(t *testing.T) {
|
||||
m := mailx.NewManager()
|
||||
mock := newMockSender("mock")
|
||||
mock.err = errors.New("boom")
|
||||
_ = m.Register(mock)
|
||||
|
||||
msg := mailx.NewMessage().To("a@b.com").Subject("s").Build()
|
||||
err := m.Send(context.Background(), msg)
|
||||
if err == nil || !strings.Contains(err.Error(), "boom") {
|
||||
t.Fatalf("err = %v, want containing boom", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSendInjectsLogger(t *testing.T) {
|
||||
m := mailx.NewManager()
|
||||
mock := newMockSender("mock")
|
||||
_ = m.Register(mock)
|
||||
m.SetLogger(recordingLogger{})
|
||||
|
||||
msg := mailx.NewMessage().To("a@b.com").Subject("s").Build()
|
||||
ctx := context.Background()
|
||||
if err := m.Send(ctx, msg); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// 发送时 context 应带有管理器注入的 logger(通道可通过 LoggerFromContext 取到)
|
||||
if _, ok := mock.lastLogger().(recordingLogger); !ok {
|
||||
t.Fatalf("injected logger type = %T, want recordingLogger", mock.lastLogger())
|
||||
}
|
||||
}
|
||||
|
||||
func TestQueryMethods(t *testing.T) {
|
||||
m := mailx.NewManager()
|
||||
_ = m.Register(newMockSender("a"))
|
||||
_ = m.Register(newMockSender("b"))
|
||||
|
||||
if !m.Registered("a") || !m.Registered("b") {
|
||||
t.Error("Registered() should be true for registered senders")
|
||||
}
|
||||
if m.Registered("nope") {
|
||||
t.Error("Registered() should be false for unknown sender")
|
||||
}
|
||||
|
||||
names := m.Names()
|
||||
if len(names) != 2 {
|
||||
t.Fatalf("Names() = %v, want 2", names)
|
||||
}
|
||||
seen := map[string]bool{}
|
||||
for _, n := range names {
|
||||
seen[n] = true
|
||||
}
|
||||
if !seen["a"] || !seen["b"] {
|
||||
t.Errorf("Names() missing a/b: %v", names)
|
||||
}
|
||||
|
||||
if m.Default() != "a" {
|
||||
t.Errorf("Default() = %q, want a", m.Default())
|
||||
}
|
||||
_ = m.SetDefault("b")
|
||||
if m.Default() != "b" {
|
||||
t.Errorf("Default() = %q, want b", m.Default())
|
||||
}
|
||||
}
|
||||
|
||||
func TestErrSentinel(t *testing.T) {
|
||||
m := mailx.NewManager()
|
||||
_ = m.Register(newMockSender("mock"))
|
||||
|
||||
// 空 manager 且未注册任何通道时,查默认通道返回 ErrSenderNotFound
|
||||
if _, err := mailx.NewManager().Sender(""); !errors.Is(err, mailx.ErrSenderNotFound) {
|
||||
t.Errorf("Sender() on empty manager = %v, want ErrSenderNotFound", err)
|
||||
}
|
||||
// 未注册的名称返回 ErrSenderNotFound
|
||||
if _, err := m.Sender("nope"); !errors.Is(err, mailx.ErrSenderNotFound) {
|
||||
t.Errorf("Sender(unknown) = %v, want ErrSenderNotFound", err)
|
||||
}
|
||||
// 消息校验失败返回 ErrInvalidMessage
|
||||
if err := m.Send(context.Background(), &mailx.Message{Subject: "s"}); !errors.Is(err, mailx.ErrInvalidMessage) {
|
||||
t.Errorf("Send(no recipients) = %v, want ErrInvalidMessage", err)
|
||||
}
|
||||
// 非法配置返回 ErrInvalidConfig
|
||||
if err := m.Register(nil); !errors.Is(err, mailx.ErrInvalidConfig) {
|
||||
t.Errorf("Register(nil) = %v, want ErrInvalidConfig", err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestConcurrentRegisterUnregister 验证并发注册/注销不 panic 且不丢数据
|
||||
func TestConcurrentRegisterUnregister(t *testing.T) {
|
||||
m := mailx.NewManager()
|
||||
_ = m.Register(newMockSender("base"))
|
||||
|
||||
ctx := context.Background()
|
||||
const n = 30
|
||||
var wg sync.WaitGroup
|
||||
for i := 0; i < n; i++ {
|
||||
wg.Add(1)
|
||||
go func(i int) {
|
||||
defer wg.Done()
|
||||
name := fmt.Sprintf("sender-%d", i)
|
||||
_ = m.RegisterNamed(name, newMockSender("mock"))
|
||||
msg := mailx.NewMessage().To("a@b.com").Subject("s").Build()
|
||||
_ = m.SendWith(ctx, name, msg)
|
||||
m.Unregister(name)
|
||||
}(i)
|
||||
}
|
||||
wg.Wait()
|
||||
if !m.Registered("base") {
|
||||
t.Error("base sender should still be registered")
|
||||
}
|
||||
}
|
||||
|
||||
func TestConcurrentSetLogger(t *testing.T) {
|
||||
m := mailx.NewManager()
|
||||
_ = m.Register(newMockSender("mock"))
|
||||
|
||||
msg := mailx.NewMessage().To("a@b.com").Subject("s").Build()
|
||||
ctx := context.Background()
|
||||
|
||||
const n = 100
|
||||
var wg sync.WaitGroup
|
||||
for i := 0; i < n; i++ {
|
||||
wg.Add(1)
|
||||
go func(i int) {
|
||||
defer wg.Done()
|
||||
if i%2 == 0 {
|
||||
m.SetLogger(recordingLogger{})
|
||||
} else {
|
||||
_ = m.Send(ctx, msg) // 内部读取 Logger,验证并发读写不 panic/不竞态
|
||||
}
|
||||
}(i)
|
||||
}
|
||||
wg.Wait()
|
||||
// Logger() 应始终返回非 nil 且类型正确
|
||||
if _, ok := m.Logger().(recordingLogger); !ok {
|
||||
t.Fatalf("Logger() type = %T, want recordingLogger", m.Logger())
|
||||
}
|
||||
}
|
||||
|
||||
func TestConcurrentSend(t *testing.T) {
|
||||
m := mailx.NewManager()
|
||||
mock := newMockSender("mock")
|
||||
_ = m.Register(mock)
|
||||
|
||||
msg := mailx.NewMessage().To("a@b.com").Subject("s").Build()
|
||||
ctx := context.Background()
|
||||
|
||||
const n = 50
|
||||
var wg sync.WaitGroup
|
||||
for i := 0; i < n; i++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
if err := m.Send(ctx, msg); err != nil {
|
||||
t.Errorf("Send: %v", err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
if mock.count() != n {
|
||||
t.Fatalf("count = %d, want %d", mock.count(), n)
|
||||
}
|
||||
}
|
||||
|
||||
// TestRegisterNamedSameType 验证同一通道类型可注册多份不同配置的实例
|
||||
func TestRegisterNamedSameType(t *testing.T) {
|
||||
m := mailx.NewManager()
|
||||
|
||||
main := newMockSender("smtp")
|
||||
backup := newMockSender("smtp")
|
||||
|
||||
if err := m.RegisterNamed("smtp-main", main); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := m.RegisterNamed("smtp-backup", backup); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// 两个实例都注册成功
|
||||
if len(m.Names()) != 2 {
|
||||
t.Fatalf("Names() = %v, want 2 instances", m.Names())
|
||||
}
|
||||
// 类型相同但实例不同
|
||||
infos := m.Senders()
|
||||
if len(infos) != 2 {
|
||||
t.Fatalf("Senders() = %v, want 2", infos)
|
||||
}
|
||||
for _, info := range infos {
|
||||
if info.Type != "smtp" {
|
||||
t.Errorf("SenderInfo.Type = %q, want smtp", info.Type)
|
||||
}
|
||||
}
|
||||
|
||||
// 按实例名路由发送到具体配置
|
||||
msg := mailx.NewMessage().To("a@b.com").Subject("s").Build()
|
||||
if err := m.SendWith(context.Background(), "smtp-backup", msg); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if main.count() != 0 || backup.count() != 1 {
|
||||
t.Errorf("main=%d backup=%d, want main=0 backup=1", main.count(), backup.count())
|
||||
}
|
||||
}
|
||||
|
||||
// TestRegisterNamedDuplicateInstance 验证实例名冲突时报错
|
||||
func TestRegisterNamedDuplicateInstance(t *testing.T) {
|
||||
m := mailx.NewManager()
|
||||
_ = m.RegisterNamed("smtp-main", newMockSender("smtp"))
|
||||
err := m.RegisterNamed("smtp-main", newMockSender("smtp"))
|
||||
if err == nil || !strings.Contains(err.Error(), "already registered") {
|
||||
t.Fatalf("err = %v, want already registered", err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestRegisterNamedEmptyName 验证空实例名时报错
|
||||
func TestRegisterNamedEmptyName(t *testing.T) {
|
||||
m := mailx.NewManager()
|
||||
if err := m.RegisterNamed("", newMockSender("smtp")); !errors.Is(err, mailx.ErrInvalidConfig) {
|
||||
t.Fatalf("err = %v, want ErrInvalidConfig", err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestRegisterSameTypeDirect 验证 Register 用通道类型名注册时,同类型只允许一个(默认实例)
|
||||
func TestRegisterSameTypeDirect(t *testing.T) {
|
||||
m := mailx.NewManager()
|
||||
_ = m.Register(newMockSender("smtp"))
|
||||
if err := m.Register(newMockSender("smtp")); err == nil {
|
||||
t.Fatal("Register(same type) should error")
|
||||
}
|
||||
}
|
||||
|
||||
// TestSendersInfo 验证 Senders() 返回实例名与类型
|
||||
func TestSendersInfo(t *testing.T) {
|
||||
m := mailx.NewManager()
|
||||
_ = m.RegisterNamed("smtp-main", newMockSender("smtp"))
|
||||
_ = m.RegisterNamed("aliyun-prod", newMockSender("aliyun"))
|
||||
|
||||
infos := m.Senders()
|
||||
want := map[string]string{"smtp-main": "smtp", "aliyun-prod": "aliyun"}
|
||||
got := map[string]string{}
|
||||
for _, i := range infos {
|
||||
got[i.Name] = i.Type
|
||||
}
|
||||
for k, v := range want {
|
||||
if got[k] != v {
|
||||
t.Errorf("Senders()[%s] = %q, want %q (all: %+v)", k, got[k], v, infos)
|
||||
}
|
||||
}
|
||||
if len(got) != len(want) {
|
||||
t.Errorf("Senders() count = %d, want %d", len(got), len(want))
|
||||
}
|
||||
}
|
||||
|
||||
// TestUnregisterNamedInstance 验证按实例名注销后不再路由
|
||||
func TestUnregisterNamedInstance(t *testing.T) {
|
||||
m := mailx.NewManager()
|
||||
main := newMockSender("smtp")
|
||||
backup := newMockSender("smtp")
|
||||
_ = m.RegisterNamed("smtp-main", main)
|
||||
_ = m.RegisterNamed("smtp-backup", backup)
|
||||
|
||||
m.Unregister("smtp-main")
|
||||
if m.Registered("smtp-main") {
|
||||
t.Error("smtp-main should be unregistered")
|
||||
}
|
||||
if !m.Registered("smtp-backup") {
|
||||
t.Error("smtp-backup should remain registered")
|
||||
}
|
||||
|
||||
// 注销的实例不可再路由
|
||||
msg := mailx.NewMessage().To("a@b.com").Subject("s").Build()
|
||||
if err := m.SendWith(context.Background(), "smtp-main", msg); !errors.Is(err, mailx.ErrSenderNotFound) {
|
||||
t.Errorf("SendWith(unregistered) = %v, want ErrSenderNotFound", err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestUnregisterLastFallback 验证注销默认实例后自动回退到其他实例
|
||||
func TestUnregisterLastFallback(t *testing.T) {
|
||||
m := mailx.NewManager()
|
||||
_ = m.RegisterNamed("a", newMockSender("smtp"))
|
||||
_ = m.RegisterNamed("b", newMockSender("aliyun"))
|
||||
|
||||
if m.Default() != "a" {
|
||||
t.Fatalf("Default() = %q, want a", m.Default())
|
||||
}
|
||||
m.Unregister("a")
|
||||
if m.Default() != "b" {
|
||||
t.Errorf("Default() after unregister = %q, want b", m.Default())
|
||||
}
|
||||
}
|
||||
|
||||
// TestSetDefaultNamedInstance 验证 SetDefault 支持实例名
|
||||
func TestSetDefaultNamedInstance(t *testing.T) {
|
||||
m := mailx.NewManager()
|
||||
_ = m.RegisterNamed("smtp-main", newMockSender("smtp"))
|
||||
_ = m.RegisterNamed("smtp-backup", newMockSender("smtp"))
|
||||
|
||||
if err := m.SetDefault("smtp-backup"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
s, _ := m.Sender("")
|
||||
if s.Name() != "smtp" {
|
||||
t.Errorf("default sender type = %q, want smtp", s.Name())
|
||||
}
|
||||
// 确认默认实例是 backup(发送到 backup)
|
||||
backup, _ := m.Sender("smtp-backup")
|
||||
if _, ok := backup.(*mockSender); !ok || m.Default() != "smtp-backup" {
|
||||
t.Errorf("Default() = %q, want smtp-backup", m.Default())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user