package mail import ( "net/smtp" "strings" "testing" ) func TestSMTPMailerSendBuildsHeadersForValidInput(t *testing.T) { mailer := &smtpMailer{cfg: Config{ Host: "smtp.example.com", Port: "587", From: "sender@example.com", }} var captured string called := false prev := smtpSendMail smtpSendMail = func(addr string, auth smtp.Auth, from string, to []string, msg []byte) error { called = true if addr != "smtp.example.com:587" { t.Fatalf("addr=%q", addr) } if from != "sender@example.com" { t.Fatalf("from=%q", from) } if len(to) != 1 || to[0] != "recipient@example.com" { t.Fatalf("to=%v", to) } captured = string(msg) return nil } t.Cleanup(func() { smtpSendMail = prev }) err := mailer.Send(Message{ To: "recipient@example.com", Subject: "Hello there", Text: "plain body", HTML: "

html body

", }) if err != nil { t.Fatal(err) } if !called { t.Fatal("expected smtpSendMail to be called") } for _, want := range []string{ "From: sender@example.com", "To: recipient@example.com", "Subject: Hello there", } { if !strings.Contains(captured, want) { t.Fatalf("message missing %q:\n%s", want, captured) } } } func TestSMTPMailerSendRejectsHeaderInjection(t *testing.T) { cases := []Message{ {To: "recipient@example.com", Subject: "ok\r\nBcc:evil@example.com", Text: "body"}, {To: "recipient@example.com\r\nBcc:evil@example.com", Subject: "ok", Text: "body"}, } for _, tc := range cases { mailer := &smtpMailer{cfg: Config{ Host: "smtp.example.com", Port: "587", From: "sender@example.com", }} called := false prev := smtpSendMail smtpSendMail = func(addr string, auth smtp.Auth, from string, to []string, msg []byte) error { called = true return nil } err := mailer.Send(tc) smtpSendMail = prev if err == nil { t.Fatalf("expected error for %#v", tc) } if called { t.Fatalf("smtpSendMail should not be called for %#v", tc) } } } func TestNewNoopWhenDisabledOrHostEmpty(t *testing.T) { if New(Config{Enabled: false, Host: "smtp.example.com"}).Enabled() { t.Fatal("disabled mailer must report Enabled=false") } if New(Config{Enabled: true, Host: ""}).Enabled() { t.Fatal("empty host must be noop") } if !New(Config{Enabled: true, Host: "smtp.example.com", From: "a@b.c"}).Enabled() { t.Fatal("enabled+host must be live SMTP mailer") } } func TestNewDynamicResolvesPerCall(t *testing.T) { calls := 0 host := "smtp-a.example.com" m := NewDynamic(func() (Config, error) { calls++ return ConfigFromParts(true, host, "587", "u", "p", "from@example.com"), nil }) if !m.Enabled() { t.Fatal("expected enabled") } host = "smtp-b.example.com" if !m.Enabled() { t.Fatal("expected still enabled after host change") } if calls < 2 { t.Fatalf("expected resolve per Enabled call, got %d", calls) } } func TestSetPasswordURL(t *testing.T) { got := SetPasswordURL("http://localhost:5174/", "tok123") want := "http://localhost:5174/accept-invite?token=tok123&mode=set-password" if got != want { t.Fatalf("SetPasswordURL=%q want %q", got, want) } msg := SetPasswordMessage("http://localhost:5174", "u@example.com", "tok123") if !strings.Contains(msg.Text, want) && !strings.Contains(msg.Text, "token=tok123&mode=set-password") { t.Fatalf("SetPasswordMessage text missing link: %q", msg.Text) } }