refac: removed 7 direct and 12 indirect go modules for custom code instead
This commit is contained in:
1 parent
cfcf108488
commit
c68e9a7858
73 files changed
+2455
-428
No files matched your search
@@ -0,0 +1,284 @@
|
||||
package test
|
||||
|
||||
import (
|
||||
"RenewCMS/internal/infrastructure/config"
|
||||
"RenewCMS/internal/infrastructure/mailer"
|
||||
"RenewCMS/internal/infrastructure/useCases"
|
||||
"io"
|
||||
"mime"
|
||||
"mime/quotedprintable"
|
||||
"net/mail"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
var smtpMailerClock = time.Date(2026, time.March, 1, 12, 0, 0, 0, time.UTC)
|
||||
|
||||
func smtpMailerFor(server *fakeSMTPServer, settings config.SmtpSettings) *mailer.MailRepository {
|
||||
if settings.Host == "" {
|
||||
settings.Host = "127.0.0.1"
|
||||
}
|
||||
settings.Port = server.port()
|
||||
if settings.Sender == "" {
|
||||
settings.Sender = "notify@example.com"
|
||||
}
|
||||
return mailer.NewMailRepository(settings, mailer.WithTLSConfig(server.clientTLS()), mailer.WithClock(func() time.Time { return smtpMailerClock }))
|
||||
}
|
||||
|
||||
func verificationData() useCases.MailValidationData {
|
||||
return useCases.MailValidationData{Host: "https://cms.example.com", VerificationCode: "abc123"}
|
||||
}
|
||||
|
||||
func parseDeliveredMail(t *testing.T, raw string) (*mail.Message, string) {
|
||||
t.Helper()
|
||||
message, err := mail.ReadMessage(strings.NewReader(raw))
|
||||
if err != nil {
|
||||
t.Fatalf("delivered data is not a valid message: %v\n%s", err, raw)
|
||||
}
|
||||
decoded, err := io.ReadAll(quotedprintable.NewReader(message.Body))
|
||||
if err != nil {
|
||||
t.Fatalf("body is not valid quoted-printable: %v", err)
|
||||
}
|
||||
return message, string(decoded)
|
||||
}
|
||||
|
||||
func TestSMTPMailerDelivery(t *testing.T) {
|
||||
t.Run("Delivers a well-formed HTML message to a plain local server", func(t *testing.T) {
|
||||
server := newFakeSMTPServer(t, fakeSMTPOptions{})
|
||||
|
||||
err := smtpMailerFor(server, config.SmtpSettings{}).Send("reader@example.org", "verification", verificationData())
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("send failed: %v", err)
|
||||
}
|
||||
sessions := server.received()
|
||||
if len(sessions) != 1 || sessions[0].from != "notify@example.com" || sessions[0].to != "reader@example.org" {
|
||||
t.Fatalf("unexpected envelope %+v", sessions)
|
||||
}
|
||||
message, body := parseDeliveredMail(t, sessions[0].data)
|
||||
if got := message.Header.Get("From"); got != `"RenewCMS" <notify@example.com>` {
|
||||
t.Errorf("unexpected From %q", got)
|
||||
}
|
||||
if got := message.Header.Get("To"); got != "<reader@example.org>" {
|
||||
t.Errorf("unexpected To %q", got)
|
||||
}
|
||||
subject, _ := new(mime.WordDecoder).DecodeHeader(message.Header.Get("Subject"))
|
||||
if subject != "Verify your RenewCMS account" {
|
||||
t.Errorf("unexpected Subject %q", subject)
|
||||
}
|
||||
if got := message.Header.Get("Content-Type"); got != "text/html; charset=UTF-8" {
|
||||
t.Errorf("unexpected Content-Type %q", got)
|
||||
}
|
||||
if got := message.Header.Get("MIME-Version"); got != "1.0" {
|
||||
t.Errorf("unexpected MIME-Version %q", got)
|
||||
}
|
||||
if !strings.Contains(body, "https://cms.example.com/register/validate?c=abc123") {
|
||||
t.Errorf("verification link missing from body %q", body)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Stamps a Date from the clock and a unique Message-ID on the sender's domain", func(t *testing.T) {
|
||||
server := newFakeSMTPServer(t, fakeSMTPOptions{})
|
||||
repo := smtpMailerFor(server, config.SmtpSettings{})
|
||||
|
||||
_ = repo.Send("a@example.org", "verification", verificationData())
|
||||
_ = repo.Send("a@example.org", "verification", verificationData())
|
||||
|
||||
sessions := server.received()
|
||||
first, _ := parseDeliveredMail(t, sessions[0].data)
|
||||
second, _ := parseDeliveredMail(t, sessions[1].data)
|
||||
date, err := first.Header.Date()
|
||||
if err != nil || !date.Equal(smtpMailerClock) {
|
||||
t.Errorf("expected Date %v, got %v (%v)", smtpMailerClock, date, err)
|
||||
}
|
||||
id := first.Header.Get("Message-ID")
|
||||
if !strings.HasPrefix(id, "<") || !strings.HasSuffix(id, "@example.com>") {
|
||||
t.Errorf("unexpected Message-ID %q", id)
|
||||
}
|
||||
if id == second.Header.Get("Message-ID") {
|
||||
t.Error("Message-IDs must be unique")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Encodes a non-ASCII subject and body without corrupting them", func(t *testing.T) {
|
||||
server := newFakeSMTPServer(t, fakeSMTPOptions{})
|
||||
repo := smtpMailerFor(server, config.SmtpSettings{})
|
||||
|
||||
err := repo.Send("reader@example.org", "verification", verificationData())
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
raw := server.received()[0].data
|
||||
headerBlock, _, _ := strings.Cut(raw, "\r\n\r\n")
|
||||
for _, r := range headerBlock {
|
||||
if r > 127 {
|
||||
t.Fatalf("header contains a raw non-ASCII rune %q", r)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Authenticates with PLAIN over STARTTLS", func(t *testing.T) {
|
||||
server := newFakeSMTPServer(t, fakeSMTPOptions{startTLS: true, authMechanisms: "PLAIN LOGIN"})
|
||||
settings := config.SmtpSettings{Login: "user@example.com", Password: "s3cret"}
|
||||
|
||||
err := smtpMailerFor(server, settings).Send("reader@example.org", "verification", verificationData())
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("send failed: %v", err)
|
||||
}
|
||||
session := server.received()[0]
|
||||
if !session.usedTLS || session.username != "user@example.com" || session.password != "s3cret" {
|
||||
t.Errorf("unexpected session %+v", session)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Falls back to LOGIN when PLAIN is not offered", func(t *testing.T) {
|
||||
server := newFakeSMTPServer(t, fakeSMTPOptions{startTLS: true, authMechanisms: "LOGIN"})
|
||||
settings := config.SmtpSettings{Login: "user@example.com", Password: "s3cret"}
|
||||
|
||||
err := smtpMailerFor(server, settings).Send("reader@example.org", "verification", verificationData())
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("send failed: %v", err)
|
||||
}
|
||||
session := server.received()[0]
|
||||
if session.username != "user@example.com" || session.password != "s3cret" {
|
||||
t.Errorf("unexpected credentials %+v", session)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Skips authentication when no login is configured", func(t *testing.T) {
|
||||
server := newFakeSMTPServer(t, fakeSMTPOptions{authMechanisms: "PLAIN"})
|
||||
|
||||
err := smtpMailerFor(server, config.SmtpSettings{}).Send("reader@example.org", "verification", verificationData())
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, command := range server.received()[0].commands {
|
||||
if strings.HasPrefix(strings.ToUpper(command), "AUTH") {
|
||||
t.Errorf("unexpected %q", command)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
for _, mechanisms := range []string{"PLAIN", "LOGIN"} {
|
||||
t.Run("Refuses to send "+mechanisms+" credentials in clear text to a non-loopback server", func(t *testing.T) {
|
||||
server := newFakeSMTPServer(t, fakeSMTPOptions{authMechanisms: mechanisms, listenAddress: "127.0.0.2:0"})
|
||||
settings := config.SmtpSettings{Login: "user@example.com", Password: "s3cret", Host: "127.0.0.2"}
|
||||
|
||||
err := smtpMailerFor(server, settings).Send("reader@example.org", "verification", verificationData())
|
||||
|
||||
if err == nil {
|
||||
t.Fatal("expected an error")
|
||||
}
|
||||
if session := server.received()[0]; session.password != "" || session.data != "" {
|
||||
t.Errorf("credentials or data leaked: %+v", session)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
t.Run("Fails when credentials are configured but the server offers no AUTH", func(t *testing.T) {
|
||||
server := newFakeSMTPServer(t, fakeSMTPOptions{})
|
||||
settings := config.SmtpSettings{Login: "user@example.com", Password: "s3cret"}
|
||||
|
||||
err := smtpMailerFor(server, settings).Send("reader@example.org", "verification", verificationData())
|
||||
|
||||
if err == nil {
|
||||
t.Fatal("expected an error")
|
||||
}
|
||||
if len(server.received()[0].data) != 0 {
|
||||
t.Error("no message may be delivered")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Fails the STARTTLS handshake against an untrusted certificate", func(t *testing.T) {
|
||||
server := newFakeSMTPServer(t, fakeSMTPOptions{startTLS: true})
|
||||
repo := mailer.NewMailRepository(config.SmtpSettings{Host: "127.0.0.1", Port: server.port(), Sender: "notify@example.com"})
|
||||
|
||||
err := repo.Send("reader@example.org", "verification", verificationData())
|
||||
|
||||
if err == nil {
|
||||
t.Fatal("expected a certificate error")
|
||||
}
|
||||
if len(server.received()[0].data) != 0 {
|
||||
t.Error("no message may be delivered")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Uses implicit TLS on port 465", func(t *testing.T) {
|
||||
server := newFakeSMTPServer(t, fakeSMTPOptions{implicitTLS: true, listenAddress: "127.0.0.1:465"})
|
||||
repo := mailer.NewMailRepository(config.SmtpSettings{Host: "127.0.0.1", Port: 465, Sender: "notify@example.com"}, mailer.WithTLSConfig(server.clientTLS()))
|
||||
|
||||
err := repo.Send("reader@example.org", "verification", verificationData())
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("send failed: %v", err)
|
||||
}
|
||||
if !server.received()[0].usedTLS {
|
||||
t.Error("expected a TLS session")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Surfaces a rejected recipient", func(t *testing.T) {
|
||||
server := newFakeSMTPServer(t, fakeSMTPOptions{rejectRcpt: true})
|
||||
|
||||
err := smtpMailerFor(server, config.SmtpSettings{}).Send("reader@example.org", "verification", verificationData())
|
||||
|
||||
if err == nil {
|
||||
t.Fatal("expected an error")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Surfaces a rejected DATA command", func(t *testing.T) {
|
||||
server := newFakeSMTPServer(t, fakeSMTPOptions{rejectData: true})
|
||||
|
||||
err := smtpMailerFor(server, config.SmtpSettings{}).Send("reader@example.org", "verification", verificationData())
|
||||
|
||||
if err == nil {
|
||||
t.Fatal("expected an error")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Surfaces an unreachable server", func(t *testing.T) {
|
||||
server := newFakeSMTPServer(t, fakeSMTPOptions{})
|
||||
repo := smtpMailerFor(server, config.SmtpSettings{})
|
||||
_ = server.listener.Close()
|
||||
|
||||
if err := repo.Send("reader@example.org", "verification", verificationData()); err == nil {
|
||||
t.Fatal("expected an error")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestSMTPMailerRejectsUnsafeInput(t *testing.T) {
|
||||
cases := map[string]struct {
|
||||
sender string
|
||||
receiver string
|
||||
template string
|
||||
}{
|
||||
"receiver with an injected header": {"notify@example.com", "a@example.org\r\nBcc: evil@example.net", "verification"},
|
||||
"receiver that is not an address": {"notify@example.com", "not-an-address", "verification"},
|
||||
"empty receiver": {"notify@example.com", "", "verification"},
|
||||
"sender that is not an address": {"nope", "a@example.org", "verification"},
|
||||
"unknown template": {"notify@example.com", "a@example.org", "does-not-exist"},
|
||||
}
|
||||
|
||||
for name, tc := range cases {
|
||||
t.Run("Rejects a "+name+" before opening a connection", func(t *testing.T) {
|
||||
server := newFakeSMTPServer(t, fakeSMTPOptions{})
|
||||
repo := smtpMailerFor(server, config.SmtpSettings{Sender: tc.sender})
|
||||
|
||||
err := repo.Send(tc.receiver, tc.template, verificationData())
|
||||
|
||||
if err == nil {
|
||||
t.Fatal("expected an error")
|
||||
}
|
||||
if len(server.received()) != 0 {
|
||||
t.Errorf("no connection may be made, got %d", len(server.received()))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user