package smtp import ( "bytes" "os" "path/filepath" "strings" "testing" mailx "code.yun.ink/pkg/mailx" ) func TestBuildMIME(t *testing.T) { s := New(Config{Host: "h", Port: 25, From: "default@example.com", ReplyTo: "dr@example.com"}) msg := mailx.NewMessage(). From("sender@example.com"). To("to@example.com"). Cc("cc@example.com"). Subject("主题"). Body("
hi
"). ReplyTo("msg-reply@example.com"). AttachBytes("a.txt", []byte("content")). Build() data, err := s.buildMIME(msg, "sender@example.com") if err != nil { t.Fatal(err) } out := string(data) for _, want := range []string{ "From: sender@example.com", "To: to@example.com", "Cc: cc@example.com", "Reply-To: msg-reply@example.com", "MIME-Version: 1.0", "multipart/mixed", "text/html; charset=UTF-8", "attachment; filename", "Y29udGVudA==", // base64("content") } { if !strings.Contains(out, want) { t.Errorf("MIME missing %q, got:\n%s", want, out) } } } func TestBuildMIMEWithoutBody(t *testing.T) { s := New(Config{}) msg := mailx.NewMessage(). To("to@example.com"). Subject("s"). AttachBytes("a.txt", []byte("x")). Build() data, err := s.buildMIME(msg, "f@example.com") if err != nil { t.Fatal(err) } out := string(data) if strings.Contains(out, "text/html") { t.Errorf("should not contain html part, got:\n%s", out) } if !strings.Contains(out, "multipart/mixed") { t.Errorf("should contain multipart/mixed for attachment, got:\n%s", out) } } func TestBuildMIMEFromFallback(t *testing.T) { // Message.From 为空时回退到 Config.From s := New(Config{From: "cfg-from@example.com"}) msg := mailx.NewMessage().To("t@e.com").Subject("s").Build() data, err := s.buildMIME(msg, "cfg-from@example.com") if err != nil { t.Fatal(err) } if !strings.Contains(string(data), "From: cfg-from@example.com") { t.Fatalf("From header missing, got:\n%s", data) } } func TestReadAttachment(t *testing.T) { s := New(Config{}) // 按路径 dir := t.TempDir() p := filepath.Join(dir, "x.txt") if err := os.WriteFile(p, []byte("abc"), 0o600); err != nil { t.Fatal(err) } name, data, err := s.readAttachment(mailx.Attachment{Path: p}) if err != nil { t.Fatal(err) } if name != "x.txt" || string(data) != "abc" { t.Errorf("name=%q data=%q", name, data) } // 内存字节 name, data, err = s.readAttachment(mailx.Attachment{Name: "m.bin", Data: []byte{1, 2}}) if err != nil { t.Fatal(err) } if name != "m.bin" || !bytes.Equal(data, []byte{1, 2}) { t.Errorf("name=%q data=%v", name, data) } // 空附件报错 if _, _, err := s.readAttachment(mailx.Attachment{}); err == nil { t.Fatal("empty attachment should error") } // 路径不存在报错 if _, _, err := s.readAttachment(mailx.Attachment{Path: filepath.Join(dir, "nope.txt")}); err == nil { t.Fatal("missing file should error") } } func TestEncodeHeader(t *testing.T) { if got := encodeHeader("plain"); got != "plain" { t.Errorf("encodeHeader(plain) = %q", got) } got := encodeHeader("主题") if !strings.HasPrefix(got, "=?UTF-8?q?") || !strings.HasSuffix(got, "?=") { t.Errorf("encodeHeader(主题) = %q, want RFC 2047 encoded", got) } } func TestEffectiveEncryption(t *testing.T) { cases := []struct { name string cfg Config want Encryption }{ {"default 587 -> tls", Config{Host: "h", Port: 587}, EncryptionTLS}, {"default 465 -> ssl", Config{Host: "h", Port: 465}, EncryptionSSL}, {"default 25 -> tls", Config{Host: "h", Port: 25}, EncryptionTLS}, {"explicit ssl", Config{Host: "h", Port: 587, Encryption: EncryptionSSL}, EncryptionSSL}, {"explicit tls", Config{Host: "h", Port: 465, Encryption: EncryptionTLS}, EncryptionTLS}, {"explicit none", Config{Host: "h", Port: 465, Encryption: EncryptionNone}, EncryptionNone}, } for _, c := range cases { t.Run(c.name, func(t *testing.T) { if got := New(c.cfg).effectiveEncryption(); got != c.want { t.Errorf("effectiveEncryption() = %q, want %q", got, c.want) } }) } } func TestBuildMIMEInlineImage(t *testing.T) { s := New(Config{}) msg := mailx.NewMessage(). From(`"张三"hi
html正文
"). Build() data, err := s.buildMIME(msg, "f@e.com") if err != nil { t.Fatal(err) } out := string(data) if !strings.Contains(out, "multipart/alternative") { t.Errorf("should use multipart/alternative, got:\n%s", out) } if !strings.Contains(out, "text/plain; charset=UTF-8") { t.Errorf("missing text/plain part, got:\n%s", out) } if !strings.Contains(out, "text/html; charset=UTF-8") { t.Errorf("missing text/html part, got:\n%s", out) } }