package api
import (
"context"
"path/filepath"
"regexp"
"strings"
"testing"
"time"
"craftdesign.group/quarry/internal/artifact"
"craftdesign.group/quarry/internal/index"
"craftdesign.group/quarry/internal/oltp"
)
// A mention's references link in the mail to the same places the site
// sends them, each with its state as a PNG and its label, every address
// absolute; one that resolves to nothing stays the author's text.
func TestMailReferencesResolveLikeTheSite(t *testing.T) {
ix, err := index.Open(filepath.Join(t.TempDir(), "m.db"))
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() { ix.Close() })
tid, cid, lid := strings.Repeat("ab", 20), strings.Repeat("c3", 20), strings.Repeat("d4", 20)
if err := ix.FoldNow([]index.Parsed{
{Hash: "t1", Art: &artifact.Artifact{Type: artifact.Ticket, TicketID: tid, Date: "2026-08-15T09:00:00", User: "dan",
Fields: []artifact.Field{{Name: "status", Value: "Fixed"}, {Name: "title", Value: "a closed ticket"}}}},
{Hash: cid, Art: &artifact.Artifact{Type: artifact.Manifest, Date: "2026-08-17T09:00:00", User: "dan", Comment: "a check-in"}},
{Hash: "w1", Art: &artifact.Artifact{Type: artifact.Wiki, WikiTitle: "Guide", Date: "2026-08-15T10:00:00", User: "dan",
Mimetype: "text/x-markdown", Body: []byte("# Guide\n")}},
{Hash: lid, Art: &artifact.Artifact{Type: artifact.Forum, ThreadTitle: "Land it", Date: "2026-08-18T09:00:00", User: "dan",
Body: []byte("zit-landing: 1\nbranch: b\ntarget: trunk\n\nmessage\n")}},
}); err != nil {
t.Fatal(err)
}
db, err := oltp.Open(filepath.Join(t.TempDir(), "o.db"))
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() { db.Close() })
if err := db.TicketNumbersAssign("demo", []string{tid}); err != nil {
t.Fatal(err)
}
tl, ll := func(_ string, n int) (string, bool) { return tid, n == 1 }, func(_ string, n int) (string, bool) { return lid, n == 768 }
ln := func(_, id string) int {
if id == lid {
return 768
}
return 0
}
prevT, prevL, prevN := ticketByNumber.Swap(&tl), landingByNumber.Swap(&ll), landingNumberOfRef.Swap(&ln)
d := Deps{Origin: "https://forge.example", Numbers: db, CanRead: func(context.Context, string) bool { return true },
CapsFor: func(_, _ string) CapSet { return fakeCaps(anyCapLetters) }}
d.Repos = NewRepos(map[string]*Repo{"demo": {Index: ix}})
facts := func(repo, prefix string) (ticketFact, bool) { return d.ticketFactOf(repo, prefix, 0) }
prevF := ticketFacts.Swap(&facts)
t.Cleanup(func() {
ticketByNumber.Store(prevT)
landingByNumber.Store(prevL)
landingNumberOfRef.Store(prevN)
ticketFacts.Store(prevF)
})
text := "landed in !768, ticket #1, hash " + cid[:12] + ", see [[Guide]], and !999"
e := d.withMailHTML(NotifyEvent{Workflow: "quarry-mention", To: "eve", Payload: map[string]any{
"repo": "demo", "from": "dan", "text": text, "surface": "chat", "link": "https://forge.example/r/demo/chat/1"}}, time.Now())
h, _ := e.Payload["html"].(string)
for _, want := range []string{
`href="https://forge.example/r/demo/landing/` + lid, `alt="landing 768, open"`, `/icons/landing-open.png`,
`href="https://forge.example/r/demo/ticket/` + tid, `alt="ticket 1, closed"`, `/icons/ticket-closed.png`,
`href="https://forge.example/r/demo/checkin/` + cid,
`href="https://forge.example/r/demo/wiki/Guide`,
`>768</a>`, `>1</a>`, "and !999",
} {
if !strings.Contains(h, want) {
t.Errorf("mail lacks %s", want)
}
}
for _, banned := range []string{"data-ref", "<svg", "tref-state", ">!768<"} {
if strings.Contains(h, banned) {
t.Errorf("mail carries %q", banned)
}
}
for _, m := range regexp.MustCompile(`(?:src|href)="([^"]*)"`).FindAllStringSubmatch(h, -1) {
if !strings.HasPrefix(m[1], "https://") {
t.Errorf("non-absolute %q", m[1])
}
}
}