package mailkit import ( "strings" "testing" ) // TestEnvelopeAddress covers the exact bug this fixes: a configured // From address that includes a display name (a very natural thing to // set ENKLY_SMTP_FROM to, e.g. "enkly ") must not // be handed to MAIL FROM/RCPT TO as-is, or Gmail answers with "555 // 5.5.2 Syntax error, cannot decode response" because the envelope // command only accepts a bare address (RFC 5321 ยง4.1.2). func TestEnvelopeAddress(t *testing.T) { cases := []struct{ in, want string }{ {"user@example.com", "user@example.com"}, {"enkly ", "user@example.com"}, {" enkly ", "user@example.com"}, {"\"Enkly Notifications\" ", "user@example.com"}, // Not parseable as an address at all: returned unchanged so the // SMTP server's own rejection explains the problem, rather than // this function silently swallowing it. {"not-an-address", "not-an-address"}, } for _, c := range cases { if got := envelopeAddress(c.in); got != c.want { t.Errorf("envelopeAddress(%q) = %q, want %q", c.in, got, c.want) } } } // TestNewTrimsConfig guards against the same class of bug from a // different angle: SMTP config values arriving from os.Getenv with // stray whitespace or an embedded "\r" (e.g. a CRLF-saved .env or // docker-compose.yml) must not reach the wire as part of an SMTP // command line, or the server sees a corrupted command. func TestNewTrimsConfig(t *testing.T) { m := New(Config{ Host: " smtp.gmail.com\r\n", Port: " 587 ", Username: " user@example.com \r", Password: " app-password \r", From: " enkly \r", }) if m.cfg.Host != "smtp.gmail.com" { t.Errorf("Host = %q", m.cfg.Host) } if m.cfg.Port != "587" { t.Errorf("Port = %q", m.cfg.Port) } if m.cfg.Username != "user@example.com" { t.Errorf("Username = %q", m.cfg.Username) } if m.cfg.Password != "app-password" { t.Errorf("Password = %q", m.cfg.Password) } if m.cfg.From != "enkly " { t.Errorf("From = %q", m.cfg.From) } } // TestNewDefaults checks the Gmail host/port/from defaults still apply // after adding trimming, so a minimal config keeps working out of the // box. func TestNewDefaults(t *testing.T) { m := New(Config{Username: "user@example.com", Password: "app-password"}) if m.cfg.Host != "smtp.gmail.com" { t.Errorf("default Host = %q", m.cfg.Host) } if m.cfg.Port != "587" { t.Errorf("default Port = %q", m.cfg.Port) } if m.cfg.From != "user@example.com" { t.Errorf("default From = %q", m.cfg.From) } } func TestConfigured(t *testing.T) { if New(Config{}).Configured() { t.Fatal("empty config must not report Configured") } if !New(Config{Username: "u", Password: "p"}).Configured() { t.Fatal("username+password must report Configured") } } func TestBuildMessageShape(t *testing.T) { msg := buildMessage("enkly ", "to@example.com", "Subject line", "body text") for _, want := range []string{ "From: enkly \r\n", "To: to@example.com\r\n", "Subject: Subject line\r\n", "\r\nbody text\r\n", } { if !strings.Contains(msg, want) { t.Errorf("buildMessage output missing %q; got:\n%s", want, msg) } } }