Files
RenewCMS/test/invitations_qa_test.go
T

115 lines
3.9 KiB
Go

package test
import (
"RenewCMS/internal/infrastructure/config"
"RenewCMS/internal/infrastructure/mailer"
"RenewCMS/internal/infrastructure/useCases"
"bytes"
"net/http"
"os"
"strings"
"testing"
"time"
)
func TestInvitationsQaMailer(t *testing.T) {
repo := mailer.NewMailRepository(config.SmtpSettings{})
t.Run("The invitation renders with the inviter, the address and the accept link", func(t *testing.T) {
data := useCases.MailInviteData{Host: invitePublicURL, VerificationCode: "abc-123", Email: "lea@example.com", InvitedBy: "Ines Duarte"}
subject, body, err := repo.Render("invitation", data)
if err != nil {
t.Fatalf("unexpected error %v", err)
}
if subject != "Ines Duarte invited you to RenewCMS" {
t.Errorf("unexpected subject %q", subject)
}
for _, want := range []string{invitePublicURL + "/invite/accept?c=abc-123", "lea@example.com", "Ines Duarte"} {
if !strings.Contains(body, want) {
t.Errorf("body misses %q", want)
}
}
})
t.Run("The inviter name is HTML-escaped in the body", func(t *testing.T) {
data := useCases.MailInviteData{Host: invitePublicURL, VerificationCode: "abc-123", Email: "lea@example.com", InvitedBy: "<b>x</b>"}
_, body, err := repo.Render("invitation", data)
if err != nil {
t.Fatalf("unexpected error %v", err)
}
if strings.Contains(body, "<b>x</b>") || !strings.Contains(body, "&lt;b&gt;x&lt;/b&gt;") {
t.Errorf("inviter name was not escaped")
}
})
t.Run("Other subjects are unchanged", func(t *testing.T) {
reset, _, err := repo.Render("password-reset", useCases.MailPasswordResetData{Host: invitePublicURL, VerificationCode: "x", Email: "a@b.example"})
if err != nil || reset != "Reset your RenewCMS password" {
t.Errorf("unexpected reset subject %q, %v", reset, err)
}
verification, _, err := repo.Render("verification", useCases.MailValidationData{Host: invitePublicURL, VerificationCode: "x"})
if err != nil || verification != "Verify your RenewCMS account" {
t.Errorf("unexpected verification subject %q, %v", verification, err)
}
})
t.Run("The shipped template is byte-identical to the design export", func(t *testing.T) {
shipped, err := os.ReadFile("../internal/infrastructure/mailer/templates/invitation.html")
if err != nil {
t.Fatal(err)
}
design, err := os.ReadFile("../docs/design/source/emails/invitation.html")
if err != nil {
t.Fatal(err)
}
if !bytes.Equal(shipped, design) {
t.Errorf("template differs from docs/design/source/emails/invitation.html")
}
})
}
func TestInvitationsQaList(t *testing.T) {
t.Run("Invite status follows its age", func(t *testing.T) {
f := newInviteFixture(t, 100)
now := time.Now()
f.invites.seed(inviteAt("old@renew.example", now.Add(-72*time.Hour-time.Minute), now.Add(-time.Minute)))
f.invites.seed(inviteAt("fresh@renew.example", now.Add(-48*time.Hour), now.Add(24*time.Hour)))
acceptedAt := now
accepted := inviteAt("done@renew.example", now.Add(-time.Hour), now.Add(71*time.Hour))
accepted.AcceptedAt = &acceptedAt
f.invites.seed(accepted)
rec := f.list(t)
if rec.Code != http.StatusOK {
t.Fatalf("expected 200, got %d", rec.Code)
}
statuses := map[string]string{}
for _, item := range decodeList(t, rec.Body.Bytes()) {
if decodedString(t, item, "kind") == "invite" {
statuses[decodedString(t, item, "email")] = decodedString(t, item, "status")
}
}
if len(statuses) != 2 || statuses["old@renew.example"] != "expired" || statuses["fresh@renew.example"] != "pending" {
t.Errorf("unexpected statuses %v", statuses)
}
})
t.Run("The list never carries secrets", func(t *testing.T) {
f := newInviteFixture(t, 100)
f.invites.seed(pendingInvite("sam@renew.example"))
body := f.list(t).Body.String()
for _, forbidden := range []string{"\"code\"", "hashed_code", "verification_code", "password"} {
if strings.Contains(body, forbidden) {
t.Errorf("list leaks %q", forbidden)
}
}
})
}