更新
This commit is contained in:
@@ -0,0 +1,183 @@
|
||||
package aws
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
mailx "code.yun.ink/pkg/mailx"
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/aws/request"
|
||||
"github.com/aws/aws-sdk-go/service/ses"
|
||||
"github.com/aws/aws-sdk-go/service/ses/sesiface"
|
||||
)
|
||||
|
||||
// mockSES 通过嵌入 sesiface.SESAPI 实现 mock,仅覆盖 SendEmailWithContext
|
||||
type mockSES struct {
|
||||
sesiface.SESAPI
|
||||
sendErr error
|
||||
lastIn *ses.SendEmailInput
|
||||
lastCtx context.Context
|
||||
}
|
||||
|
||||
func (m *mockSES) SendEmailWithContext(ctx aws.Context, input *ses.SendEmailInput, _ ...request.Option) (*ses.SendEmailOutput, error) {
|
||||
m.lastCtx = ctx
|
||||
m.lastIn = input
|
||||
if m.sendErr != nil {
|
||||
return nil, m.sendErr
|
||||
}
|
||||
return &ses.SendEmailOutput{MessageId: aws.String("mock-id")}, nil
|
||||
}
|
||||
|
||||
// TestSendSuccess 验证完整发送流程与输入构造
|
||||
func TestSendSuccess(t *testing.T) {
|
||||
mock := &mockSES{}
|
||||
a := &Aws{cfg: Config{Sender: "noreply@example.com"}, svc: mock}
|
||||
|
||||
msg := mailx.NewMessage().
|
||||
To("a@example.com", "b@example.com").
|
||||
Cc("cc@example.com").
|
||||
Bcc("bcc@example.com").
|
||||
Subject("hi").
|
||||
Text("plain").
|
||||
HTML("<b>html</b>").
|
||||
ReplyTo("reply@example.com").
|
||||
Build()
|
||||
|
||||
if err := a.Send(context.Background(), msg); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
in := mock.lastIn
|
||||
if in == nil {
|
||||
t.Fatal("no input captured")
|
||||
}
|
||||
if *in.Source != "noreply@example.com" {
|
||||
t.Errorf("Source = %q", *in.Source)
|
||||
}
|
||||
if len(in.Destination.ToAddresses) != 2 || len(in.Destination.CcAddresses) != 1 || len(in.Destination.BccAddresses) != 1 {
|
||||
t.Errorf("dest addresses wrong: %+v", in.Destination)
|
||||
}
|
||||
if len(in.ReplyToAddresses) != 1 || *in.ReplyToAddresses[0] != "reply@example.com" {
|
||||
t.Errorf("ReplyTo = %+v", in.ReplyToAddresses)
|
||||
}
|
||||
if in.Message.Body.Html == nil || in.Message.Body.Text == nil {
|
||||
t.Errorf("body should have both html and text: %+v", in.Message.Body)
|
||||
}
|
||||
if *in.Message.Subject.Charset != "UTF-8" {
|
||||
t.Errorf("subject charset = %q", *in.Message.Subject.Charset)
|
||||
}
|
||||
}
|
||||
|
||||
// TestSendFromFallback 验证 msg.From 为空时回退 cfg.Sender
|
||||
func TestSendFromFallback(t *testing.T) {
|
||||
mock := &mockSES{}
|
||||
a := &Aws{cfg: Config{Sender: "cfg-sender@example.com"}, svc: mock}
|
||||
|
||||
msg := mailx.NewMessage().To("a@example.com").Subject("s").Build()
|
||||
if err := a.Send(context.Background(), msg); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if *mock.lastIn.Source != "cfg-sender@example.com" {
|
||||
t.Errorf("Source = %q, want cfg-sender", *mock.lastIn.Source)
|
||||
}
|
||||
}
|
||||
|
||||
// TestSendFail 验证发送失败包装为 ErrSendFailed
|
||||
func TestSendFail(t *testing.T) {
|
||||
mock := &mockSES{sendErr: errors.New("aws down")}
|
||||
a := &Aws{cfg: Config{Sender: "s@example.com"}, svc: mock}
|
||||
|
||||
msg := mailx.NewMessage().To("a@example.com").Subject("s").Build()
|
||||
err := a.Send(context.Background(), msg)
|
||||
if err == nil || !errors.Is(err, mailx.ErrSendFailed) {
|
||||
t.Fatalf("err = %v, want ErrSendFailed", err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestSendNoSender 验证缺少 Sender 时报错且不调用 SDK
|
||||
func TestSendNoSender(t *testing.T) {
|
||||
mock := &mockSES{}
|
||||
a := &Aws{cfg: Config{}, svc: mock}
|
||||
|
||||
msg := mailx.NewMessage().To("a@example.com").Subject("s").Build()
|
||||
err := a.Send(context.Background(), msg)
|
||||
if err == nil || !errors.Is(err, mailx.ErrInvalidConfig) {
|
||||
t.Fatalf("err = %v, want ErrInvalidConfig", err)
|
||||
}
|
||||
if mock.lastIn != nil {
|
||||
t.Error("SDK should not be called")
|
||||
}
|
||||
}
|
||||
|
||||
// TestSendInlineError 验证内嵌图片返回明确错误
|
||||
func TestSendInlineError(t *testing.T) {
|
||||
mock := &mockSES{}
|
||||
a := &Aws{cfg: Config{Sender: "s@example.com"}, svc: mock}
|
||||
|
||||
msg := mailx.NewMessage().To("a@example.com").Subject("s").
|
||||
InlineImageBytes("cid1", "a.png", []byte{1}).
|
||||
Build()
|
||||
err := a.Send(context.Background(), msg)
|
||||
if err == nil || !errors.Is(err, mailx.ErrInvalidConfig) {
|
||||
t.Fatalf("err = %v, want ErrInvalidConfig", err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestAwsBody 验证 awsBody 各分支
|
||||
func TestAwsBody(t *testing.T) {
|
||||
// 仅 HTML
|
||||
b := awsBody(&mailx.Message{Body: "<b>hi</b>"})
|
||||
if b.Html == nil || b.Text != nil {
|
||||
t.Errorf("html only: %+v", b)
|
||||
}
|
||||
// 仅 Text
|
||||
b = awsBody(&mailx.Message{TextBody: "plain"})
|
||||
if b.Html != nil || b.Text == nil {
|
||||
t.Errorf("text only: %+v", b)
|
||||
}
|
||||
// 两者都有
|
||||
b = awsBody(&mailx.Message{Body: "<b>hi</b>", TextBody: "plain"})
|
||||
if b.Html == nil || b.Text == nil {
|
||||
t.Errorf("both: %+v", b)
|
||||
}
|
||||
// 都没有
|
||||
b = awsBody(&mailx.Message{})
|
||||
if b.Html != nil || b.Text != nil {
|
||||
t.Errorf("neither: %+v", b)
|
||||
}
|
||||
}
|
||||
|
||||
// TestWithTimeout 验证超时叠加逻辑
|
||||
func TestWithTimeout(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
// 未配置 Timeout:原样返回
|
||||
a := &Aws{cfg: Config{}}
|
||||
c, cancel := a.withTimeout(ctx)
|
||||
if c != ctx {
|
||||
t.Error("no timeout should return original ctx")
|
||||
}
|
||||
cancel()
|
||||
|
||||
// 配置了 Timeout:返回带 deadline 的 ctx
|
||||
a = &Aws{cfg: Config{Timeout: time.Second}}
|
||||
c, cancel = a.withTimeout(ctx)
|
||||
if c == ctx {
|
||||
t.Error("with timeout should return new ctx")
|
||||
}
|
||||
if _, ok := c.Deadline(); !ok {
|
||||
t.Error("new ctx should have deadline")
|
||||
}
|
||||
cancel()
|
||||
|
||||
// ctx 已有更早 deadline:保持原 ctx
|
||||
early, ecancel := context.WithTimeout(ctx, time.Millisecond)
|
||||
defer ecancel()
|
||||
c, cancel = a.withTimeout(early)
|
||||
if c != early {
|
||||
t.Error("earlier deadline should keep original ctx")
|
||||
}
|
||||
cancel()
|
||||
}
|
||||
Reference in New Issue
Block a user