122 lines
3.3 KiB
Go
122 lines
3.3 KiB
Go
package mail
|
|||
|
|
|
||
|
|
import (
|
||
|
|
"context"
|
||
|
|
"errors"
|
||
|
|
"strings"
|
||
|
|
"testing"
|
||
|
|
)
|
||
|
|
|
||
|
|
type stubSettings map[string]string
|
||
|
|
|
||
|
|
func (s stubSettings) Get(_ context.Context, key string) (string, error) {
|
||
|
|
return s[key], nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func configuredSettings() stubSettings {
|
||
|
|
return stubSettings{
|
||
|
|
SettingEnabled: "true",
|
||
|
|
SettingSMTPHost: "smtp.example.com",
|
||
|
|
SettingFromAddress: "silo@example.com",
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestLoadConfigGates(t *testing.T) {
|
||
|
|
ctx := context.Background()
|
||
|
|
|
||
|
|
t.Run("disabled by default", func(t *testing.T) {
|
||
|
|
sender := NewSMTPSender(stubSettings{})
|
||
|
|
if sender.Enabled(ctx) {
|
||
|
|
t.Fatal("email must be disabled with no settings")
|
||
|
|
}
|
||
|
|
err := sender.Send(ctx, Message{To: []string{"a@b.c"}, Subject: "x", TextBody: "y"})
|
||
|
|
if !errors.Is(err, ErrNotConfigured) {
|
||
|
|
t.Fatalf("Send = %v, want ErrNotConfigured", err)
|
||
|
|
}
|
||
|
|
})
|
||
|
|
|
||
|
|
t.Run("requires host and from address", func(t *testing.T) {
|
||
|
|
settings := configuredSettings()
|
||
|
|
settings[SettingSMTPHost] = ""
|
||
|
|
if NewSMTPSender(settings).Enabled(ctx) {
|
||
|
|
t.Fatal("missing host must disable email")
|
||
|
|
}
|
||
|
|
settings = configuredSettings()
|
||
|
|
settings[SettingFromAddress] = ""
|
||
|
|
if NewSMTPSender(settings).Enabled(ctx) {
|
||
|
|
t.Fatal("missing from address must disable email")
|
||
|
|
}
|
||
|
|
})
|
||
|
|
|
||
|
|
t.Run("complete config enables", func(t *testing.T) {
|
||
|
|
if !NewSMTPSender(configuredSettings()).Enabled(ctx) {
|
||
|
|
t.Fatal("complete config must enable email")
|
||
|
|
}
|
||
|
|
})
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestLoadConfigValidation(t *testing.T) {
|
||
|
|
ctx := context.Background()
|
||
|
|
|
||
|
|
t.Run("defaults", func(t *testing.T) {
|
||
|
|
cfg, err := NewSMTPSender(configuredSettings()).loadConfig(ctx)
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("loadConfig: %v", err)
|
||
|
|
}
|
||
|
|
if cfg.port != 587 || cfg.security != securityStartTLS || cfg.fromName != "Silo" {
|
||
|
|
t.Fatalf("unexpected defaults: %+v", cfg)
|
||
|
|
}
|
||
|
|
})
|
||
|
|
|
||
|
|
t.Run("invalid port", func(t *testing.T) {
|
||
|
|
settings := configuredSettings()
|
||
|
|
settings[SettingSMTPPort] = "99999"
|
||
|
|
if _, err := NewSMTPSender(settings).loadConfig(ctx); err == nil {
|
||
|
|
t.Fatal("out-of-range port must be rejected")
|
||
|
|
}
|
||
|
|
})
|
||
|
|
|
||
|
|
t.Run("invalid security mode", func(t *testing.T) {
|
||
|
|
settings := configuredSettings()
|
||
|
|
settings[SettingSMTPSecurity] = "plz-hack-me"
|
||
|
|
if _, err := NewSMTPSender(settings).loadConfig(ctx); err == nil {
|
||
|
|
t.Fatal("unknown security mode must be rejected")
|
||
|
|
}
|
||
|
|
})
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestSendInputValidation(t *testing.T) {
|
||
|
|
ctx := context.Background()
|
||
|
|
sender := NewSMTPSender(configuredSettings())
|
||
|
|
|
||
|
|
if err := sender.Send(ctx, Message{Subject: "x", TextBody: "y"}); err == nil {
|
||
|
|
t.Fatal("a message without recipients must be rejected")
|
||
|
|
}
|
||
|
|
if err := sender.Send(ctx, Message{To: []string{"a@b.c"}, Subject: "x"}); err == nil {
|
||
|
|
t.Fatal("a message without a body must be rejected")
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestBuildMessageMultipart(t *testing.T) {
|
||
|
|
cfg := &smtpConfig{fromAddress: "silo@example.com", fromName: "Silo"}
|
||
|
|
message, err := buildMessage(cfg, Message{
|
||
|
|
To: []string{"user@example.com"},
|
||
|
|
Subject: "Hello",
|
||
|
|
TextBody: "plain",
|
||
|
|
HTMLBody: "<b>rich</b>",
|
||
|
|
})
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("buildMessage: %v", err)
|
||
|
|
}
|
||
|
|
var rendered strings.Builder
|
||
|
|
if _, err := message.WriteTo(&rendered); err != nil {
|
||
|
|
t.Fatalf("render message: %v", err)
|
||
|
|
}
|
||
|
|
output := rendered.String()
|
||
|
|
for _, want := range []string{"multipart/alternative", "plain", "rich", "Silo", "user@example.com"} {
|
||
|
|
if !strings.Contains(output, want) {
|
||
|
|
t.Fatalf("rendered message missing %q:\n%s", want, output)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|