mirror of
https://github.com/warmbly/warmbly.git
synced 2026-09-13 08:05:05 +00:00
143 lines
6.5 KiB
Go
143 lines
6.5 KiB
Go
package tasks
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/google/uuid"
|
|
"github.com/warmbly/warmbly/internal/models"
|
|
)
|
|
|
|
func TestUnsubscribeLinkIsNeverTracked(t *testing.T) {
|
|
body := `<p>Hi <a href="https://acme.com/pricing">pricing</a> and <a href="https://api.example.com/unsubscribe/abc123">unsubscribe</a></p>`
|
|
out, links := WrapLinksForTracking(body, uuid.New(), uuid.New(), "t.example.com")
|
|
if len(links) != 1 || links[0].Destination != "https://acme.com/pricing" {
|
|
t.Fatalf("expected only the pricing link to be ticketed, got %+v", links)
|
|
}
|
|
if !strings.Contains(out, `href="https://api.example.com/unsubscribe/abc123"`) {
|
|
t.Fatalf("unsubscribe link was rewritten: %s", out)
|
|
}
|
|
}
|
|
|
|
func TestOptOutFooter(t *testing.T) {
|
|
text := models.UnsubscribeSettings{Mode: models.UnsubscribeModeText, Text: "Reply and I'll stop."}
|
|
h, p := appendOptOut("<p>Hi</p>", "Hi", text, "")
|
|
if !strings.Contains(h, "Reply and I'll stop.") || !strings.HasSuffix(p, "Reply and I'll stop.") {
|
|
t.Fatalf("text footer missing: %q / %q", h, p)
|
|
}
|
|
|
|
link := models.UnsubscribeSettings{Mode: models.UnsubscribeModeLink, Text: "fallback", LinkIntro: "Not interested?", LinkText: "Unsubscribe"}
|
|
h, p = appendOptOut("<p>Hi</p>", "Hi", link, "https://api.example.com/unsubscribe/tok")
|
|
if !strings.Contains(h, `href="https://api.example.com/unsubscribe/tok"`) || !strings.Contains(p, "Unsubscribe: https://api.example.com/unsubscribe/tok") {
|
|
t.Fatalf("link footer missing: %q / %q", h, p)
|
|
}
|
|
// No link to mint: link mode degrades to the text line, never to nothing.
|
|
h, _ = appendOptOut("<p>Hi</p>", "Hi", link, "")
|
|
if !strings.Contains(h, "fallback") {
|
|
t.Fatalf("link mode without a link should fall back to text: %q", h)
|
|
}
|
|
h, p = appendOptOut("<p>Hi</p>", "Hi", models.UnsubscribeSettings{Mode: models.UnsubscribeModeOff}, "x")
|
|
if h != "<p>Hi</p>" || p != "Hi" {
|
|
t.Fatalf("off mode changed the body: %q / %q", h, p)
|
|
}
|
|
}
|
|
|
|
func TestLinkifyUnsubscribeURL(t *testing.T) {
|
|
const url = "https://api.example.com/unsubscribe/tok123"
|
|
|
|
// The variable chip the composer saves: a bare URL in a text node.
|
|
got := linkifyUnsubscribeURL(`<p>Not interested? <span data-var>`+url+`</span></p>`, url, "Unsubscribe")
|
|
want := `<p>Not interested? <span data-var><a href="` + url + `">Unsubscribe</a></span></p>`
|
|
if got != want {
|
|
t.Fatalf("bare token not linkified:\n got %s\nwant %s", got, want)
|
|
}
|
|
|
|
// The workspace's own link wording is what the anchor says.
|
|
if got := linkifyUnsubscribeURL("<p>"+url+"</p>", url, "Opt out"); !strings.Contains(got, `>Opt out</a>`) {
|
|
t.Fatalf("link text ignored: %s", got)
|
|
}
|
|
if got := linkifyUnsubscribeURL("<p>"+url+"</p>", url, " "); !strings.Contains(got, ">Unsubscribe</a>") {
|
|
t.Fatalf("blank link text should fall back to the default: %s", got)
|
|
}
|
|
if got := linkifyUnsubscribeURL("<p>"+url+"</p>", url, `Bill & "Ted"`); !strings.Contains(got, `>Bill & "Ted"</a>`) {
|
|
t.Fatalf("link text not escaped: %s", got)
|
|
}
|
|
|
|
// An author's own anchor is left exactly as written: no second link, no
|
|
// rewritten href.
|
|
own := `<p><a href="` + url + `" style="color:red">click here</a></p>`
|
|
if got := linkifyUnsubscribeURL(own, url, "Unsubscribe"); got != own {
|
|
t.Fatalf("author's anchor was rewritten: %s", got)
|
|
}
|
|
|
|
// The URL as the text of an existing anchor becomes its label rather than
|
|
// a nested link.
|
|
nested := `<a href="` + url + `">` + url + `</a>`
|
|
if got := linkifyUnsubscribeURL(nested, url, "Unsubscribe"); got != `<a href="`+url+`">Unsubscribe</a>` {
|
|
t.Fatalf("URL inside an anchor should become the label: %s", got)
|
|
}
|
|
|
|
// Nothing to do cases.
|
|
if got := linkifyUnsubscribeURL("<p>Hi</p>", url, "Unsubscribe"); got != "<p>Hi</p>" {
|
|
t.Fatalf("body without the link changed: %s", got)
|
|
}
|
|
if got := linkifyUnsubscribeURL("<p>"+url+"</p>", "", "Unsubscribe"); got != "<p>"+url+"</p>" {
|
|
t.Fatalf("no link to mint should be a no-op: %s", got)
|
|
}
|
|
if got := linkifyUnsubscribeURL("", url, "Unsubscribe"); got != "" {
|
|
t.Fatalf("empty body changed: %q", got)
|
|
}
|
|
|
|
// A ">" inside a quoted attribute closes nothing. Reading one as the end of
|
|
// the tag turned the href that followed it into a dead link.
|
|
quoted := `<a title="x > y" href="` + url + `">read this</a>`
|
|
if got := linkifyUnsubscribeURL(quoted, url, "Unsubscribe"); got != quoted {
|
|
t.Fatalf("a quoted \">\" broke the tag scan: %s", got)
|
|
}
|
|
if got := linkifyUnsubscribeURL(`<p title="a > b">Bye. `+url+`</p>`, url, "Unsubscribe"); got != `<p title="a > b">Bye. <a href="`+url+`">Unsubscribe</a></p>` {
|
|
t.Fatalf("text after a quoted \">\" should still linkify: %s", got)
|
|
}
|
|
|
|
// A malformed body (unterminated tag) is copied through, never truncated.
|
|
if got := linkifyUnsubscribeURL("<p>hi<span "+url, url, "Unsubscribe"); got != "<p>hi<span "+url {
|
|
t.Fatalf("unterminated tag mangled: %s", got)
|
|
}
|
|
|
|
// Case-insensitive anchor tracking: </A> closes <A>, so what follows is
|
|
// loose text again.
|
|
mixed := `<A HREF="` + url + `">x</A> or ` + url
|
|
if got := linkifyUnsubscribeURL(mixed, url, "Unsubscribe"); got != `<A HREF="`+url+`">x</A> or <a href="`+url+`">Unsubscribe</a>` {
|
|
t.Fatalf("mixed-case anchors mishandled: %s", got)
|
|
}
|
|
}
|
|
|
|
func TestLinkifiedUnsubscribeLinkSurvivesTracking(t *testing.T) {
|
|
const url = "https://api.example.com/unsubscribe/tok123"
|
|
body := linkifyUnsubscribeURL(`<p>Or `+url+` to stop. <a href="https://acme.com/pricing">pricing</a></p>`, url, "Unsubscribe")
|
|
out, links := WrapLinksForTracking(body, uuid.New(), uuid.New(), "t.example.com")
|
|
if len(links) != 1 || links[0].Destination != "https://acme.com/pricing" {
|
|
t.Fatalf("expected only the pricing link to be ticketed, got %+v", links)
|
|
}
|
|
if !strings.Contains(out, `href="`+url+`">Unsubscribe</a>`) {
|
|
t.Fatalf("unsubscribe anchor was rewritten: %s", out)
|
|
}
|
|
}
|
|
|
|
func TestFinishBodyLinkifiesHTMLAndKeepsThePlainURL(t *testing.T) {
|
|
const url = "https://api.example.com/unsubscribe/tok123"
|
|
off := models.UnsubscribeSettings{Mode: models.UnsubscribeModeOff, LinkText: "Unsubscribe"}
|
|
htmlOut, plainOut := finishBody(`<p>Bye. `+url+`</p>`, "", false, nil, &off, url)
|
|
if !strings.Contains(htmlOut, `<a href="`+url+`">Unsubscribe</a>`) {
|
|
t.Fatalf("html part not linkified: %s", htmlOut)
|
|
}
|
|
// Plain text cannot hide a URL, so it keeps the address itself.
|
|
if !strings.Contains(plainOut, url) {
|
|
t.Fatalf("plain part lost the link: %q", plainOut)
|
|
}
|
|
// No settings at all still linkifies, with the default wording.
|
|
htmlOut, _ = finishBody(`<p>`+url+`</p>`, "x", false, nil, nil, url)
|
|
if !strings.Contains(htmlOut, ">Unsubscribe</a>") {
|
|
t.Fatalf("nil settings should still linkify: %s", htmlOut)
|
|
}
|
|
}
|