mirror of
https://github.com/wneessen/go-mail.git
synced 2024-11-23 06:10:48 +01:00
Add QuickSend function and unit tests
Introduce the QuickSend function for sending emails quickly with TLS and optional SMTP authentication. Added comprehensive unit tests to ensure QuickSend works correctly with different authentication mechanisms and handles various error scenarios.
This commit is contained in:
parent
25a0fb23a9
commit
d30a4a73c6
2 changed files with 480 additions and 0 deletions
112
quicksend.go
Normal file
112
quicksend.go
Normal file
|
@ -0,0 +1,112 @@
|
||||||
|
// SPDX-FileCopyrightText: 2024 The go-mail Authors
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package mail
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/tls"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
type AuthData struct {
|
||||||
|
Auth bool
|
||||||
|
Username string
|
||||||
|
Password string
|
||||||
|
}
|
||||||
|
|
||||||
|
var testHookTLSConfig func() *tls.Config // nil, except for tests
|
||||||
|
|
||||||
|
// QuickSend is an all-in-one method for quickly sending simple text mails in go-mail.
|
||||||
|
//
|
||||||
|
// This method will create a new client that connects to the server at addr, switches to TLS if possible,
|
||||||
|
// authenticates with the optional AuthData provided in auth and create a new simple Msg with the provided
|
||||||
|
// subject string and message bytes as body. The message will be sent using from as sender address and will
|
||||||
|
// be delivered to every address in rcpts. QuickSend will always send as text/plain ContentType.
|
||||||
|
//
|
||||||
|
// For the SMTP authentication, if auth is not nil and AuthData.Auth is set to true, it will try to
|
||||||
|
// autodiscover the best SMTP authentication mechanism supported by the server. If auth is set to true
|
||||||
|
// but autodiscover is not able to find a suitable authentication mechanism or if the authentication
|
||||||
|
// fails, the mail delivery will fail completely.
|
||||||
|
//
|
||||||
|
// The content parameter should be an RFC 822-style email body. The lines of content should be CRLF terminated.
|
||||||
|
//
|
||||||
|
// Parameters:
|
||||||
|
// - addr: The hostname and port of the mail server, it must include a port, as in "mail.example.com:smtp".
|
||||||
|
// - auth: A AuthData pointer. If nil or if AuthData.Auth is set to false, not SMTP authentication will be performed.
|
||||||
|
// - from: The from address of the sender as string.
|
||||||
|
// - rcpts: A slice of strings of receipient addresses.
|
||||||
|
// - subject: The subject line as string.
|
||||||
|
// - content: A byte slice of the mail content
|
||||||
|
// - opts: Optional parameters for customizing the body part.
|
||||||
|
//
|
||||||
|
// Returns:
|
||||||
|
// - A pointer to the generated Msg.
|
||||||
|
// - An error if any step in the process of mail generation or delivery failed.
|
||||||
|
func QuickSend(addr string, auth *AuthData, from string, rcpts []string, subject string, content []byte) (*Msg, error) {
|
||||||
|
host, port, err := net.SplitHostPort(addr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to split host and port from address: %w", err)
|
||||||
|
}
|
||||||
|
portnum, err := strconv.Atoi(port)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to convert port to int: %w", err)
|
||||||
|
}
|
||||||
|
client, err := NewClient(host, WithPort(portnum), WithTLSPolicy(TLSOpportunistic))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create new client: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if auth != nil && auth.Auth {
|
||||||
|
client.SetSMTPAuth(SMTPAuthAutoDiscover)
|
||||||
|
client.SetUsername(auth.Username)
|
||||||
|
client.SetPassword(auth.Password)
|
||||||
|
}
|
||||||
|
|
||||||
|
tlsConfig := client.tlsconfig
|
||||||
|
if testHookTLSConfig != nil {
|
||||||
|
tlsConfig = testHookTLSConfig()
|
||||||
|
}
|
||||||
|
if err = client.SetTLSConfig(tlsConfig); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to set TLS config: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
message := NewMsg()
|
||||||
|
if err = message.From(from); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to set MAIL FROM address: %w", err)
|
||||||
|
}
|
||||||
|
if err = message.To(rcpts...); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to set RCPT TO address: %w", err)
|
||||||
|
}
|
||||||
|
message.Subject(subject)
|
||||||
|
buffer := bytes.NewBuffer(content)
|
||||||
|
writeFunc := writeFuncFromBuffer(buffer)
|
||||||
|
message.SetBodyWriter(TypeTextPlain, writeFunc)
|
||||||
|
|
||||||
|
if err = client.DialAndSend(message); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to dial and send message: %w", err)
|
||||||
|
}
|
||||||
|
return message, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewAuthData creates a new AuthData instance with the provided username and password.
|
||||||
|
//
|
||||||
|
// This function initializes an AuthData struct with authentication enabled and sets the
|
||||||
|
// username and password fields.
|
||||||
|
//
|
||||||
|
// Parameters:
|
||||||
|
// - user: The username for authentication.
|
||||||
|
// - pass: The password for authentication.
|
||||||
|
//
|
||||||
|
// Returns:
|
||||||
|
// - A pointer to the initialized AuthData instance.
|
||||||
|
func NewAuthData(user, pass string) *AuthData {
|
||||||
|
return &AuthData{
|
||||||
|
Auth: true,
|
||||||
|
Username: user,
|
||||||
|
Password: pass,
|
||||||
|
}
|
||||||
|
}
|
368
quicksend_test.go
Normal file
368
quicksend_test.go
Normal file
|
@ -0,0 +1,368 @@
|
||||||
|
// SPDX-FileCopyrightText: 2024 The go-mail Authors
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package mail
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNewAuthData(t *testing.T) {
|
||||||
|
t.Run("AuthData with username and password", func(t *testing.T) {
|
||||||
|
auth := NewAuthData("username", "password")
|
||||||
|
if !auth.Auth {
|
||||||
|
t.Fatal("expected auth to be true")
|
||||||
|
}
|
||||||
|
if auth.Username != "username" {
|
||||||
|
t.Fatalf("expected username to be %s, got %s", "username", auth.Username)
|
||||||
|
}
|
||||||
|
if auth.Password != "password" {
|
||||||
|
t.Fatalf("expected password to be %s, got %s", "password", auth.Password)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
t.Run("AuthData with username and empty password", func(t *testing.T) {
|
||||||
|
auth := NewAuthData("username", "")
|
||||||
|
if !auth.Auth {
|
||||||
|
t.Fatal("expected auth to be true")
|
||||||
|
}
|
||||||
|
if auth.Username != "username" {
|
||||||
|
t.Fatalf("expected username to be %s, got %s", "username", auth.Username)
|
||||||
|
}
|
||||||
|
if auth.Password != "" {
|
||||||
|
t.Fatalf("expected password to be %s, got %s", "", auth.Password)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
t.Run("AuthData with empty username and set password", func(t *testing.T) {
|
||||||
|
auth := NewAuthData("", "password")
|
||||||
|
if !auth.Auth {
|
||||||
|
t.Fatal("expected auth to be true")
|
||||||
|
}
|
||||||
|
if auth.Username != "" {
|
||||||
|
t.Fatalf("expected username to be %s, got %s", "", auth.Username)
|
||||||
|
}
|
||||||
|
if auth.Password != "password" {
|
||||||
|
t.Fatalf("expected password to be %s, got %s", "password", auth.Password)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
t.Run("AuthData with empty data", func(t *testing.T) {
|
||||||
|
auth := NewAuthData("", "")
|
||||||
|
if !auth.Auth {
|
||||||
|
t.Fatal("expected auth to be true")
|
||||||
|
}
|
||||||
|
if auth.Username != "" {
|
||||||
|
t.Fatalf("expected username to be %s, got %s", "", auth.Username)
|
||||||
|
}
|
||||||
|
if auth.Password != "" {
|
||||||
|
t.Fatalf("expected password to be %s, got %s", "", auth.Password)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestQuickSend(t *testing.T) {
|
||||||
|
subject := "This is a test subject"
|
||||||
|
body := []byte("This is a test body\r\nWith multiple lines\r\n\r\nBest,\r\n The go-mail team")
|
||||||
|
sender := TestSenderValid
|
||||||
|
rcpts := []string{TestRcptValid}
|
||||||
|
t.Run("QuickSend with authentication and TLS", func(t *testing.T) {
|
||||||
|
ctxAuth, cancelAuth := context.WithCancel(context.Background())
|
||||||
|
defer cancelAuth()
|
||||||
|
PortAdder.Add(1)
|
||||||
|
serverPort := int(TestServerPortBase + PortAdder.Load())
|
||||||
|
featureSet := "250-AUTH PLAIN\r\n250-8BITMIME\r\n250-DSN\r\n250-STARTTLS\r\n250 SMTPUTF8"
|
||||||
|
echoBuffer := bytes.NewBuffer(nil)
|
||||||
|
props := &serverProps{
|
||||||
|
EchoBuffer: echoBuffer,
|
||||||
|
FeatureSet: featureSet,
|
||||||
|
ListenPort: serverPort,
|
||||||
|
}
|
||||||
|
go func() {
|
||||||
|
if err := simpleSMTPServer(ctxAuth, t, props); err != nil {
|
||||||
|
t.Errorf("failed to start test server: %s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
time.Sleep(time.Millisecond * 30)
|
||||||
|
addr := TestServerAddr + ":" + fmt.Sprint(serverPort)
|
||||||
|
testHookTLSConfig = func() *tls.Config { return &tls.Config{InsecureSkipVerify: true} }
|
||||||
|
|
||||||
|
_, err := QuickSend(addr, NewAuthData("username", "password"), sender, rcpts, subject, body)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to send email: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
props.BufferMutex.RLock()
|
||||||
|
resp := strings.Split(echoBuffer.String(), "\r\n")
|
||||||
|
props.BufferMutex.RUnlock()
|
||||||
|
|
||||||
|
expects := []struct {
|
||||||
|
line int
|
||||||
|
data string
|
||||||
|
}{
|
||||||
|
{8, "STARTTLS"},
|
||||||
|
{17, "AUTH PLAIN AHVzZXJuYW1lAHBhc3N3b3Jk"},
|
||||||
|
{21, "MAIL FROM:<valid-from@domain.tld> BODY=8BITMIME SMTPUTF8"},
|
||||||
|
{23, "RCPT TO:<valid-to@domain.tld>"},
|
||||||
|
{30, "Subject: " + subject},
|
||||||
|
{33, "From: <valid-from@domain.tld>"},
|
||||||
|
{34, "To: <valid-to@domain.tld>"},
|
||||||
|
{35, "Content-Type: text/plain; charset=UTF-8"},
|
||||||
|
{36, "Content-Transfer-Encoding: quoted-printable"},
|
||||||
|
{38, "This is a test body"},
|
||||||
|
{39, "With multiple lines"},
|
||||||
|
{40, ""},
|
||||||
|
{41, "Best,"},
|
||||||
|
{42, " The go-mail team"},
|
||||||
|
}
|
||||||
|
for _, expect := range expects {
|
||||||
|
if !strings.EqualFold(resp[expect.line], expect.data) {
|
||||||
|
t.Errorf("expected %q at line %d, got: %q", expect.data, expect.line, resp[expect.line])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
t.Run("QuickSend with authentication and TLS and multiple receipients", func(t *testing.T) {
|
||||||
|
ctxAuth, cancelAuth := context.WithCancel(context.Background())
|
||||||
|
defer cancelAuth()
|
||||||
|
PortAdder.Add(1)
|
||||||
|
serverPort := int(TestServerPortBase + PortAdder.Load())
|
||||||
|
featureSet := "250-AUTH PLAIN\r\n250-8BITMIME\r\n250-DSN\r\n250-STARTTLS\r\n250 SMTPUTF8"
|
||||||
|
echoBuffer := bytes.NewBuffer(nil)
|
||||||
|
props := &serverProps{
|
||||||
|
EchoBuffer: echoBuffer,
|
||||||
|
FeatureSet: featureSet,
|
||||||
|
ListenPort: serverPort,
|
||||||
|
}
|
||||||
|
go func() {
|
||||||
|
if err := simpleSMTPServer(ctxAuth, t, props); err != nil {
|
||||||
|
t.Errorf("failed to start test server: %s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
time.Sleep(time.Millisecond * 30)
|
||||||
|
addr := TestServerAddr + ":" + fmt.Sprint(serverPort)
|
||||||
|
testHookTLSConfig = func() *tls.Config { return &tls.Config{InsecureSkipVerify: true} }
|
||||||
|
|
||||||
|
multiRcpts := []string{TestRcptValid, TestRcptValid, TestRcptValid}
|
||||||
|
_, err := QuickSend(addr, NewAuthData("username", "password"), sender, multiRcpts, subject, body)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to send email: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
props.BufferMutex.RLock()
|
||||||
|
resp := strings.Split(echoBuffer.String(), "\r\n")
|
||||||
|
props.BufferMutex.RUnlock()
|
||||||
|
|
||||||
|
expects := []struct {
|
||||||
|
line int
|
||||||
|
data string
|
||||||
|
}{
|
||||||
|
{8, "STARTTLS"},
|
||||||
|
{17, "AUTH PLAIN AHVzZXJuYW1lAHBhc3N3b3Jk"},
|
||||||
|
{21, "MAIL FROM:<valid-from@domain.tld> BODY=8BITMIME SMTPUTF8"},
|
||||||
|
{23, "RCPT TO:<valid-to@domain.tld>"},
|
||||||
|
{25, "RCPT TO:<valid-to@domain.tld>"},
|
||||||
|
{27, "RCPT TO:<valid-to@domain.tld>"},
|
||||||
|
{34, "Subject: " + subject},
|
||||||
|
{37, "From: <valid-from@domain.tld>"},
|
||||||
|
{38, "To: <valid-to@domain.tld>, <valid-to@domain.tld>, <valid-to@domain.tld>"},
|
||||||
|
{39, "Content-Type: text/plain; charset=UTF-8"},
|
||||||
|
{40, "Content-Transfer-Encoding: quoted-printable"},
|
||||||
|
{42, "This is a test body"},
|
||||||
|
{43, "With multiple lines"},
|
||||||
|
{44, ""},
|
||||||
|
{45, "Best,"},
|
||||||
|
{46, " The go-mail team"},
|
||||||
|
}
|
||||||
|
for _, expect := range expects {
|
||||||
|
if !strings.EqualFold(resp[expect.line], expect.data) {
|
||||||
|
t.Errorf("expected %q at line %d, got: %q", expect.data, expect.line, resp[expect.line])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
t.Run("QuickSend uses stronged authentication method", func(t *testing.T) {
|
||||||
|
ctxAuth, cancelAuth := context.WithCancel(context.Background())
|
||||||
|
defer cancelAuth()
|
||||||
|
PortAdder.Add(1)
|
||||||
|
serverPort := int(TestServerPortBase + PortAdder.Load())
|
||||||
|
featureSet := "250-AUTH PLAIN CRAM-MD5 SCRAM-SHA-256-PLUS SCRAM-SHA-256\r\n250-8BITMIME\r\n250-DSN\r\n250-STARTTLS\r\n250 SMTPUTF8"
|
||||||
|
echoBuffer := bytes.NewBuffer(nil)
|
||||||
|
props := &serverProps{
|
||||||
|
EchoBuffer: echoBuffer,
|
||||||
|
FeatureSet: featureSet,
|
||||||
|
ListenPort: serverPort,
|
||||||
|
}
|
||||||
|
go func() {
|
||||||
|
if err := simpleSMTPServer(ctxAuth, t, props); err != nil {
|
||||||
|
t.Errorf("failed to start test server: %s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
time.Sleep(time.Millisecond * 30)
|
||||||
|
addr := TestServerAddr + ":" + fmt.Sprint(serverPort)
|
||||||
|
testHookTLSConfig = func() *tls.Config { return &tls.Config{InsecureSkipVerify: true} }
|
||||||
|
|
||||||
|
_, err := QuickSend(addr, NewAuthData("username", "password"), sender, rcpts, subject, body)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to send email: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
props.BufferMutex.RLock()
|
||||||
|
resp := strings.Split(echoBuffer.String(), "\r\n")
|
||||||
|
props.BufferMutex.RUnlock()
|
||||||
|
|
||||||
|
expects := []struct {
|
||||||
|
line int
|
||||||
|
data string
|
||||||
|
}{
|
||||||
|
{17, "AUTH SCRAM-SHA-256-PLUS"},
|
||||||
|
}
|
||||||
|
for _, expect := range expects {
|
||||||
|
if !strings.EqualFold(resp[expect.line], expect.data) {
|
||||||
|
t.Errorf("expected %q at line %d, got: %q", expect.data, expect.line, resp[expect.line])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
t.Run("QuickSend uses stronged authentication method without TLS", func(t *testing.T) {
|
||||||
|
ctxAuth, cancelAuth := context.WithCancel(context.Background())
|
||||||
|
defer cancelAuth()
|
||||||
|
PortAdder.Add(1)
|
||||||
|
serverPort := int(TestServerPortBase + PortAdder.Load())
|
||||||
|
featureSet := "250-AUTH PLAIN CRAM-MD5 SCRAM-SHA-256-PLUS SCRAM-SHA-256\r\n250-8BITMIME\r\n250-DSN\r\n250 SMTPUTF8"
|
||||||
|
echoBuffer := bytes.NewBuffer(nil)
|
||||||
|
props := &serverProps{
|
||||||
|
EchoBuffer: echoBuffer,
|
||||||
|
FeatureSet: featureSet,
|
||||||
|
ListenPort: serverPort,
|
||||||
|
}
|
||||||
|
go func() {
|
||||||
|
if err := simpleSMTPServer(ctxAuth, t, props); err != nil {
|
||||||
|
t.Errorf("failed to start test server: %s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
time.Sleep(time.Millisecond * 30)
|
||||||
|
addr := TestServerAddr + ":" + fmt.Sprint(serverPort)
|
||||||
|
testHookTLSConfig = func() *tls.Config { return &tls.Config{InsecureSkipVerify: true} }
|
||||||
|
|
||||||
|
_, err := QuickSend(addr, NewAuthData("username", "password"), sender, rcpts, subject, body)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to send email: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
props.BufferMutex.RLock()
|
||||||
|
resp := strings.Split(echoBuffer.String(), "\r\n")
|
||||||
|
props.BufferMutex.RUnlock()
|
||||||
|
|
||||||
|
expects := []struct {
|
||||||
|
line int
|
||||||
|
data string
|
||||||
|
}{
|
||||||
|
{7, "AUTH SCRAM-SHA-256"},
|
||||||
|
}
|
||||||
|
for _, expect := range expects {
|
||||||
|
if !strings.EqualFold(resp[expect.line], expect.data) {
|
||||||
|
t.Errorf("expected %q at line %d, got: %q", expect.data, expect.line, resp[expect.line])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
t.Run("QuickSend fails during DialAndSned", func(t *testing.T) {
|
||||||
|
ctxAuth, cancelAuth := context.WithCancel(context.Background())
|
||||||
|
defer cancelAuth()
|
||||||
|
PortAdder.Add(1)
|
||||||
|
serverPort := int(TestServerPortBase + PortAdder.Load())
|
||||||
|
featureSet := "250-AUTH PLAIN CRAM-MD5 SCRAM-SHA-256-PLUS SCRAM-SHA-256\r\n250-8BITMIME\r\n250-DSN\r\n250 SMTPUTF8"
|
||||||
|
props := &serverProps{
|
||||||
|
FailOnMailFrom: true,
|
||||||
|
FeatureSet: featureSet,
|
||||||
|
ListenPort: serverPort,
|
||||||
|
}
|
||||||
|
go func() {
|
||||||
|
if err := simpleSMTPServer(ctxAuth, t, props); err != nil {
|
||||||
|
t.Errorf("failed to start test server: %s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
time.Sleep(time.Millisecond * 30)
|
||||||
|
addr := TestServerAddr + ":" + fmt.Sprint(serverPort)
|
||||||
|
testHookTLSConfig = func() *tls.Config { return &tls.Config{InsecureSkipVerify: true} }
|
||||||
|
|
||||||
|
_, err := QuickSend(addr, NewAuthData("username", "password"), sender, rcpts, subject, body)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("expected QuickSend to fail during DialAndSend")
|
||||||
|
}
|
||||||
|
expect := `failed to dial and send message: send failed: sending SMTP MAIL FROM command: 500 ` +
|
||||||
|
`5.5.2 Error: fail on MAIL FROM`
|
||||||
|
if !strings.EqualFold(err.Error(), expect) {
|
||||||
|
t.Errorf("expected error to contain %s, got %s", expect, err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
t.Run("QuickSend fails on server address without port", func(t *testing.T) {
|
||||||
|
addr := TestServerAddr
|
||||||
|
_, err := QuickSend(addr, NewAuthData("username", "password"), sender, rcpts, subject, body)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("expected QuickSend to fail with invalid server address")
|
||||||
|
}
|
||||||
|
expect := "failed to split host and port from address: address 127.0.0.1: missing port in address"
|
||||||
|
if !strings.Contains(err.Error(), expect) {
|
||||||
|
t.Errorf("expected error to contain %s, got %s", expect, err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
t.Run("QuickSend fails on server address with invalid port", func(t *testing.T) {
|
||||||
|
addr := TestServerAddr + ":invalid"
|
||||||
|
_, err := QuickSend(addr, NewAuthData("username", "password"), sender, rcpts, subject, body)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("expected QuickSend to fail with invalid server port")
|
||||||
|
}
|
||||||
|
expect := `failed to convert port to int: strconv.Atoi: parsing "invalid": invalid syntax`
|
||||||
|
if !strings.Contains(err.Error(), expect) {
|
||||||
|
t.Errorf("expected error to contain %s, got %s", expect, err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
t.Run("QuickSend fails on nil TLS config (test hook only)", func(t *testing.T) {
|
||||||
|
addr := TestServerAddr + ":587"
|
||||||
|
testHookTLSConfig = func() *tls.Config { return nil }
|
||||||
|
defer func() {
|
||||||
|
testHookTLSConfig = nil
|
||||||
|
}()
|
||||||
|
_, err := QuickSend(addr, NewAuthData("username", "password"), sender, rcpts, subject, body)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("expected QuickSend to fail with nil-tlsConfig")
|
||||||
|
}
|
||||||
|
expect := `failed to set TLS config: invalid TLS config`
|
||||||
|
if !strings.Contains(err.Error(), expect) {
|
||||||
|
t.Errorf("expected error to contain %s, got %s", expect, err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
t.Run("QuickSend fails with invalid from address", func(t *testing.T) {
|
||||||
|
addr := TestServerAddr + ":587"
|
||||||
|
invalid := "invalid-fromdomain.tld"
|
||||||
|
_, err := QuickSend(addr, NewAuthData("username", "password"), invalid, rcpts, subject, body)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("expected QuickSend to fail with invalid from address")
|
||||||
|
}
|
||||||
|
expect := `failed to set MAIL FROM address: failed to parse mail address "invalid-fromdomain.tld": ` +
|
||||||
|
`mail: missing '@' or angle-addr`
|
||||||
|
if !strings.Contains(err.Error(), expect) {
|
||||||
|
t.Errorf("expected error to contain %s, got %s", expect, err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
t.Run("QuickSend fails with invalid from address", func(t *testing.T) {
|
||||||
|
addr := TestServerAddr + ":587"
|
||||||
|
invalid := []string{"invalid-todomain.tld"}
|
||||||
|
_, err := QuickSend(addr, NewAuthData("username", "password"), sender, invalid, subject, body)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("expected QuickSend to fail with invalid to address")
|
||||||
|
}
|
||||||
|
expect := `failed to set RCPT TO address: failed to parse mail address "invalid-todomain.tld": ` +
|
||||||
|
`mail: missing '@' or angle-add`
|
||||||
|
if !strings.Contains(err.Error(), expect) {
|
||||||
|
t.Errorf("expected error to contain %s, got %s", expect, err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
Loading…
Reference in a new issue