package email import ( "net/mail" "strings" "testing" ) func headers(t *testing.T, raw string) mail.Header { t.Helper() m, err := mail.ReadMessage(strings.NewReader(strings.ReplaceAll(raw, "\n", "\r\n") + "\r\n\r\nbody\r\n")) if err != nil { t.Fatalf("read headers: %v", err) } return m.Header } func TestClassifyJunk(t *testing.T) { cases := []struct { name string raw string junk bool reason string }{ {"personal", "From: a@b.c\nSubject: привет", false, ""}, {"list-unsubscribe", "From: a@b.c\nList-Unsubscribe: ", true, "list-unsubscribe"}, {"list-id", "From: a@b.c\nList-Id: ", true, "list-id"}, {"precedence bulk", "From: a@b.c\nPrecedence: bulk", true, "precedence"}, {"auto-submitted", "From: a@b.c\nAuto-Submitted: auto-generated", true, "auto-submitted"}, {"auto-submitted no", "From: a@b.c\nAuto-Submitted: no", false, ""}, {"spam flag", "From: a@b.c\nX-Spam-Flag: YES", true, "x-spam-flag"}, {"spam status", "From: a@b.c\nX-Spam-Status: Yes, score=9.1", true, "x-spam-status"}, {"spam status no", "From: a@b.c\nX-Spam-Status: No, score=0.1", false, ""}, // X-GM-LABELS is a Gmail FETCH data item, not a header, so it never // reaches classifyJunk through this client. The rule that matched it was // removed rather than left claiming a Promotions filter that never ran. {"gmail label header is not a rule", "From: a@b.c\nX-Gmail-Labels: Inbox,CATEGORY_PROMOTIONS", false, ""}, } for _, c := range cases { t.Run(c.name, func(t *testing.T) { junk, reason := classifyJunk(headers(t, c.raw)) if junk != c.junk || reason != c.reason { t.Errorf("classifyJunk = (%v, %q), want (%v, %q)", junk, reason, c.junk, c.reason) } }) } } func TestNewsletterFixtureIsJunk(t *testing.T) { msg, err := ParseMessage(9, fixture(t, "newsletter.eml")) if err != nil { t.Fatalf("parse: %v", err) } if !msg.Junk { t.Fatal("a newsletter with List-Unsubscribe + Precedence: bulk must be junk") } // The reason is what gets logged, so it must never carry mail content. if strings.Contains(msg.JunkReason, "@") || strings.Contains(msg.JunkReason, "Скидки") { t.Errorf("junk reason leaks content: %q", msg.JunkReason) } }