package mail import ( "os" "path/filepath" "testing" ) func loadFixture(t *testing.T, name string) []byte { t.Helper() b, err := os.ReadFile(filepath.Join("testdata", name)) if err != nil { t.Fatalf("read fixture: %v", err) } return b } func TestParseMessage(t *testing.T) { raw := loadFixture(t, "with_attachment.eml") m, err := ParseMessage(42, raw) if err != nil { t.Fatalf("ParseMessage: %v", err) } if m.Header.UID != 42 { t.Fatalf("uid: %d", m.Header.UID) } if m.Header.Subject != "Your Invoice #5" { t.Fatalf("subject: %q", m.Header.Subject) } if m.Header.From != `"Bob" ` && m.Header.From != "Bob " { t.Fatalf("from: %q", m.Header.From) } if m.Header.MessageID != "abc123@trusted.com" && m.Header.MessageID != "" { t.Fatalf("message-id: %q", m.Header.MessageID) } if want := "Hello, here is your invoice."; !contains(m.BodyText, want) { t.Fatalf("body=%q want contains %q", m.BodyText, want) } if !m.Header.HasAttachments { t.Fatal("HasAttachments should be true") } if len(m.Attachments) != 1 || m.Attachments[0].Name != "invoice.txt" { t.Fatalf("attachments: %+v", m.Attachments) } if !contains(string(m.Attachments[0].Content), "LINE-ITEM 1") { t.Fatalf("attachment content: %q", m.Attachments[0].Content) } } func TestParseHeaderOnly(t *testing.T) { raw := loadFixture(t, "with_attachment.eml") h, err := ParseHeaderOnly(7, raw) if err != nil { t.Fatalf("ParseHeaderOnly: %v", err) } if h.Subject != "Your Invoice #5" || !h.HasAttachments { t.Fatalf("header: %+v", h) } } func TestParseHeaderBytes(t *testing.T) { // Header-only bytes (no body), as returned by an IMAP BODY[HEADER] fetch, // including an RFC2047-encoded subject to confirm decoding is preserved. raw := []byte("From: \"Bob\" \r\n" + "To: me@example.com\r\n" + "Subject: =?UTF-8?Q?Caf=C3=A9=20Invoice?=\r\n" + "Date: Sat, 20 Jun 2026 10:00:00 +0000\r\n" + "Message-ID: \r\n" + "\r\n") h, err := ParseHeaderBytes(9, raw) if err != nil { t.Fatalf("ParseHeaderBytes: %v", err) } if h.UID != 9 { t.Fatalf("uid: %d", h.UID) } if h.Subject != "Café Invoice" { t.Fatalf("subject not RFC2047-decoded: %q", h.Subject) } if h.From != `"Bob" ` { t.Fatalf("from: %q", h.From) } if h.MessageID != "abc123@trusted.com" && h.MessageID != "" { t.Fatalf("message-id: %q", h.MessageID) } // HasAttachments is left false — the IMAP layer sets it from BODYSTRUCTURE. if h.HasAttachments { t.Fatal("ParseHeaderBytes must not set HasAttachments") } } func contains(s, sub string) bool { return len(s) >= len(sub) && (func() bool { for i := 0; i+len(sub) <= len(s); i++ { if s[i:i+len(sub)] == sub { return true } } return false }()) }