Add queued email notifications for collaboration events

This commit is contained in:
2026-07-22 08:51:42 -04:00
parent b6d2ded141
commit 09f51d1ef4
20 changed files with 1507 additions and 102 deletions

View File

@@ -0,0 +1,119 @@
package email
import (
"strings"
"testing"
)
func TestRender_FileChanged(t *testing.T) {
subject, body, err := Render("file_changed", TemplateData{
ActorName: "Alice",
DocumentPath: "docs/api.md",
DocumentTitle: "API Reference",
ViewURL: "https://example.com/docs/api.md",
UnsubURL: "https://example.com/unsub/123",
InstanceName: "Cairnquire",
})
if err != nil {
t.Fatalf("Render: %v", err)
}
if !strings.Contains(subject, "docs/api.md") {
t.Errorf("subject should include path: %q", subject)
}
if !strings.Contains(subject, "Alice") {
t.Errorf("subject should include actor: %q", subject)
}
if !strings.Contains(body, "Alice") {
t.Errorf("body should mention actor: %q", body)
}
if !strings.Contains(body, "API Reference") {
t.Errorf("body should mention title: %q", body)
}
if !strings.Contains(body, "https://example.com/docs/api.md") {
t.Errorf("body should include view url: %q", body)
}
if !strings.Contains(body, "https://example.com/unsub/123") {
t.Errorf("body should include unsub url: %q", body)
}
}
func TestRender_Comment(t *testing.T) {
subject, body, err := Render("comment", TemplateData{
ActorName: "Bob",
DocumentTitle: "Getting Started",
CommentBody: "This step is unclear.\nCan we add an example?",
ViewURL: "https://example.com/docs/getting-started",
})
if err != nil {
t.Fatalf("Render: %v", err)
}
if !strings.Contains(subject, "Bob") {
t.Errorf("subject should include actor: %q", subject)
}
if !strings.Contains(body, "> This step is unclear.") {
t.Errorf("body should quote comment: %q", body)
}
if !strings.Contains(body, "> Can we add an example?") {
t.Errorf("body should quote second line: %q", body)
}
if !strings.Contains(body, "Reply to this email") {
t.Errorf("body should mention reply-to-respond: %q", body)
}
}
func TestRender_Mention(t *testing.T) {
subject, body, err := Render("mention", TemplateData{
ActorName: "Carol",
DocumentTitle: "Spec",
MentionText: "@dan please review",
})
if err != nil {
t.Fatalf("Render: %v", err)
}
if !strings.Contains(subject, "Mention") {
t.Errorf("subject: %q", subject)
}
if !strings.Contains(body, "> @dan please review") {
t.Errorf("body should quote mention: %q", body)
}
}
func TestRender_Conflict(t *testing.T) {
subject, body, err := Render("conflict", TemplateData{
DocumentPath: "notes/2024.md",
ConflictDesc: "Local and remote both edited line 12",
})
if err != nil {
t.Fatalf("Render: %v", err)
}
if !strings.Contains(subject, "conflict") {
t.Errorf("subject: %q", subject)
}
if !strings.Contains(body, "sync conflict") {
t.Errorf("body should mention conflict: %q", body)
}
if !strings.Contains(body, "line 12") {
t.Errorf("body should include conflict desc: %q", body)
}
}
func TestRender_UnknownKind(t *testing.T) {
_, _, err := Render("bogus", TemplateData{})
if err == nil {
t.Fatal("expected error for unknown kind")
}
}
func TestRender_PlainTextOnly(t *testing.T) {
for _, kind := range []string{"file_changed", "comment", "mention", "conflict"} {
_, body, err := Render(kind, TemplateData{
ActorName: "A", DocumentTitle: "T", CommentBody: "<b>html</b>",
})
if err != nil {
t.Fatalf("Render %s: %v", kind, err)
}
if strings.Contains(body, "<html>") || strings.Contains(body, "<body>") {
t.Errorf("template %s produced HTML: %q", kind, body)
}
}
}