6a99e5bb6e
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
148 lines
4.2 KiB
Go
148 lines
4.2 KiB
Go
package mail
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestBuildMIMERoundTrip(t *testing.T) {
|
|
msg := OutgoingMessage{
|
|
From: "emcli@stevecliff.com",
|
|
To: []string{"me@stevecliff.com"},
|
|
Cc: []string{"cc@stevecliff.com"},
|
|
Subject: "hello from emcli",
|
|
BodyText: "this is the body\nwith two lines",
|
|
Date: time.Date(2026, 6, 22, 12, 0, 0, 0, time.UTC),
|
|
InReplyTo: "abc123@origin.example",
|
|
References: []string{"root@origin.example", "abc123@origin.example"},
|
|
Attachments: []Attachment{
|
|
{Name: "note.txt", MIME: "text/plain", Content: []byte("attached bytes")},
|
|
},
|
|
}
|
|
|
|
raw, err := BuildMIME(msg)
|
|
if err != nil {
|
|
t.Fatalf("BuildMIME: %v", err)
|
|
}
|
|
|
|
// Headers present in the raw bytes.
|
|
rawStr := string(raw)
|
|
for _, want := range []string{
|
|
"From:", "emcli@stevecliff.com",
|
|
"To:", "me@stevecliff.com",
|
|
"Cc:", "cc@stevecliff.com",
|
|
"Subject:", "hello from emcli",
|
|
"In-Reply-To:", "abc123@origin.example",
|
|
"References:", "root@origin.example",
|
|
} {
|
|
if !strings.Contains(rawStr, want) {
|
|
t.Fatalf("MIME missing %q in:\n%s", want, rawStr)
|
|
}
|
|
}
|
|
|
|
// Round-trips back through the parser: body and attachment survive.
|
|
parsed, err := ParseMessage(0, raw)
|
|
if err != nil {
|
|
t.Fatalf("ParseMessage: %v", err)
|
|
}
|
|
gotBody := strings.ReplaceAll(strings.TrimSpace(parsed.BodyText), "\r\n", "\n")
|
|
if gotBody != "this is the body\nwith two lines" {
|
|
t.Fatalf("body not preserved: %q", parsed.BodyText)
|
|
}
|
|
if len(parsed.Attachments) != 1 {
|
|
t.Fatalf("want 1 attachment, got %d", len(parsed.Attachments))
|
|
}
|
|
if parsed.Attachments[0].Name != "note.txt" || string(parsed.Attachments[0].Content) != "attached bytes" {
|
|
t.Fatalf("attachment not preserved: %+v", parsed.Attachments[0])
|
|
}
|
|
}
|
|
|
|
func TestBuildMIMENoAttachments(t *testing.T) {
|
|
msg := OutgoingMessage{
|
|
From: "emcli@stevecliff.com",
|
|
To: []string{"me@stevecliff.com"},
|
|
Subject: "plain",
|
|
BodyText: "just text",
|
|
Date: time.Date(2026, 6, 22, 12, 0, 0, 0, time.UTC),
|
|
}
|
|
raw, err := BuildMIME(msg)
|
|
if err != nil {
|
|
t.Fatalf("BuildMIME: %v", err)
|
|
}
|
|
parsed, err := ParseMessage(0, raw)
|
|
if err != nil {
|
|
t.Fatalf("ParseMessage: %v", err)
|
|
}
|
|
if strings.TrimSpace(parsed.BodyText) != "just text" {
|
|
t.Fatalf("body not preserved: %q", parsed.BodyText)
|
|
}
|
|
if len(parsed.Attachments) != 0 {
|
|
t.Fatalf("want 0 attachments, got %d", len(parsed.Attachments))
|
|
}
|
|
}
|
|
|
|
func TestRecipientsCombinesAllFields(t *testing.T) {
|
|
msg := OutgoingMessage{
|
|
To: []string{"a@x.com"},
|
|
Cc: []string{"b@x.com"},
|
|
Bcc: []string{"c@x.com"},
|
|
}
|
|
got := msg.Recipients()
|
|
want := []string{"a@x.com", "b@x.com", "c@x.com"}
|
|
if len(got) != len(want) {
|
|
t.Fatalf("Recipients()=%v want %v", got, want)
|
|
}
|
|
for i := range want {
|
|
if got[i] != want[i] {
|
|
t.Fatalf("Recipients()=%v want %v", got, want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestEnvelopeFromStripsDisplayName(t *testing.T) {
|
|
cases := map[string]string{
|
|
"Steve Cliff <me@stevecliff.com>": "me@stevecliff.com",
|
|
"me@stevecliff.com": "me@stevecliff.com",
|
|
"<me@stevecliff.com>": "me@stevecliff.com",
|
|
"not a valid address": "not a valid address", // unparseable ⇒ passthrough
|
|
}
|
|
for in, want := range cases {
|
|
if got := envelopeFrom(in); got != want {
|
|
t.Fatalf("envelopeFrom(%q) = %q, want %q", in, got, want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestBuildMIMEKeepsDisplayNameInHeader(t *testing.T) {
|
|
raw, err := BuildMIME(OutgoingMessage{
|
|
From: "Steve Cliff <me@stevecliff.com>",
|
|
To: []string{"you@example.com"},
|
|
Subject: "hi",
|
|
BodyText: "body",
|
|
Date: time.Date(2026, 6, 23, 12, 0, 0, 0, time.UTC),
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("BuildMIME: %v", err)
|
|
}
|
|
if !strings.Contains(string(raw), "Steve Cliff") {
|
|
t.Fatalf("From header lost display name:\n%s", raw)
|
|
}
|
|
}
|
|
|
|
func TestReadHeaderParsesReferences(t *testing.T) {
|
|
raw := "From: a@x.com\r\n" +
|
|
"To: b@x.com\r\n" +
|
|
"Subject: re: hi\r\n" +
|
|
"Message-Id: <reply@x.com>\r\n" +
|
|
"References: <root@x.com> <mid@x.com>\r\n" +
|
|
"\r\nbody\r\n"
|
|
h, err := ParseHeaderBytes(7, []byte(raw))
|
|
if err != nil {
|
|
t.Fatalf("ParseHeaderBytes: %v", err)
|
|
}
|
|
if len(h.References) != 2 || h.References[0] != "root@x.com" || h.References[1] != "mid@x.com" {
|
|
t.Fatalf("References not parsed: %v", h.References)
|
|
}
|
|
}
|