diff --git a/.github/workflows/codecov.yml b/.github/workflows/codecov.yml index 2178e32..9ab2f52 100644 --- a/.github/workflows/codecov.yml +++ b/.github/workflows/codecov.yml @@ -36,7 +36,7 @@ jobs: strategy: matrix: os: [ubuntu-latest, macos-latest, windows-latest] - go: ['1.20', '1.21', '1.22', '1.23'] + go: ['1.19', '1.20', '1.23'] steps: - name: Harden Runner uses: step-security/harden-runner@91182cccc01eb5e619899d80e4e971d6181294a7 # v2.10.1 diff --git a/client_119.go b/client_119.go index 8084913..7de5d59 100644 --- a/client_119.go +++ b/client_119.go @@ -7,16 +7,22 @@ package mail +import "errors" + // Send sends out the mail message func (c *Client) Send(messages ...*Msg) error { if err := c.checkConn(); err != nil { return &SendError{Reason: ErrConnCheck, errlist: []error{err}, isTemp: isTempError(err)} } var errs []*SendError - for _, message := range messages { + for id, message := range messages { if sendErr := c.sendSingleMsg(message); sendErr != nil { messages[id].sendError = sendErr - errs = append(errs, sendErr) + + var msgSendErr *SendError + if errors.As(sendErr, &msgSendErr) { + errs = append(errs, msgSendErr) + } } } diff --git a/client_test.go b/client_test.go index efccea4..3d50a2d 100644 --- a/client_test.go +++ b/client_test.go @@ -5,6 +5,7 @@ package mail import ( + "bufio" "context" "crypto/tls" "errors" @@ -21,11 +22,18 @@ import ( "github.com/wneessen/go-mail/smtp" ) -// DefaultHost is used as default hostname for the Client -const DefaultHost = "localhost" - -// TestRcpt -const TestRcpt = "go-mail@mytrashmailer.com" +const ( + // DefaultHost is used as default hostname for the Client + DefaultHost = "localhost" + // TestRcpt is a trash mail address to send test mails to + TestRcpt = "go-mail@mytrashmailer.com" + // TestServerProto is the protocol used for the simple SMTP test server + TestServerProto = "tcp" + // TestServerAddr is the address the simple SMTP test server listens on + TestServerAddr = "127.0.0.1" + // TestServerPortBase is the base port for the simple SMTP test server + TestServerPortBase = 2025 +) // TestNewClient tests the NewClient() method with its default options func TestNewClient(t *testing.T) { @@ -1251,6 +1259,475 @@ func TestClient_DialAndSendWithContext_withSendError(t *testing.T) { } } +func TestClient_SendErrorNoEncoding(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + featureSet := "250-AUTH PLAIN\r\n250-DSN\r\n250 SMTPUTF8" + serverPort := TestServerPortBase + 1 + go func() { + if err := simpleSMTPServer(ctx, featureSet, false, serverPort); err != nil { + t.Errorf("failed to start test server: %s", err) + return + } + }() + time.Sleep(time.Millisecond * 300) + + message := NewMsg() + if err := message.From("valid-from@domain.tld"); err != nil { + t.Errorf("failed to set FROM address: %s", err) + return + } + if err := message.To("valid-to@domain.tld"); err != nil { + t.Errorf("failed to set TO address: %s", err) + return + } + message.Subject("Test subject") + message.SetBodyString(TypeTextPlain, "Test body") + message.SetMessageIDWithValue("this.is.a.message.id") + message.SetEncoding(NoEncoding) + + client, err := NewClient(TestServerAddr, WithPort(serverPort), + WithTLSPortPolicy(NoTLS), WithSMTPAuth(SMTPAuthPlain), + WithUsername("toni@tester.com"), + WithPassword("V3ryS3cr3t+")) + if err != nil { + t.Errorf("unable to create new client: %s", err) + } + if err = client.DialWithContext(context.Background()); err != nil { + t.Errorf("failed to dial to test server: %s", err) + } + if err = client.Send(message); err == nil { + t.Error("expected Send() to fail but didn't") + } + + var sendErr *SendError + if !errors.As(err, &sendErr) { + t.Errorf("expected *SendError type as returned error, but got %T", sendErr) + } + if errors.As(err, &sendErr) { + if sendErr.IsTemp() { + t.Errorf("expected permanent error but IsTemp() returned true") + } + if sendErr.Reason != ErrNoUnencoded { + t.Errorf("expected ErrNoUnencoded error, but got %s", sendErr.Reason) + } + if !strings.EqualFold(sendErr.MessageID(), "") { + t.Errorf("expected message ID: %q, but got %q", "", + sendErr.MessageID()) + } + if sendErr.Msg() == nil { + t.Errorf("expected message to be set, but got nil") + } + } + + if err = client.Close(); err != nil { + t.Errorf("failed to close server connection: %s", err) + } +} + +func TestClient_SendErrorMailFrom(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + serverPort := TestServerPortBase + 2 + featureSet := "250-AUTH PLAIN\r\n250-8BITMIME\r\n250-DSN\r\n250 SMTPUTF8" + go func() { + if err := simpleSMTPServer(ctx, featureSet, false, serverPort); err != nil { + t.Errorf("failed to start test server: %s", err) + return + } + }() + time.Sleep(time.Millisecond * 300) + + message := NewMsg() + if err := message.From("invalid-from@domain.tld"); err != nil { + t.Errorf("failed to set FROM address: %s", err) + return + } + if err := message.To("valid-to@domain.tld"); err != nil { + t.Errorf("failed to set TO address: %s", err) + return + } + message.Subject("Test subject") + message.SetBodyString(TypeTextPlain, "Test body") + message.SetMessageIDWithValue("this.is.a.message.id") + + client, err := NewClient(TestServerAddr, WithPort(serverPort), + WithTLSPortPolicy(NoTLS), WithSMTPAuth(SMTPAuthPlain), + WithUsername("toni@tester.com"), + WithPassword("V3ryS3cr3t+")) + if err != nil { + t.Errorf("unable to create new client: %s", err) + } + if err = client.DialWithContext(context.Background()); err != nil { + t.Errorf("failed to dial to test server: %s", err) + } + if err = client.Send(message); err == nil { + t.Error("expected Send() to fail but didn't") + } + + var sendErr *SendError + if !errors.As(err, &sendErr) { + t.Errorf("expected *SendError type as returned error, but got %T", sendErr) + } + if errors.As(err, &sendErr) { + if sendErr.IsTemp() { + t.Errorf("expected permanent error but IsTemp() returned true") + } + if sendErr.Reason != ErrSMTPMailFrom { + t.Errorf("expected ErrSMTPMailFrom error, but got %s", sendErr.Reason) + } + if !strings.EqualFold(sendErr.MessageID(), "") { + t.Errorf("expected message ID: %q, but got %q", "", + sendErr.MessageID()) + } + if sendErr.Msg() == nil { + t.Errorf("expected message to be set, but got nil") + } + } + + if err = client.Close(); err != nil { + t.Errorf("failed to close server connection: %s", err) + } +} + +func TestClient_SendErrorMailFromReset(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + serverPort := TestServerPortBase + 3 + featureSet := "250-AUTH PLAIN\r\n250-8BITMIME\r\n250-DSN\r\n250 SMTPUTF8" + go func() { + if err := simpleSMTPServer(ctx, featureSet, true, serverPort); err != nil { + t.Errorf("failed to start test server: %s", err) + return + } + }() + time.Sleep(time.Millisecond * 300) + + message := NewMsg() + if err := message.From("invalid-from@domain.tld"); err != nil { + t.Errorf("failed to set FROM address: %s", err) + return + } + if err := message.To("valid-to@domain.tld"); err != nil { + t.Errorf("failed to set TO address: %s", err) + return + } + message.Subject("Test subject") + message.SetBodyString(TypeTextPlain, "Test body") + message.SetMessageIDWithValue("this.is.a.message.id") + + client, err := NewClient(TestServerAddr, WithPort(serverPort), + WithTLSPortPolicy(NoTLS), WithSMTPAuth(SMTPAuthPlain), + WithUsername("toni@tester.com"), + WithPassword("V3ryS3cr3t+")) + if err != nil { + t.Errorf("unable to create new client: %s", err) + } + if err = client.DialWithContext(context.Background()); err != nil { + t.Errorf("failed to dial to test server: %s", err) + } + if err = client.Send(message); err == nil { + t.Error("expected Send() to fail but didn't") + } + + var sendErr *SendError + if !errors.As(err, &sendErr) { + t.Errorf("expected *SendError type as returned error, but got %T", sendErr) + } + if errors.As(err, &sendErr) { + if sendErr.IsTemp() { + t.Errorf("expected permanent error but IsTemp() returned true") + } + if sendErr.Reason != ErrSMTPMailFrom { + t.Errorf("expected ErrSMTPMailFrom error, but got %s", sendErr.Reason) + } + if !strings.EqualFold(sendErr.MessageID(), "") { + t.Errorf("expected message ID: %q, but got %q", "", + sendErr.MessageID()) + } + if len(sendErr.errlist) != 2 { + t.Errorf("expected 2 errors, but got %d", len(sendErr.errlist)) + return + } + if !strings.EqualFold(sendErr.errlist[0].Error(), "503 5.1.2 Invalid from: ") { + t.Errorf("expected error: %q, but got %q", + "503 5.1.2 Invalid from: ", sendErr.errlist[0].Error()) + } + if !strings.EqualFold(sendErr.errlist[1].Error(), "500 5.1.2 Error: reset failed") { + t.Errorf("expected error: %q, but got %q", + "500 5.1.2 Error: reset failed", sendErr.errlist[1].Error()) + } + } + + if err = client.Close(); err != nil { + t.Errorf("failed to close server connection: %s", err) + } +} + +func TestClient_SendErrorToReset(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + serverPort := TestServerPortBase + 4 + featureSet := "250-AUTH PLAIN\r\n250-8BITMIME\r\n250-DSN\r\n250 SMTPUTF8" + go func() { + if err := simpleSMTPServer(ctx, featureSet, true, serverPort); err != nil { + t.Errorf("failed to start test server: %s", err) + return + } + }() + time.Sleep(time.Millisecond * 300) + + message := NewMsg() + if err := message.From("valid-from@domain.tld"); err != nil { + t.Errorf("failed to set FROM address: %s", err) + return + } + if err := message.To("invalid-to@domain.tld"); err != nil { + t.Errorf("failed to set TO address: %s", err) + return + } + message.Subject("Test subject") + message.SetBodyString(TypeTextPlain, "Test body") + message.SetMessageIDWithValue("this.is.a.message.id") + + client, err := NewClient(TestServerAddr, WithPort(serverPort), + WithTLSPortPolicy(NoTLS), WithSMTPAuth(SMTPAuthPlain), + WithUsername("toni@tester.com"), + WithPassword("V3ryS3cr3t+")) + if err != nil { + t.Errorf("unable to create new client: %s", err) + } + if err = client.DialWithContext(context.Background()); err != nil { + t.Errorf("failed to dial to test server: %s", err) + } + if err = client.Send(message); err == nil { + t.Error("expected Send() to fail but didn't") + } + + var sendErr *SendError + if !errors.As(err, &sendErr) { + t.Errorf("expected *SendError type as returned error, but got %T", sendErr) + } + if errors.As(err, &sendErr) { + if sendErr.IsTemp() { + t.Errorf("expected permanent error but IsTemp() returned true") + } + if sendErr.Reason != ErrSMTPRcptTo { + t.Errorf("expected ErrSMTPRcptTo error, but got %s", sendErr.Reason) + } + if !strings.EqualFold(sendErr.MessageID(), "") { + t.Errorf("expected message ID: %q, but got %q", "", + sendErr.MessageID()) + } + if len(sendErr.errlist) != 2 { + t.Errorf("expected 2 errors, but got %d", len(sendErr.errlist)) + return + } + if !strings.EqualFold(sendErr.errlist[0].Error(), "500 5.1.2 Invalid to: ") { + t.Errorf("expected error: %q, but got %q", + "500 5.1.2 Invalid to: ", sendErr.errlist[0].Error()) + } + if !strings.EqualFold(sendErr.errlist[1].Error(), "500 5.1.2 Error: reset failed") { + t.Errorf("expected error: %q, but got %q", + "500 5.1.2 Error: reset failed", sendErr.errlist[1].Error()) + } + } + + if err = client.Close(); err != nil { + t.Errorf("failed to close server connection: %s", err) + } +} + +func TestClient_SendErrorDataClose(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + serverPort := TestServerPortBase + 5 + featureSet := "250-AUTH PLAIN\r\n250-8BITMIME\r\n250-DSN\r\n250 SMTPUTF8" + go func() { + if err := simpleSMTPServer(ctx, featureSet, false, serverPort); err != nil { + t.Errorf("failed to start test server: %s", err) + return + } + }() + time.Sleep(time.Millisecond * 300) + + message := NewMsg() + if err := message.From("valid-from@domain.tld"); err != nil { + t.Errorf("failed to set FROM address: %s", err) + return + } + if err := message.To("valid-to@domain.tld"); err != nil { + t.Errorf("failed to set TO address: %s", err) + return + } + message.Subject("Test subject") + message.SetBodyString(TypeTextPlain, "DATA close should fail") + message.SetMessageIDWithValue("this.is.a.message.id") + + client, err := NewClient(TestServerAddr, WithPort(serverPort), + WithTLSPortPolicy(NoTLS), WithSMTPAuth(SMTPAuthPlain), + WithUsername("toni@tester.com"), + WithPassword("V3ryS3cr3t+")) + if err != nil { + t.Errorf("unable to create new client: %s", err) + } + if err = client.DialWithContext(context.Background()); err != nil { + t.Errorf("failed to dial to test server: %s", err) + } + if err = client.Send(message); err == nil { + t.Error("expected Send() to fail but didn't") + } + + var sendErr *SendError + if !errors.As(err, &sendErr) { + t.Errorf("expected *SendError type as returned error, but got %T", sendErr) + } + if errors.As(err, &sendErr) { + if sendErr.IsTemp() { + t.Errorf("expected permanent error but IsTemp() returned true") + } + if sendErr.Reason != ErrSMTPDataClose { + t.Errorf("expected ErrSMTPDataClose error, but got %s", sendErr.Reason) + } + if !strings.EqualFold(sendErr.MessageID(), "") { + t.Errorf("expected message ID: %q, but got %q", "", + sendErr.MessageID()) + } + } + + if err = client.Close(); err != nil { + t.Errorf("failed to close server connection: %s", err) + } +} + +func TestClient_SendErrorDataWrite(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + serverPort := TestServerPortBase + 6 + featureSet := "250-AUTH PLAIN\r\n250-8BITMIME\r\n250-DSN\r\n250 SMTPUTF8" + go func() { + if err := simpleSMTPServer(ctx, featureSet, false, serverPort); err != nil { + t.Errorf("failed to start test server: %s", err) + return + } + }() + time.Sleep(time.Millisecond * 300) + + message := NewMsg() + if err := message.From("valid-from@domain.tld"); err != nil { + t.Errorf("failed to set FROM address: %s", err) + return + } + if err := message.To("valid-to@domain.tld"); err != nil { + t.Errorf("failed to set TO address: %s", err) + return + } + message.Subject("Test subject") + message.SetBodyString(TypeTextPlain, "DATA write should fail") + message.SetMessageIDWithValue("this.is.a.message.id") + message.SetGenHeader("X-Test-Header", "DATA write should fail") + + client, err := NewClient(TestServerAddr, WithPort(serverPort), + WithTLSPortPolicy(NoTLS), WithSMTPAuth(SMTPAuthPlain), + WithUsername("toni@tester.com"), + WithPassword("V3ryS3cr3t+")) + if err != nil { + t.Errorf("unable to create new client: %s", err) + } + if err = client.DialWithContext(context.Background()); err != nil { + t.Errorf("failed to dial to test server: %s", err) + } + if err = client.Send(message); err == nil { + t.Error("expected Send() to fail but didn't") + } + + var sendErr *SendError + if !errors.As(err, &sendErr) { + t.Errorf("expected *SendError type as returned error, but got %T", sendErr) + } + if errors.As(err, &sendErr) { + if sendErr.IsTemp() { + t.Errorf("expected permanent error but IsTemp() returned true") + } + if sendErr.Reason != ErrSMTPDataClose { + t.Errorf("expected ErrSMTPDataClose error, but got %s", sendErr.Reason) + } + if !strings.EqualFold(sendErr.MessageID(), "") { + t.Errorf("expected message ID: %q, but got %q", "", + sendErr.MessageID()) + } + } +} + +func TestClient_SendErrorReset(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + serverPort := TestServerPortBase + 7 + featureSet := "250-AUTH PLAIN\r\n250-8BITMIME\r\n250-DSN\r\n250 SMTPUTF8" + go func() { + if err := simpleSMTPServer(ctx, featureSet, true, serverPort); err != nil { + t.Errorf("failed to start test server: %s", err) + return + } + }() + time.Sleep(time.Millisecond * 300) + + message := NewMsg() + if err := message.From("valid-from@domain.tld"); err != nil { + t.Errorf("failed to set FROM address: %s", err) + return + } + if err := message.To("valid-to@domain.tld"); err != nil { + t.Errorf("failed to set TO address: %s", err) + return + } + message.Subject("Test subject") + message.SetBodyString(TypeTextPlain, "Test body") + message.SetMessageIDWithValue("this.is.a.message.id") + + client, err := NewClient(TestServerAddr, WithPort(serverPort), + WithTLSPortPolicy(NoTLS), WithSMTPAuth(SMTPAuthPlain), + WithUsername("toni@tester.com"), + WithPassword("V3ryS3cr3t+")) + if err != nil { + t.Errorf("unable to create new client: %s", err) + } + if err = client.DialWithContext(context.Background()); err != nil { + t.Errorf("failed to dial to test server: %s", err) + } + if err = client.Send(message); err == nil { + t.Error("expected Send() to fail but didn't") + } + + var sendErr *SendError + if !errors.As(err, &sendErr) { + t.Errorf("expected *SendError type as returned error, but got %T", sendErr) + } + if errors.As(err, &sendErr) { + if sendErr.IsTemp() { + t.Errorf("expected permanent error but IsTemp() returned true") + } + if sendErr.Reason != ErrSMTPReset { + t.Errorf("expected ErrSMTPReset error, but got %s", sendErr.Reason) + } + if !strings.EqualFold(sendErr.MessageID(), "") { + t.Errorf("expected message ID: %q, but got %q", "", + sendErr.MessageID()) + } + } + + if err = client.Close(); err != nil { + t.Errorf("failed to close server connection: %s", err) + } +} + // 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) { @@ -1545,3 +2022,155 @@ func (f faker) RemoteAddr() net.Addr { return nil } func (f faker) SetDeadline(time.Time) error { return nil } func (f faker) SetReadDeadline(time.Time) error { return nil } func (f faker) SetWriteDeadline(time.Time) error { return nil } + +// simpleSMTPServer starts a simple TCP server that resonds to SMTP commands. +// The provided featureSet represents in what the server responds to EHLO command +// failReset controls if a RSET succeeds +func simpleSMTPServer(ctx context.Context, featureSet string, failReset bool, port int) error { + listener, err := net.Listen(TestServerProto, fmt.Sprintf("%s:%d", TestServerAddr, port)) + if err != nil { + return fmt.Errorf("unable to listen on %s://%s: %w", TestServerProto, TestServerAddr, err) + } + + defer func() { + if err := listener.Close(); err != nil { + fmt.Printf("unable to close listener: %s\n", err) + os.Exit(1) + } + }() + + for { + select { + case <-ctx.Done(): + return nil + default: + connection, err := listener.Accept() + var opErr *net.OpError + if err != nil { + if errors.As(err, &opErr) && opErr.Temporary() { + continue + } + return fmt.Errorf("unable to accept connection: %w", err) + } + handleTestServerConnection(connection, featureSet, failReset) + } + } +} + +func handleTestServerConnection(connection net.Conn, featureSet string, failReset bool) { + defer func() { + if err := connection.Close(); err != nil { + fmt.Printf("unable to close connection: %s\n", err) + } + }() + + reader := bufio.NewReader(connection) + writer := bufio.NewWriter(connection) + + writeLine := func(data string) error { + _, err := writer.WriteString(data + "\r\n") + if err != nil { + return fmt.Errorf("unable to write line: %w", err) + } + return writer.Flush() + } + writeOK := func() { + _ = writeLine("250 2.0.0 OK") + } + + if err := writeLine("220 go-mail test server ready ESMTP"); err != nil { + fmt.Printf("unable to write to client: %s\n", err) + return + } + + data, err := reader.ReadString('\n') + if err != nil { + fmt.Printf("unable to read from connection: %s\n", err) + return + } + if !strings.HasPrefix(data, "EHLO") && !strings.HasPrefix(data, "HELO") { + fmt.Printf("expected EHLO, got %q", data) + return + } + if err = writeLine("250-localhost.localdomain\r\n" + featureSet); err != nil { + fmt.Printf("unable to write to connection: %s\n", err) + return + } + + for { + data, err = reader.ReadString('\n') + if err != nil { + if errors.Is(err, io.EOF) { + break + } + fmt.Println("Error reading data:", err) + break + } + + var datastring string + data = strings.TrimSpace(data) + switch { + case strings.HasPrefix(data, "MAIL FROM:"): + from := strings.TrimPrefix(data, "MAIL FROM:") + from = strings.ReplaceAll(from, "BODY=8BITMIME", "") + from = strings.ReplaceAll(from, "SMTPUTF8", "") + from = strings.TrimSpace(from) + if !strings.EqualFold(from, "") { + _ = writeLine(fmt.Sprintf("503 5.1.2 Invalid from: %s", from)) + break + } + writeOK() + case strings.HasPrefix(data, "RCPT TO:"): + to := strings.TrimPrefix(data, "RCPT TO:") + to = strings.TrimSpace(to) + if !strings.EqualFold(to, "") { + _ = writeLine(fmt.Sprintf("500 5.1.2 Invalid to: %s", to)) + break + } + writeOK() + case strings.HasPrefix(data, "AUTH PLAIN"): + auth := strings.TrimPrefix(data, "AUTH PLAIN ") + if !strings.EqualFold(auth, "AHRvbmlAdGVzdGVyLmNvbQBWM3J5UzNjcjN0Kw==") { + _ = writeLine("535 5.7.8 Error: authentication failed") + break + } + _ = writeLine("235 2.7.0 Authentication successful") + case strings.EqualFold(data, "DATA"): + _ = writeLine("354 End data with .") + for { + ddata, derr := reader.ReadString('\n') + if derr != nil { + fmt.Printf("failed to read DATA data from connection: %s\n", derr) + break + } + ddata = strings.TrimSpace(ddata) + if strings.EqualFold(ddata, "DATA write should fail") { + _ = writeLine("500 5.0.0 Error during DATA transmission") + break + } + if ddata == "." { + if strings.Contains(datastring, "DATA close should fail") { + _ = writeLine("500 5.0.0 Error during DATA closing") + break + } + _ = writeLine("250 2.0.0 Ok: queued as 1234567890") + break + } + datastring += ddata + "\n" + } + case strings.EqualFold(data, "noop"), + strings.EqualFold(data, "vrfy"): + writeOK() + case strings.EqualFold(data, "rset"): + if failReset { + _ = writeLine("500 5.1.2 Error: reset failed") + break + } + writeOK() + case strings.EqualFold(data, "quit"): + _ = writeLine("221 2.0.0 Bye") + default: + _ = writeLine("500 5.5.2 Error: bad syntax") + } + } +} diff --git a/senderror.go b/senderror.go index 494bfd3..90c8c4a 100644 --- a/senderror.go +++ b/senderror.go @@ -118,6 +118,23 @@ func (e *SendError) IsTemp() bool { return e.isTemp } +// MessageID returns the message ID of the affected Msg that caused the error +// If no message ID was set for the Msg, an empty string will be returned +func (e *SendError) MessageID() string { + if e == nil || e.affectedMsg == nil { + return "" + } + return e.affectedMsg.GetMessageID() +} + +// Msg returns the pointer to the affected message that caused the error +func (e *SendError) Msg() *Msg { + if e == nil || e.affectedMsg == nil { + return nil + } + return e.affectedMsg +} + // String implements the Stringer interface for the SendErrReason func (r SendErrReason) String() string { switch r { diff --git a/senderror_test.go b/senderror_test.go index 789b290..8b7bfb3 100644 --- a/senderror_test.go +++ b/senderror_test.go @@ -90,7 +90,55 @@ func TestSendError_IsTempNil(t *testing.T) { } } +func TestSendError_MessageID(t *testing.T) { + var se *SendError + err := returnSendError(ErrAmbiguous, false) + if !errors.As(err, &se) { + t.Errorf("error mismatch, expected error to be of type *SendError") + return + } + if errors.As(err, &se) { + if se.MessageID() == "" { + t.Errorf("sendError expected message-id, but got empty string") + } + if !strings.EqualFold(se.MessageID(), "") { + t.Errorf("sendError message-id expected: %s, but got: %s", "", + se.MessageID()) + } + } +} + +func TestSendError_Msg(t *testing.T) { + var se *SendError + err := returnSendError(ErrAmbiguous, false) + if !errors.As(err, &se) { + t.Errorf("error mismatch, expected error to be of type *SendError") + return + } + if errors.As(err, &se) { + if se.Msg() == nil { + t.Errorf("sendError expected msg pointer, but got nil") + } + from := se.Msg().GetFromString() + if len(from) == 0 { + t.Errorf("sendError expected msg from, but got empty string") + return + } + if !strings.EqualFold(from[0], "") { + t.Errorf("sendError message from expected: %s, but got: %s", "", + from[0]) + } + } +} + // 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} + message := NewMsg() + _ = message.From("toni.tester@domain.tld") + _ = message.To("tina.tester@domain.tld") + message.Subject("This is the subject") + message.SetBodyString(TypeTextPlain, "This is the message body") + message.SetMessageIDWithValue("this.is.a.message.id") + + return &SendError{Reason: r, isTemp: t, affectedMsg: message} }