mirror of
https://github.com/wneessen/go-mail.git
synced 2024-12-23 02:50:39 +01:00
Merge pull request #91 from wneessen/feature/90_provide-a-way-of-knowing-which-emails-failedsucceeded-in-sending
Introduction of new error type for sending errors
This commit is contained in:
commit
3b3c1e6e8d
7 changed files with 430 additions and 38 deletions
|
@ -7,89 +7,118 @@
|
|||
|
||||
package mail
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Send sends out the mail message
|
||||
func (c *Client) Send(ml ...*Msg) error {
|
||||
var errs []error
|
||||
if err := c.checkConn(); err != nil {
|
||||
return fmt.Errorf("failed to send mail: %w", err)
|
||||
if cerr := c.checkConn(); cerr != nil {
|
||||
return &SendError{Reason: ErrConnCheck, errlist: []error{cerr}, isTemp: isTempError(cerr)}
|
||||
}
|
||||
var errs []*SendError
|
||||
for _, m := range ml {
|
||||
m.sendError = nil
|
||||
if m.encoding == NoEncoding {
|
||||
if ok, _ := c.sc.Extension("8BITMIME"); !ok {
|
||||
errs = append(errs, ErrServerNoUnencoded)
|
||||
se := &SendError{Reason: ErrNoUnencoded, isTemp: false}
|
||||
m.sendError = se
|
||||
errs = append(errs, se)
|
||||
continue
|
||||
}
|
||||
}
|
||||
f, err := m.GetSender(false)
|
||||
if err != nil {
|
||||
errs = append(errs, err)
|
||||
se := &SendError{Reason: ErrGetSender, errlist: []error{err}, isTemp: isTempError(err)}
|
||||
m.sendError = se
|
||||
errs = append(errs, se)
|
||||
continue
|
||||
}
|
||||
rl, err := m.GetRecipients()
|
||||
if err != nil {
|
||||
errs = append(errs, err)
|
||||
se := &SendError{Reason: ErrGetRcpts, errlist: []error{err}, isTemp: isTempError(err)}
|
||||
m.sendError = se
|
||||
errs = append(errs, se)
|
||||
continue
|
||||
}
|
||||
|
||||
if err := c.mail(f); err != nil {
|
||||
errs = append(errs, fmt.Errorf("sending MAIL FROM command failed: %w", err))
|
||||
se := &SendError{Reason: ErrSMTPMailFrom, errlist: []error{err}, isTemp: isTempError(err)}
|
||||
if reserr := c.sc.Reset(); reserr != nil {
|
||||
errs = append(errs, reserr)
|
||||
se.errlist = append(se.errlist, reserr)
|
||||
}
|
||||
m.sendError = se
|
||||
errs = append(errs, se)
|
||||
continue
|
||||
}
|
||||
failed := false
|
||||
rse := &SendError{}
|
||||
rse.errlist = make([]error, 0)
|
||||
rse.rcpt = make([]string, 0)
|
||||
for _, r := range rl {
|
||||
if err := c.rcpt(r); err != nil {
|
||||
errs = append(errs, fmt.Errorf("sending RCPT TO command failed: %w", err))
|
||||
rse.Reason = ErrSMTPRcptTo
|
||||
rse.errlist = append(rse.errlist, err)
|
||||
rse.rcpt = append(rse.rcpt, r)
|
||||
rse.isTemp = isTempError(err)
|
||||
failed = true
|
||||
}
|
||||
}
|
||||
if failed {
|
||||
if reserr := c.sc.Reset(); reserr != nil {
|
||||
errs = append(errs, reserr)
|
||||
rse.errlist = append(rse.errlist, err)
|
||||
}
|
||||
m.sendError = rse
|
||||
errs = append(errs, rse)
|
||||
continue
|
||||
}
|
||||
w, err := c.sc.Data()
|
||||
if err != nil {
|
||||
errs = append(errs, fmt.Errorf("sending DATA command failed: %w", err))
|
||||
se := &SendError{Reason: ErrSMTPData, errlist: []error{err}, isTemp: isTempError(err)}
|
||||
m.sendError = se
|
||||
errs = append(errs, se)
|
||||
continue
|
||||
}
|
||||
_, err = m.WriteTo(w)
|
||||
if err != nil {
|
||||
errs = append(errs, fmt.Errorf("sending mail content failed: %w", err))
|
||||
se := &SendError{Reason: ErrWriteContent, errlist: []error{err}, isTemp: isTempError(err)}
|
||||
m.sendError = se
|
||||
errs = append(errs, se)
|
||||
continue
|
||||
}
|
||||
|
||||
if err := w.Close(); err != nil {
|
||||
errs = append(errs, fmt.Errorf("failed to close DATA writer: %w", err))
|
||||
se := &SendError{Reason: ErrSMTPDataClose, errlist: []error{err}, isTemp: isTempError(err)}
|
||||
m.sendError = se
|
||||
errs = append(errs, se)
|
||||
continue
|
||||
}
|
||||
|
||||
if err := c.Reset(); err != nil {
|
||||
errs = append(errs, fmt.Errorf("sending RSET command failed: %w", err))
|
||||
se := &SendError{Reason: ErrSMTPReset, errlist: []error{err}, isTemp: isTempError(err)}
|
||||
m.sendError = se
|
||||
errs = append(errs, se)
|
||||
continue
|
||||
}
|
||||
if err := c.checkConn(); err != nil {
|
||||
errs = append(errs, fmt.Errorf("failed to check server connection: %w", err))
|
||||
se := &SendError{Reason: ErrConnCheck, errlist: []error{err}, isTemp: isTempError(err)}
|
||||
m.sendError = se
|
||||
errs = append(errs, se)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if len(errs) > 0 {
|
||||
errtxt := ""
|
||||
for i := range errs {
|
||||
errtxt += fmt.Sprintf("%s", errs[i])
|
||||
if i < len(errs) {
|
||||
errtxt += "\n"
|
||||
if len(errs) > 1 {
|
||||
re := &SendError{Reason: ErrAmbiguous}
|
||||
for i := range errs {
|
||||
re.errlist = append(re.errlist, errs[i].errlist...)
|
||||
re.rcpt = append(re.rcpt, errs[i].rcpt...)
|
||||
}
|
||||
|
||||
// We assume that the isTemp flag from the last error we received should be the
|
||||
// indicator for the returned isTemp flag as well
|
||||
re.isTemp = errs[len(errs)-1].isTemp
|
||||
|
||||
return re
|
||||
}
|
||||
return fmt.Errorf("%s", errtxt)
|
||||
return errs[0]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -9,44 +9,54 @@ package mail
|
|||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Send sends out the mail message
|
||||
func (c *Client) Send(ml ...*Msg) (rerr error) {
|
||||
if err := c.checkConn(); err != nil {
|
||||
rerr = fmt.Errorf("failed to send mail: %w", err)
|
||||
rerr = &SendError{Reason: ErrConnCheck, errlist: []error{err}, isTemp: isTempError(err)}
|
||||
return
|
||||
}
|
||||
for _, m := range ml {
|
||||
m.sendError = nil
|
||||
if m.encoding == NoEncoding {
|
||||
if ok, _ := c.sc.Extension("8BITMIME"); !ok {
|
||||
rerr = errors.Join(rerr, ErrServerNoUnencoded)
|
||||
m.sendError = &SendError{Reason: ErrNoUnencoded, isTemp: false}
|
||||
rerr = errors.Join(rerr, m.sendError)
|
||||
continue
|
||||
}
|
||||
}
|
||||
f, err := m.GetSender(false)
|
||||
if err != nil {
|
||||
rerr = errors.Join(rerr, err)
|
||||
m.sendError = &SendError{Reason: ErrGetSender, errlist: []error{err}, isTemp: isTempError(err)}
|
||||
rerr = errors.Join(rerr, m.sendError)
|
||||
continue
|
||||
}
|
||||
rl, err := m.GetRecipients()
|
||||
if err != nil {
|
||||
rerr = errors.Join(rerr, err)
|
||||
m.sendError = &SendError{Reason: ErrGetRcpts, errlist: []error{err}, isTemp: isTempError(err)}
|
||||
rerr = errors.Join(rerr, m.sendError)
|
||||
continue
|
||||
}
|
||||
|
||||
if err := c.mail(f); err != nil {
|
||||
rerr = errors.Join(rerr, fmt.Errorf("sending MAIL FROM command failed: %w", err))
|
||||
m.sendError = &SendError{Reason: ErrSMTPMailFrom, errlist: []error{err}, isTemp: isTempError(err)}
|
||||
rerr = errors.Join(rerr, m.sendError)
|
||||
if reserr := c.sc.Reset(); reserr != nil {
|
||||
rerr = errors.Join(rerr, reserr)
|
||||
}
|
||||
continue
|
||||
}
|
||||
failed := false
|
||||
rse := &SendError{}
|
||||
rse.errlist = make([]error, 0)
|
||||
rse.rcpt = make([]string, 0)
|
||||
for _, r := range rl {
|
||||
if err := c.rcpt(r); err != nil {
|
||||
rerr = errors.Join(rerr, fmt.Errorf("sending RCPT TO command failed: %w", err))
|
||||
rse.Reason = ErrSMTPRcptTo
|
||||
rse.errlist = append(rse.errlist, err)
|
||||
rse.rcpt = append(rse.rcpt, r)
|
||||
rse.isTemp = isTempError(err)
|
||||
failed = true
|
||||
}
|
||||
}
|
||||
|
@ -54,30 +64,37 @@ func (c *Client) Send(ml ...*Msg) (rerr error) {
|
|||
if reserr := c.sc.Reset(); reserr != nil {
|
||||
rerr = errors.Join(rerr, reserr)
|
||||
}
|
||||
m.sendError = rse
|
||||
rerr = errors.Join(rerr, m.sendError)
|
||||
continue
|
||||
}
|
||||
w, err := c.sc.Data()
|
||||
if err != nil {
|
||||
rerr = errors.Join(rerr, fmt.Errorf("sending DATA command failed: %w", err))
|
||||
m.sendError = &SendError{Reason: ErrSMTPData, errlist: []error{err}, isTemp: isTempError(err)}
|
||||
rerr = errors.Join(rerr, m.sendError)
|
||||
continue
|
||||
}
|
||||
_, err = m.WriteTo(w)
|
||||
if err != nil {
|
||||
rerr = errors.Join(rerr, fmt.Errorf("sending mail content failed: %w", err))
|
||||
m.sendError = &SendError{Reason: ErrWriteContent, errlist: []error{err}, isTemp: isTempError(err)}
|
||||
rerr = errors.Join(rerr, m.sendError)
|
||||
continue
|
||||
}
|
||||
|
||||
if err := w.Close(); err != nil {
|
||||
rerr = errors.Join(rerr, fmt.Errorf("failed to close DATA writer: %w", err))
|
||||
m.sendError = &SendError{Reason: ErrSMTPDataClose, errlist: []error{err}, isTemp: isTempError(err)}
|
||||
rerr = errors.Join(rerr, m.sendError)
|
||||
continue
|
||||
}
|
||||
|
||||
if err := c.Reset(); err != nil {
|
||||
rerr = errors.Join(rerr, fmt.Errorf("sending RSET command failed: %w", err))
|
||||
m.sendError = &SendError{Reason: ErrSMTPReset, errlist: []error{err}, isTemp: isTempError(err)}
|
||||
rerr = errors.Join(rerr, m.sendError)
|
||||
continue
|
||||
}
|
||||
if err := c.checkConn(); err != nil {
|
||||
rerr = errors.Join(rerr, fmt.Errorf("failed to check server connection: %w", err))
|
||||
m.sendError = &SendError{Reason: ErrConnCheck, errlist: []error{err}, isTemp: isTempError(err)}
|
||||
rerr = errors.Join(rerr, m.sendError)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -7,6 +7,7 @@ package mail
|
|||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/smtp"
|
||||
"os"
|
||||
|
@ -989,6 +990,93 @@ func TestValidateLine(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
// TestClient_Send_MsgSendError tests the Client.Send method with a broken recipient and verifies
|
||||
// that the SendError type works properly
|
||||
func TestClient_Send_MsgSendError(t *testing.T) {
|
||||
if os.Getenv("TEST_ALLOW_SEND") == "" {
|
||||
t.Skipf("TEST_ALLOW_SEND is not set. Skipping mail sending test")
|
||||
}
|
||||
var msgs []*Msg
|
||||
rcpts := []string{"invalid@domain.tld", "invalid@address.invalid"}
|
||||
for _, rcpt := range rcpts {
|
||||
m := NewMsg()
|
||||
_ = m.FromFormat("go-mail Test Mailer", os.Getenv("TEST_FROM"))
|
||||
_ = m.To(rcpt)
|
||||
m.Subject(fmt.Sprintf("This is a test mail from go-mail/v%s", VERSION))
|
||||
m.SetBulk()
|
||||
m.SetDate()
|
||||
m.SetMessageID()
|
||||
m.SetBodyString(TypeTextPlain, "This is a test mail from the go-mail library")
|
||||
msgs = append(msgs, m)
|
||||
}
|
||||
|
||||
c, err := getTestConnection(true)
|
||||
if err != nil {
|
||||
t.Skipf("failed to create test client: %s. Skipping tests", err)
|
||||
}
|
||||
|
||||
ctx, cfn := context.WithTimeout(context.Background(), DefaultTimeout)
|
||||
defer cfn()
|
||||
if err := c.DialWithContext(ctx); err != nil {
|
||||
t.Errorf("failed to dial to sending server: %s", err)
|
||||
}
|
||||
if err := c.Send(msgs...); err == nil {
|
||||
t.Errorf("sending messages with broken recipients was supposed to fail but didn't")
|
||||
}
|
||||
if err := c.Close(); err != nil {
|
||||
t.Errorf("failed to close client connection: %s", err)
|
||||
}
|
||||
for _, m := range msgs {
|
||||
if !m.HasSendError() {
|
||||
t.Errorf("message was expected to have a send error, but didn't")
|
||||
}
|
||||
se := &SendError{Reason: ErrSMTPRcptTo}
|
||||
if !errors.Is(m.SendError(), se) {
|
||||
t.Errorf("error mismatch, expected: %s, got: %s", se, m.SendError())
|
||||
}
|
||||
if m.SendErrorIsTemp() {
|
||||
t.Errorf("message was not expected to be a temporary error, but reported as such")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestClient_DialAndSendWithContext_withSendError tests the Client.DialAndSendWithContext method
|
||||
// with a broken recipient to make sure that the returned error satisfies the Msg.SendError type
|
||||
func TestClient_DialAndSendWithContext_withSendError(t *testing.T) {
|
||||
if os.Getenv("TEST_ALLOW_SEND") == "" {
|
||||
t.Skipf("TEST_ALLOW_SEND is not set. Skipping mail sending test")
|
||||
}
|
||||
m := NewMsg()
|
||||
_ = m.FromFormat("go-mail Test Mailer", os.Getenv("TEST_FROM"))
|
||||
_ = m.To("invalid@domain.tld")
|
||||
m.Subject(fmt.Sprintf("This is a test mail from go-mail/v%s", VERSION))
|
||||
m.SetBulk()
|
||||
m.SetDate()
|
||||
m.SetMessageID()
|
||||
m.SetBodyString(TypeTextPlain, "This is a test mail from the go-mail library")
|
||||
|
||||
c, err := getTestConnection(true)
|
||||
if err != nil {
|
||||
t.Skipf("failed to create test client: %s. Skipping tests", err)
|
||||
}
|
||||
ctx, cfn := context.WithTimeout(context.Background(), DefaultTimeout)
|
||||
defer cfn()
|
||||
err = c.DialAndSendWithContext(ctx, m)
|
||||
if err == nil {
|
||||
t.Errorf("expected DialAndSendWithContext with broken mail recipient to fail, but didn't")
|
||||
return
|
||||
}
|
||||
var se *SendError
|
||||
if !errors.As(err, &se) {
|
||||
t.Errorf("expected *SendError type as returned error, but didn't")
|
||||
return
|
||||
}
|
||||
if se.IsTemp() {
|
||||
t.Errorf("expected permanent error but IsTemp() returned true")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// getTestConnection takes environment variables to establish a connection to a real
|
||||
// SMTP server to test all functionality that requires a connection
|
||||
func getTestConnection(auth bool) (*Client, error) {
|
||||
|
|
2
doc.go
2
doc.go
|
@ -6,4 +6,4 @@
|
|||
package mail
|
||||
|
||||
// VERSION is used in the default user agent string
|
||||
const VERSION = "0.3.6"
|
||||
const VERSION = "0.3.7"
|
||||
|
|
24
msg.go
24
msg.go
|
@ -90,6 +90,9 @@ type Msg struct {
|
|||
|
||||
// middlewares is the list of middlewares to apply to the Msg before sending in FIFO order
|
||||
middlewares []Middleware
|
||||
|
||||
// sendError holds the SendError in case a Msg could not be delivered during the Client.Send operation
|
||||
sendError error
|
||||
}
|
||||
|
||||
// SendmailPath is the default system path to the sendmail binary
|
||||
|
@ -957,6 +960,27 @@ func (m *Msg) UpdateReader(r *Reader) {
|
|||
r.err = err
|
||||
}
|
||||
|
||||
// HasSendError returns true if the Msg experienced an error during the message delivery and the
|
||||
// sendError field of the Msg is not nil
|
||||
func (m *Msg) HasSendError() bool {
|
||||
return m.sendError != nil
|
||||
}
|
||||
|
||||
// SendErrorIsTemp returns true if the Msg experienced an error during the message delivery and the
|
||||
// corresponding error was of temporary nature and should be retried later
|
||||
func (m *Msg) SendErrorIsTemp() bool {
|
||||
var e *SendError
|
||||
if errors.As(m.sendError, &e) {
|
||||
return e.isTemp
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// SendError returns the sendError field of the Msg
|
||||
func (m *Msg) SendError() error {
|
||||
return m.sendError
|
||||
}
|
||||
|
||||
// encodeString encodes a string based on the configured message encoder and the corresponding
|
||||
// charset for the Msg
|
||||
func (m *Msg) encodeString(s string) string {
|
||||
|
|
145
senderror.go
Normal file
145
senderror.go
Normal file
|
@ -0,0 +1,145 @@
|
|||
// SPDX-FileCopyrightText: 2022 Winni Neessen <winni@neessen.dev>
|
||||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package mail
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// List of SendError reasons
|
||||
const (
|
||||
// ErrGetSender is returned if the Msg.GetSender method fails during a Client.Send
|
||||
ErrGetSender SendErrReason = iota
|
||||
|
||||
// ErrGetRcpts is returned if the Msg.GetRecipients method fails during a Client.Send
|
||||
ErrGetRcpts
|
||||
|
||||
// ErrSMTPMailFrom is returned if the Msg delivery failed when sending the MAIL FROM command
|
||||
// to the sending SMTP server
|
||||
ErrSMTPMailFrom
|
||||
|
||||
// ErrSMTPRcptTo is returned if the Msg delivery failed when sending the RCPT TO command
|
||||
// to the sending SMTP server
|
||||
ErrSMTPRcptTo
|
||||
|
||||
// ErrSMTPData is returned if the Msg delivery failed when sending the DATA command
|
||||
// to the sending SMTP server
|
||||
ErrSMTPData
|
||||
|
||||
// ErrSMTPDataClose is returned if the Msg delivery failed when trying to close the
|
||||
// Client data writer
|
||||
ErrSMTPDataClose
|
||||
|
||||
// ErrSMTPReset is returned if the Msg delivery failed when sending the RSET command
|
||||
// to the sending SMTP server
|
||||
ErrSMTPReset
|
||||
|
||||
// ErrWriteContent is returned if the Msg delivery failed when sending Msg content
|
||||
// to the Client writer
|
||||
ErrWriteContent
|
||||
|
||||
// ErrConnCheck is returned if the Msg delivery failed when checking if the SMTP
|
||||
// server connection is still working
|
||||
ErrConnCheck
|
||||
|
||||
// ErrNoUnencoded is returned if the Msg delivery failed when the Msg is configured for
|
||||
// unencoded delivery but the server does not support this
|
||||
ErrNoUnencoded
|
||||
|
||||
// ErrAmbiguous is a generalized delivery error for the SendError type that is
|
||||
// returned if the exact reason for the delivery failure is ambiguous
|
||||
ErrAmbiguous
|
||||
)
|
||||
|
||||
// SendError is an error wrapper for delivery errors of the Msg
|
||||
type SendError struct {
|
||||
Reason SendErrReason
|
||||
isTemp bool
|
||||
errlist []error
|
||||
rcpt []string
|
||||
}
|
||||
|
||||
// SendErrReason represents a comparable reason on why the delivery failed
|
||||
type SendErrReason int
|
||||
|
||||
// Error implements the error interface for the SendError type
|
||||
func (e *SendError) Error() string {
|
||||
if e.Reason > 10 {
|
||||
return "unknown reason"
|
||||
}
|
||||
|
||||
var em strings.Builder
|
||||
em.WriteString(e.Reason.String())
|
||||
if len(e.errlist) > 0 {
|
||||
em.WriteRune(':')
|
||||
for i := range e.errlist {
|
||||
em.WriteRune(' ')
|
||||
em.WriteString(e.errlist[i].Error())
|
||||
if i != len(e.errlist)-1 {
|
||||
em.WriteString(", ")
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(e.rcpt) > 0 {
|
||||
em.WriteString(", affected recipient(s): ")
|
||||
for i := range e.rcpt {
|
||||
em.WriteString(e.rcpt[i])
|
||||
if i != len(e.rcpt)-1 {
|
||||
em.WriteString(", ")
|
||||
}
|
||||
}
|
||||
}
|
||||
return em.String()
|
||||
}
|
||||
|
||||
// Is implements the errors.Is functionality and compares the SendErrReason
|
||||
func (e *SendError) Is(et error) bool {
|
||||
var t *SendError
|
||||
if errors.As(et, &t) {
|
||||
return e.Reason == t.Reason && e.isTemp == t.isTemp
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// IsTemp returns true if the delivery error is of temporary nature and can be retried
|
||||
func (e *SendError) IsTemp() bool {
|
||||
return e.isTemp
|
||||
}
|
||||
|
||||
// String implements the Stringer interface for the SendErrReason
|
||||
func (r SendErrReason) String() string {
|
||||
switch r {
|
||||
case ErrGetSender:
|
||||
return "getting sender address"
|
||||
case ErrGetRcpts:
|
||||
return "getting recipient addresses"
|
||||
case ErrSMTPMailFrom:
|
||||
return "sending SMTP MAIL FROM command"
|
||||
case ErrSMTPRcptTo:
|
||||
return "sending SMTP RCPT TO command"
|
||||
case ErrSMTPData:
|
||||
return "sending SMTP DATA command"
|
||||
case ErrSMTPDataClose:
|
||||
return "closing SMTP DATA writer"
|
||||
case ErrSMTPReset:
|
||||
return "sending SMTP RESET command"
|
||||
case ErrWriteContent:
|
||||
return "sending message content"
|
||||
case ErrConnCheck:
|
||||
return "checking SMTP connection"
|
||||
case ErrNoUnencoded:
|
||||
return ErrServerNoUnencoded.Error()
|
||||
case ErrAmbiguous:
|
||||
return "ambiguous reason, check Msg.SendError for message specific reasons"
|
||||
}
|
||||
return "unknown reason"
|
||||
}
|
||||
|
||||
// isTempError checks the given SMTP error and returns true if the given error is of temporary nature
|
||||
// and should be retried
|
||||
func isTempError(e error) bool {
|
||||
return e.Error()[0] == '4'
|
||||
}
|
89
senderror_test.go
Normal file
89
senderror_test.go
Normal file
|
@ -0,0 +1,89 @@
|
|||
// SPDX-FileCopyrightText: 2022 Winni Neessen <winni@neessen.dev>
|
||||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package mail
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// TestSendError_Error tests the SendError and SendErrReason error handling methods
|
||||
func TestSendError_Error(t *testing.T) {
|
||||
tl := []struct {
|
||||
n string
|
||||
r SendErrReason
|
||||
te bool
|
||||
}{
|
||||
{"ErrGetSender/temp", ErrGetSender, true},
|
||||
{"ErrGetSender/perm", ErrGetSender, false},
|
||||
{"ErrGetRcpts/temp", ErrGetRcpts, true},
|
||||
{"ErrGetRcpts/perm", ErrGetRcpts, false},
|
||||
{"ErrSMTPMailFrom/temp", ErrSMTPMailFrom, true},
|
||||
{"ErrSMTPMailFrom/perm", ErrSMTPMailFrom, false},
|
||||
{"ErrSMTPRcptTo/temp", ErrSMTPRcptTo, true},
|
||||
{"ErrSMTPRcptTo/perm", ErrSMTPRcptTo, false},
|
||||
{"ErrSMTPData/temp", ErrSMTPData, true},
|
||||
{"ErrSMTPData/perm", ErrSMTPData, false},
|
||||
{"ErrSMTPDataClose/temp", ErrSMTPDataClose, true},
|
||||
{"ErrSMTPDataClose/perm", ErrSMTPDataClose, false},
|
||||
{"ErrSMTPReset/temp", ErrSMTPReset, true},
|
||||
{"ErrSMTPReset/perm", ErrSMTPReset, false},
|
||||
{"ErrWriteContent/temp", ErrWriteContent, true},
|
||||
{"ErrWriteContent/perm", ErrWriteContent, false},
|
||||
{"ErrConnCheck/temp", ErrConnCheck, true},
|
||||
{"ErrConnCheck/perm", ErrConnCheck, false},
|
||||
{"ErrNoUnencoded/temp", ErrNoUnencoded, true},
|
||||
{"ErrNoUnencoded/perm", ErrNoUnencoded, false},
|
||||
{"ErrAmbiguous/temp", ErrAmbiguous, true},
|
||||
{"ErrAmbiguous/perm", ErrAmbiguous, false},
|
||||
{"Unknown/temp", 9999, true},
|
||||
{"Unknown/perm", 9999, false},
|
||||
}
|
||||
|
||||
for _, tt := range tl {
|
||||
t.Run(tt.n, func(t *testing.T) {
|
||||
if err := returnSendError(tt.r, tt.te); err != nil {
|
||||
exp := &SendError{Reason: tt.r, isTemp: tt.te}
|
||||
if !errors.Is(err, exp) {
|
||||
t.Errorf("error mismatch, expected: %s (temp: %t), got: %s (temp: %t)", tt.r, tt.te,
|
||||
exp.Error(), exp.isTemp)
|
||||
}
|
||||
if !strings.Contains(fmt.Sprintf("%s", err), tt.r.String()) {
|
||||
t.Errorf("error string mismatch, expected: %s, got: %s",
|
||||
tt.r.String(), fmt.Sprintf("%s", err))
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSendError_IsTemp(t *testing.T) {
|
||||
var se *SendError
|
||||
err1 := returnSendError(ErrAmbiguous, true)
|
||||
if !errors.As(err1, &se) {
|
||||
t.Errorf("error mismatch, expected error to be of type *SendError")
|
||||
return
|
||||
}
|
||||
if errors.As(err1, &se) && !se.IsTemp() {
|
||||
t.Errorf("error mismatch, expected temporary error")
|
||||
return
|
||||
}
|
||||
err2 := returnSendError(ErrAmbiguous, false)
|
||||
if !errors.As(err2, &se) {
|
||||
t.Errorf("error mismatch, expected error to be of type *SendError")
|
||||
return
|
||||
}
|
||||
if errors.As(err2, &se) && se.IsTemp() {
|
||||
t.Errorf("error mismatch, expected non-temporary error")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// returnSendError is a helper method to retunr a SendError with a specific reason
|
||||
func returnSendError(r SendErrReason, t bool) error {
|
||||
return &SendError{Reason: r, isTemp: t}
|
||||
}
|
Loading…
Reference in a new issue