package smtp import ( "bytes" "encoding/base64" "io" "mime" "mime/multipart" "net/mail" "strings" "testing" ) func TestBuildMessage_plainOnly(t *testing.T) { msg := buildMessage(&SendRequest{ From: "alice@example.com", To: []string{"bob@example.com"}, Subject: "Hello", BodyText: "Plain body", }) if strings.Contains(msg, "multipart/") { t.Fatal("plain-only message must not be multipart") } if !strings.Contains(msg, "Content-Type: text/plain; charset=UTF-8") { t.Fatal("missing text/plain content type") } if !strings.Contains(msg, "Plain body") { t.Fatal("missing body text") } } func TestBuildMessage_alternativeOnly(t *testing.T) { msg := buildMessage(&SendRequest{ From: "alice@example.com", To: []string{"bob@example.com"}, Subject: "Hello", BodyText: "Plain body", BodyHTML: "
HTML body
", }) if !strings.Contains(msg, "Content-Type: multipart/alternative") { t.Fatal("missing multipart/alternative top-level content type") } if strings.Contains(msg, "multipart/mixed") { t.Fatal("alternative-only message must not be multipart/mixed") } if !strings.Contains(msg, "Content-Type: text/html; charset=UTF-8") { t.Fatal("missing text/html part") } } func TestBuildMessage_mixedWithPlainAttachment(t *testing.T) { payload := []byte("file-bytes") msg := buildMessage(&SendRequest{ From: "alice@example.com", To: []string{"bob@example.com"}, Subject: "With file", BodyText: "See attached", Attachments: []SendAttachment{{ Filename: "doc.pdf", ContentType: "application/pdf", Data: payload, }}, }) parsed, err := mail.ReadMessage(strings.NewReader(msg)) if err != nil { t.Fatalf("ReadMessage: %v", err) } mediaType, params, err := mime.ParseMediaType(parsed.Header.Get("Content-Type")) if err != nil { t.Fatalf("ParseMediaType: %v", err) } if mediaType != "multipart/mixed" { t.Fatalf("top-level content type = %q, want multipart/mixed", mediaType) } parts := readAllParts(t, parsed.Body, params["boundary"]) if len(parts) != 2 { t.Fatalf("part count = %d, want 2", len(parts)) } if got := parts[0].Header.Get("Content-Type"); got != "text/plain; charset=UTF-8" { t.Fatalf("body part content type = %q", got) } if string(parts[0].Body) != "See attached" { t.Fatalf("body part = %q", parts[0].Body) } attType, attParams, err := mime.ParseMediaType(parts[1].Header.Get("Content-Type")) if err != nil { t.Fatalf("ParseMediaType attachment: %v", err) } if attType != "application/pdf" { t.Fatalf("attachment content type = %q", attType) } if attParams["name"] != "doc.pdf" { t.Fatalf("attachment name = %q", attParams["name"]) } if parts[1].Header.Get("Content-Transfer-Encoding") != "base64" { t.Fatal("attachment missing base64 transfer encoding") } disp, dispParams, err := mime.ParseMediaType(parts[1].Header.Get("Content-Disposition")) if err != nil { t.Fatalf("ParseMediaType disposition: %v", err) } if disp != "attachment" { t.Fatalf("disposition = %q, want attachment", disp) } if dispParams["filename"] != "doc.pdf" { t.Fatalf("filename = %q", dispParams["filename"]) } if !bytes.Equal(decodeBase64Part(t, parts[1].Body), payload) { t.Fatal("attachment payload mismatch") } } func TestBuildMessage_mixedWithAlternativeAndAttachment(t *testing.T) { msg := buildMessage(&SendRequest{ From: "alice@example.com", To: []string{"bob@example.com"}, Subject: "Rich mail", BodyText: "Plain", BodyHTML: "HTML", Attachments: []SendAttachment{{ Filename: "note.txt", ContentType: "text/plain", Data: []byte("attachment"), }}, }) parsed, err := mail.ReadMessage(strings.NewReader(msg)) if err != nil { t.Fatalf("ReadMessage: %v", err) } mediaType, params, err := mime.ParseMediaType(parsed.Header.Get("Content-Type")) if err != nil { t.Fatalf("ParseMediaType: %v", err) } if mediaType != "multipart/mixed" { t.Fatalf("top-level content type = %q, want multipart/mixed", mediaType) } parts := readAllParts(t, parsed.Body, params["boundary"]) if len(parts) != 2 { t.Fatalf("part count = %d, want 2", len(parts)) } altType, altParams, err := mime.ParseMediaType(parts[0].Header.Get("Content-Type")) if err != nil { t.Fatalf("ParseMediaType alternative: %v", err) } if altType != "multipart/alternative" { t.Fatalf("first part type = %q, want multipart/alternative", altType) } altParts := readAllParts(t, bytes.NewReader(parts[0].Body), altParams["boundary"]) if len(altParts) != 2 { t.Fatalf("alternative part count = %d, want 2", len(altParts)) } if string(altParts[0].Body) != "Plain" { t.Fatalf("plain part = %q", altParts[0].Body) } if string(altParts[1].Body) != "HTML" { t.Fatalf("html part = %q", altParts[1].Body) } disp, _, err := mime.ParseMediaType(parts[1].Header.Get("Content-Disposition")) if err != nil { t.Fatalf("ParseMediaType disposition: %v", err) } if disp != "attachment" { t.Fatalf("attachment disposition = %q", disp) } } func TestBuildMessage_inlineAttachmentHeaders(t *testing.T) { msg := buildMessage(&SendRequest{ From: "alice@example.com", To: []string{"bob@example.com"}, Subject: "Inline", BodyHTML: `