275 lines
6.9 KiB
Go
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)
|
|
}
|
|
}
|