package mailhtml import "strings" import "testing" func TestSanitizeDropsScriptAndStyleContent(t *testing.T) { in := `

Hello world

` out := Sanitize(in) for _, banned := range []string{"alert(1)", "color:red", "onclick", "world") { t.Fatalf("formatting dropped: %s", out) } } func TestSanitizeKeepsLayoutMarkup(t *testing.T) { in := `
` + `Link
` out := Sanitize(in) for _, want := range []string{`width="600"`, `bgcolor="#ffffff"`, `align="center"`, "font-size", `href="https://example.com"`, `target="_blank"`, "xy`) if strings.Contains(out, "javascript:") || strings.Contains(out, "data:text/html") { t.Fatalf("unsafe scheme survived: %s", out) } } func TestToTextDecodesEntitiesAndKeepsLineStructure(t *testing.T) { in := `

Hello,

Terms & conditions © café

` out := ToText(in) if strings.Contains(out, "color:red") { t.Fatalf("stylesheet text leaked: %q", out) } if !strings.Contains(out, "Terms & conditions © café") { t.Fatalf("entities not decoded: %q", out) } if !strings.Contains(out, "\n") { t.Fatalf("block structure lost: %q", out) } } func TestSanitizeAllowsInlineAndRemoteImages(t *testing.T) { out := Sanitize(`inline` + `remote` + `attachment`) if !strings.Contains(out, "data:image/png;base64") { t.Fatalf("inline image dropped: %s", out) } if !strings.Contains(out, "https://cdn.example.com/logo.png") { t.Fatalf("remote image dropped: %s", out) } if strings.Contains(out, "cid:") { t.Fatalf("cid image should not survive: %s", out) } } func TestSanitizeLeavesUnicodeIntact(t *testing.T) { out := Sanitize(`

café ™ 🎉 & more

`) if !strings.Contains(out, "café ™ 🎉") { t.Fatalf("unicode mangled: %s", out) } // "&" must stay escaped exactly once so the browser renders one ampersand. if !strings.Contains(out, "& more") || strings.Contains(out, "&") { t.Fatalf("entity handling wrong: %s", out) } } func TestLooksLikeHTML(t *testing.T) { html := []string{ "

hi

", "x", "line
break", `
y
`, "
a
", } for _, in := range html { if !LooksLikeHTML(in) { t.Fatalf("expected HTML: %q", in) } } plain := []string{ "Hello,\n\nSend it to when ready.", "a < b and c > d", "> quoted line\nreply text", "", } for _, in := range plain { if LooksLikeHTML(in) { t.Fatalf("expected plain text: %q", in) } } } func TestSearchTextKeepsQuotedHistoryAndFlattensHTML(t *testing.T) { got := SearchText("", `

Happy to help.

> what is the pricing?
`, 0) if !strings.Contains(got, "Happy to help.") || !strings.Contains(got, "what is the pricing?") { t.Fatalf("search text lost content: %q", got) } if strings.Contains(got, "<") || strings.Contains(got, ">") { t.Fatalf("markup or entities survived: %q", got) } } func TestSearchTextPrefersPlainAndRespectsLimit(t *testing.T) { if got := SearchText("plain wins", "

html loses

", 0); got != "plain wins" { t.Fatalf("got %q", got) } got := SearchText(strings.Repeat("é", 100), "", 10) if len([]rune(got)) != 10 { t.Fatalf("limit not applied on rune boundaries: %q", got) } }