Files
mailx/message_util_test.go
2026-08-15 01:38:05 +08:00

275 lines
6.9 KiB
Go

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(`"张三" <a@example.com>`) {
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(`"张三" <a@example.com>`)
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" <a@e.com>, 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("<p>h</p>").
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 != "<p>h</p>" {
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": "<b>hi</b>",
"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 != "<b>hi</b>" {
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
}{
{"<p>hi</p>", true},
{"<h1>hello</h1>", true},
{"<div class=\"a\">x</div>", true},
{"a < b > c", false}, // 普通比较,不应误判
{"plain text", false}, // 纯文本
{"<123>", false}, // 无有效标签名
{"<!-- comment -->", 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": "<b>html body</b>",
}
msg, err := mailx.FromMap(m)
if err != nil {
t.Fatal(err)
}
if msg.Body != "<b>html body</b>" {
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": "<p>h</p>",
"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 != "<p>h</p>" || 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)
}
}