package mailx_test import ( "encoding/json" "testing" mailx "code.yun.ink/pkg/mailx" ) func TestAddressHelpers(t *testing.T) { // 纯地址 if !mailx.IsValidAddress("a@example.com") { t.Error("IsValidAddress(a@example.com) = false") } // 显示名 if !mailx.IsValidAddress(`"张三" `) { t.Error("IsValidAddress(with display name) = false") } // 非法 if mailx.IsValidAddress("not-an-email") { t.Error("IsValidAddress(not-an-email) = true") } if mailx.IsValidAddress("") { t.Error("IsValidAddress(empty) = true") } // ExtractEmail 提取纯地址 addr, err := mailx.ExtractEmail(`"张三" `) if err != nil { t.Fatal(err) } if addr != "a@example.com" { t.Errorf("ExtractEmail = %q, want a@example.com", addr) } // ExtractEmail 纯地址原样返回 addr, err = mailx.ExtractEmail("a@example.com") if err != nil || addr != "a@example.com" { t.Errorf("ExtractEmail(plain) = %q, %v", addr, err) } } func TestAddressList(t *testing.T) { addrs, err := mailx.AddressList(`"A" , b@e.com; c@e.com`) if err != nil { t.Fatal(err) } if len(addrs) != 3 { t.Fatalf("AddressList len = %d, want 3", len(addrs)) } if addrs[0].Address != "a@e.com" || addrs[1].Address != "b@e.com" || addrs[2].Address != "c@e.com" { t.Errorf("unexpected addresses: %+v", addrs) } } func TestFromBytes(t *testing.T) { m := mailx.NewMessage(). From("a@e.com"). To("b@e.com"). Subject("s"). Text("t"). HTML("

h

"). Build() data, err := json.Marshal(m) if err != nil { t.Fatal(err) } got, err := mailx.FromBytes(data) if err != nil { t.Fatal(err) } if got.From != "a@e.com" || got.Subject != "s" || got.Body != "

h

" { t.Errorf("FromBytes mismatch: %+v", got) } } func TestFromBytesInvalid(t *testing.T) { if _, err := mailx.FromBytes([]byte("not json")); err == nil { t.Fatal("FromBytes(invalid) should error") } } func TestFromMap(t *testing.T) { m := map[string]any{ "from": "a@e.com", "to": "b@e.com, c@e.com", "cc": []string{"d@e.com"}, "subject": "hello", "text": "plain", "html": "hi", "replyto": "r@e.com", } msg, err := mailx.FromMap(m) if err != nil { t.Fatal(err) } if msg.From != "a@e.com" || msg.Subject != "hello" { t.Errorf("FromMap fields: %+v", msg) } if len(msg.To) != 2 || msg.To[0] != "b@e.com" || msg.To[1] != "c@e.com" { t.Errorf("FromMap To: %v", msg.To) } if len(msg.Cc) != 1 || msg.Cc[0] != "d@e.com" { t.Errorf("FromMap Cc: %v", msg.Cc) } if msg.TextBody != "plain" || msg.Body != "hi" { t.Errorf("FromMap bodies: text=%q html=%q", msg.TextBody, msg.Body) } if msg.ReplyTo != "r@e.com" { t.Errorf("FromMap ReplyTo: %q", msg.ReplyTo) } } func TestFromMapNil(t *testing.T) { if _, err := mailx.FromMap(nil); err == nil { t.Fatal("FromMap(nil) should error") } } func TestFromMapAttachments(t *testing.T) { m := map[string]any{ "to": "a@e.com", "attachments": []any{ map[string]any{"name": "x.txt", "path": "tmp/x.txt"}, }, } msg, err := mailx.FromMap(m) if err != nil { t.Fatal(err) } if len(msg.Attachments) != 1 || msg.Attachments[0].Name != "x.txt" { t.Errorf("FromMap attachments: %+v", msg.Attachments) } } // TestFromMapAttachmentNoName 验证附件 map 缺 name 键时不 panic,且 name 从 path 推导 func TestFromMapAttachmentNoName(t *testing.T) { m := map[string]any{ "to": "a@e.com", "attachments": []any{ map[string]any{"path": "tmp/report.pdf"}, // 无 name }, } msg, err := mailx.FromMap(m) if err != nil { t.Fatal(err) } if len(msg.Attachments) != 1 { t.Fatalf("attachments = %+v, want 1", msg.Attachments) } if msg.Attachments[0].Name != "report.pdf" { t.Errorf("Name = %q, want derived report.pdf", msg.Attachments[0].Name) } } // TestFromMapAttachmentTypeError 验证 attachments 类型非法时返回错误而非 panic func TestFromMapAttachmentTypeError(t *testing.T) { m := map[string]any{ "to": "a@e.com", "attachments": "not-a-list", // 非法类型 } if _, err := mailx.FromMap(m); err == nil { t.Fatal("FromMap with invalid attachments type should error") } } func TestIsHTML(t *testing.T) { cases := []struct { in string want bool }{ {"

hi

", true}, {"

hello

", true}, {"
x
", true}, {"a < b > c", false}, // 普通比较,不应误判 {"plain text", false}, // 纯文本 {"<123>", false}, // 无有效标签名 {"", false}, // 注释 {"", false}, } for _, c := range cases { if got := mailx.IsHTML(c.in); got != c.want { t.Errorf("IsHTML(%q) = %v, want %v", c.in, got, c.want) } } } // TestFromMapAttachmentPaths 验证 attachments 为 []string(路径列表) func TestFromMapAttachmentPaths(t *testing.T) { m := map[string]any{ "to": "a@e.com", "attachments": []string{"tmp/a.txt", "tmp/b.txt"}, } msg, err := mailx.FromMap(m) if err != nil { t.Fatal(err) } if len(msg.Attachments) != 2 { t.Fatalf("attachments = %+v, want 2", msg.Attachments) } if msg.Attachments[0].Name != "a.txt" || msg.Attachments[0].Path != "tmp/a.txt" { t.Errorf("attachments[0] = %+v", msg.Attachments[0]) } } // TestFromMapToListVariants 验证收件人多种输入形式 func TestFromMapToListVariants(t *testing.T) { // []any 形式 m := map[string]any{"to": []any{"a@e.com", "b@e.com"}} msg, err := mailx.FromMap(m) if err != nil { t.Fatal(err) } if len(msg.To) != 2 || msg.To[0] != "a@e.com" { t.Errorf("To = %v", msg.To) } // 非法类型 m = map[string]any{"to": 123} if _, err := mailx.FromMap(m); err == nil { t.Fatal("To with invalid type should error") } } // TestFromMapBodyFallback 验证 html 优先于 body func TestFromMapBodyFallback(t *testing.T) { m := map[string]any{ "to": "a@e.com", "body": "plain body", "html": "html body", } msg, err := mailx.FromMap(m) if err != nil { t.Fatal(err) } if msg.Body != "html body" { t.Errorf("Body = %q, want html preferred", msg.Body) } } // TestFromBytesAllFields 验证 JSON 反序列化完整字段 func TestFromBytesAllFields(t *testing.T) { data := []byte(`{ "from": "a@e.com", "to": ["b@e.com"], "cc": ["c@e.com"], "bcc": ["d@e.com"], "subject": "s", "textbody": "t", "body": "

h

", "replyto": "r@e.com", "attachments": [{"name": "a.txt", "data": "aGk="}], "inline": [{"cid": "cid1", "name": "i.png", "data": "aGk="}] }`) msg, err := mailx.FromBytes(data) if err != nil { t.Fatal(err) } if msg.From != "a@e.com" || len(msg.To) != 1 || msg.To[0] != "b@e.com" { t.Errorf("fields: %+v", msg) } if len(msg.Cc) != 1 || len(msg.Bcc) != 1 { t.Errorf("cc/bcc: %+v", msg) } if msg.TextBody != "t" || msg.Body != "

h

" || msg.ReplyTo != "r@e.com" { t.Errorf("body fields: %+v", msg) } if len(msg.Attachments) != 1 || msg.Attachments[0].Name != "a.txt" { t.Errorf("attachments: %+v", msg.Attachments) } if len(msg.Inline) != 1 || msg.Inline[0].CID != "cid1" { t.Errorf("inline: %+v", msg.Inline) } }