Refactor msgWriter tests and add new test cases

Reorganize existing tests for msgWriter and writeMsg, adding subtests for various encoding, charset, and failure scenarios. Enhanced tests to cover multipart/mixed, multipart/related, multipart/alternative, application/pgp-encrypted, application/pgp-signature messages, and handling of preformatted headers.
This commit is contained in:
Winni Neessen 2024-10-28 20:52:38 +01:00
parent 7bbcee7d48
commit 61244a541e
Signed by: wneessen
GPG key ID: 385AC9889632126E
2 changed files with 278 additions and 136 deletions

View file

@ -3528,9 +3528,9 @@ func parseJSONLog(t *testing.T, buf *bytes.Buffer) logData {
} }
// testMessage configures and returns a new email message for testing, initializing it with valid sender and recipient. // testMessage configures and returns a new email message for testing, initializing it with valid sender and recipient.
func testMessage(t *testing.T) *Msg { func testMessage(t *testing.T, opts ...MsgOption) *Msg {
t.Helper() t.Helper()
message := NewMsg() message := NewMsg(opts...)
if message == nil { if message == nil {
t.Fatal("failed to create new message") t.Fatal("failed to create new message")
} }

View file

@ -6,151 +6,293 @@ package mail
import ( import (
"bytes" "bytes"
"errors"
"fmt" "fmt"
"io"
"mime" "mime"
"strings" "strings"
"testing" "testing"
"time" "time"
) )
// brokenWriter implements a broken writer for io.Writer testing
type brokenWriter struct {
io.Writer
}
// Write implements the io.Writer interface but intentionally returns an error at
// any time
func (bw *brokenWriter) Write([]byte) (int, error) {
return 0, fmt.Errorf("intentionally failed")
}
// TestMsgWriter_Write tests the WriteTo() method of the msgWriter
func TestMsgWriter_Write(t *testing.T) { func TestMsgWriter_Write(t *testing.T) {
bw := &brokenWriter{} t.Run("msgWriter writes to memory for all charsets", func(t *testing.T) {
mw := &msgWriter{writer: bw, charset: CharsetUTF8, encoder: mime.QEncoding} for _, tt := range charsetTests {
_, err := mw.Write([]byte("test")) t.Run(tt.name, func(t *testing.T) {
if err == nil { buffer := bytes.NewBuffer(nil)
t.Errorf("msgWriter WriteTo() with brokenWriter should fail, but didn't") msgwriter := &msgWriter{
writer: buffer,
charset: tt.value,
encoder: mime.QEncoding,
} }
_, err := msgwriter.Write([]byte("test"))
// Also test the part when a previous error happened if err != nil {
mw.err = fmt.Errorf("broken") t.Errorf("msgWriter failed to write: %s", err)
_, err = mw.Write([]byte("test"))
if err == nil {
t.Errorf("msgWriter WriteTo() with brokenWriter should fail, but didn't")
} }
})
}
})
t.Run("msgWriter writes to memory for all encodings", func(t *testing.T) {
for _, tt := range encodingTests {
t.Run(tt.name, func(t *testing.T) {
buffer := bytes.NewBuffer(nil)
msgwriter := &msgWriter{
writer: buffer,
charset: CharsetUTF8,
encoder: getEncoder(tt.value),
}
_, err := msgwriter.Write([]byte("test"))
if err != nil {
t.Errorf("msgWriter failed to write: %s", err)
}
})
}
})
t.Run("msgWriter should fail on write", func(t *testing.T) {
msgwriter := &msgWriter{
writer: failReadWriteSeekCloser{},
charset: CharsetUTF8,
encoder: getEncoder(EncodingQP),
}
_, err := msgwriter.Write([]byte("test"))
if err == nil {
t.Fatalf("msgWriter was supposed to fail on write")
}
})
t.Run("msgWriter should fail on previous error", func(t *testing.T) {
buffer := bytes.NewBuffer(nil)
msgwriter := &msgWriter{
writer: buffer,
charset: CharsetUTF8,
encoder: getEncoder(EncodingQP),
}
_, err := msgwriter.Write([]byte("test"))
if err != nil {
t.Errorf("msgWriter failed to write: %s", err)
}
msgwriter.err = errors.New("intentionally failed")
_, err = msgwriter.Write([]byte("test2"))
if err == nil {
t.Fatalf("msgWriter was supposed to fail on second write")
}
})
} }
// TestMsgWriter_writeMsg tests the writeMsg method of the msgWriter
func TestMsgWriter_writeMsg(t *testing.T) { func TestMsgWriter_writeMsg(t *testing.T) {
m := NewMsg() msgwriter := &msgWriter{
_ = m.From(`"Toni Tester" <test@example.com>`) charset: CharsetUTF8,
_ = m.To(`"Toni Receiver" <receiver@example.com>`) encoder: getEncoder(EncodingQP),
m.Subject("This is a subject") }
m.SetBulk() t.Run("msgWriter writes a simple message", func(t *testing.T) {
buffer := bytes.NewBuffer(nil)
now := time.Now() now := time.Now()
m.SetDateWithValue(now) msgwriter.writer = buffer
m.SetMessageIDWithValue("message@id.com") message := testMessage(t)
m.SetBodyString(TypeTextPlain, "This is the body") message.SetDateWithValue(now)
m.AddAlternativeString(TypeTextHTML, "This is the alternative body") message.SetMessageIDWithValue("message@id.com")
buf := bytes.Buffer{} message.SetBulk()
mw := &msgWriter{writer: &buf, charset: CharsetUTF8, encoder: mime.QEncoding} msgwriter.writeMsg(message)
mw.writeMsg(m) if msgwriter.err != nil {
ms := buf.String() t.Errorf("msgWriter failed to write: %s", msgwriter.err)
var ea []string
if !strings.Contains(ms, `MIME-Version: 1.0`) {
ea = append(ea, "MIME-Version")
}
if !strings.Contains(ms, fmt.Sprintf("Date: %s", now.Format(time.RFC1123Z))) {
ea = append(ea, "Date")
}
if !strings.Contains(ms, `Message-ID: <message@id.com>`) {
ea = append(ea, "Message-ID")
}
if !strings.Contains(ms, `Precedence: bulk`) {
ea = append(ea, "Precedence")
}
if !strings.Contains(ms, `Subject: This is a subject`) {
ea = append(ea, "Subject")
}
if !strings.Contains(ms, `User-Agent: go-mail v`) {
ea = append(ea, "User-Agent")
}
if !strings.Contains(ms, `X-Mailer: go-mail v`) {
ea = append(ea, "X-Mailer")
}
if !strings.Contains(ms, `From: "Toni Tester" <test@example.com>`) {
ea = append(ea, "From")
}
if !strings.Contains(ms, `To: "Toni Receiver" <receiver@example.com>`) {
ea = append(ea, "To")
}
if !strings.Contains(ms, `Content-Type: text/plain; charset=UTF-8`) {
ea = append(ea, "Content-Type")
}
if !strings.Contains(ms, `Content-Transfer-Encoding: quoted-printable`) {
ea = append(ea, "Content-Transfer-Encoding")
}
if !strings.Contains(ms, "\r\n\r\nThis is the body") {
ea = append(ea, "Message body")
} }
pl := m.GetParts() var incorrectFields []string
if len(pl) <= 0 { if !strings.Contains(buffer.String(), "MIME-Version: 1.0\r\n") {
t.Errorf("expected multiple parts but got none") incorrectFields = append(incorrectFields, "MIME-Version")
return
} }
if len(pl) == 2 { if !strings.Contains(buffer.String(), fmt.Sprintf("Date: %s\r\n", now.Format(time.RFC1123Z))) {
ap := pl[1] incorrectFields = append(incorrectFields, "Date")
ap.SetCharset(CharsetISO88591)
} }
buf.Reset() if !strings.Contains(buffer.String(), "Message-ID: <message@id.com>\r\n") {
mw.writeMsg(m) incorrectFields = append(incorrectFields, "Message-ID")
ms = buf.String()
if !strings.Contains(ms, "\r\n\r\nThis is the alternative body") {
ea = append(ea, "Message alternative body")
} }
if !strings.Contains(ms, `Content-Type: text/html; charset=ISO-8859-1`) { if !strings.Contains(buffer.String(), "Precedence: bulk\r\n") {
ea = append(ea, "alternative body charset") incorrectFields = append(incorrectFields, "Precedence")
} }
if !strings.Contains(buffer.String(), "X-Auto-Response-Suppress: All\r\n") {
if len(ea) > 0 { incorrectFields = append(incorrectFields, "X-Auto-Response-Suppress")
em := "writeMsg() failed. The following errors occurred:\n"
for e := range ea {
em += fmt.Sprintf("* incorrect %q field", ea[e])
} }
em += fmt.Sprintf("\n\nFull message:\n%s", ms) if !strings.Contains(buffer.String(), "Subject: Testmail\r\n") {
t.Error(em) incorrectFields = append(incorrectFields, "Subject")
} }
if !strings.Contains(buffer.String(), "User-Agent: go-mail v") {
incorrectFields = append(incorrectFields, "User-Agent")
}
if !strings.Contains(buffer.String(), "X-Mailer: go-mail v") {
incorrectFields = append(incorrectFields, "X-Mailer")
}
if !strings.Contains(buffer.String(), `From: <`+TestSenderValid+`>`) {
incorrectFields = append(incorrectFields, "From")
}
if !strings.Contains(buffer.String(), `To: <`+TestRcptValid+`>`) {
incorrectFields = append(incorrectFields, "From")
}
if !strings.Contains(buffer.String(), "Content-Type: text/plain; charset=UTF-8\r\n") {
incorrectFields = append(incorrectFields, "Content-Type")
}
if !strings.Contains(buffer.String(), "Content-Transfer-Encoding: quoted-printable\r\n") {
incorrectFields = append(incorrectFields, "Content-Transfer-Encoding")
}
if !strings.HasSuffix(buffer.String(), "\r\n\r\nTestmail") {
incorrectFields = append(incorrectFields, "Message body")
}
if len(incorrectFields) > 0 {
t.Fatalf("msgWriter failed to write correct fields: %s - mail: %s",
strings.Join(incorrectFields, ", "), buffer.String())
}
})
t.Run("msgWriter with no from address uses envelope from", func(t *testing.T) {
buffer := bytes.NewBuffer(nil)
msgwriter.writer = buffer
message := NewMsg()
if message == nil {
t.Fatal("failed to create new message")
}
if err := message.EnvelopeFrom(TestSenderValid); err != nil {
t.Errorf("failed to set sender address: %s", err)
}
if err := message.To(TestRcptValid); err != nil {
t.Errorf("failed to set recipient address: %s", err)
}
message.Subject("Testmail")
message.SetBodyString(TypeTextPlain, "Testmail")
msgwriter.writeMsg(message)
if msgwriter.err != nil {
t.Errorf("msgWriter failed to write: %s", msgwriter.err)
}
if !strings.Contains(buffer.String(), "From: <"+TestSenderValid+">") {
t.Errorf("expected envelope from address as from address, got: %s", buffer.String())
}
})
t.Run("msgWriter with no from address or envelope from", func(t *testing.T) {
buffer := bytes.NewBuffer(nil)
msgwriter.writer = buffer
message := NewMsg()
if message == nil {
t.Fatal("failed to create new message")
}
msgwriter.writeMsg(message)
if msgwriter.err != nil {
t.Errorf("msgWriter failed to write: %s", msgwriter.err)
}
if strings.Contains(buffer.String(), "From:") {
t.Errorf("expected no from address, got: %s", buffer.String())
}
})
t.Run("msgWriter writes a multipart/mixed message", func(t *testing.T) {
buffer := bytes.NewBuffer(nil)
msgwriter.writer = buffer
message := testMessage(t, WithBoundary("testboundary"))
message.AttachFile("testdata/attachment.txt")
msgwriter.writeMsg(message)
if msgwriter.err != nil {
t.Errorf("msgWriter failed to write: %s", msgwriter.err)
}
if !strings.Contains(buffer.String(), "Content-Type: multipart/mixed") {
t.Errorf("expected multipart/mixed, got: %s", buffer.String())
}
if !strings.Contains(buffer.String(), "--testboundary\r\n") {
t.Errorf("expected boundary, got: %s", buffer.String())
}
if !strings.Contains(buffer.String(), "--testboundary--") {
t.Errorf("expected end boundary, got: %s", buffer.String())
}
})
t.Run("msgWriter writes a multipart/related message", func(t *testing.T) {
buffer := bytes.NewBuffer(nil)
msgwriter.writer = buffer
message := testMessage(t, WithBoundary("testboundary"))
message.EmbedFile("testdata/embed.txt")
msgwriter.writeMsg(message)
if msgwriter.err != nil {
t.Errorf("msgWriter failed to write: %s", msgwriter.err)
}
if !strings.Contains(buffer.String(), "Content-Type: multipart/related") {
t.Errorf("expected multipart/related, got: %s", buffer.String())
}
if !strings.Contains(buffer.String(), "--testboundary\r\n") {
t.Errorf("expected boundary, got: %s", buffer.String())
}
if !strings.Contains(buffer.String(), "--testboundary--") {
t.Errorf("expected end boundary, got: %s", buffer.String())
}
})
t.Run("msgWriter writes a multipart/alternative message", func(t *testing.T) {
buffer := bytes.NewBuffer(nil)
msgwriter.writer = buffer
message := testMessage(t, WithBoundary("testboundary"))
message.AddAlternativeString(TypeTextHTML, "<html><body><h1>Testmail</h1></body></html>")
msgwriter.writeMsg(message)
if msgwriter.err != nil {
t.Errorf("msgWriter failed to write: %s", msgwriter.err)
}
if !strings.Contains(buffer.String(), "Content-Type: multipart/alternative") {
t.Errorf("expected multipart/alternative, got: %s", buffer.String())
}
if !strings.Contains(buffer.String(), "--testboundary\r\n") {
t.Errorf("expected boundary, got: %s", buffer.String())
}
if !strings.Contains(buffer.String(), "--testboundary--") {
t.Errorf("expected end boundary, got: %s", buffer.String())
}
})
t.Run("msgWriter writes a application/pgp-encrypted message", func(t *testing.T) {
buffer := bytes.NewBuffer(nil)
msgwriter.writer = buffer
message := testMessage(t, WithPGPType(PGPEncrypt), WithBoundary("testboundary"))
msgwriter.writeMsg(message)
if msgwriter.err != nil {
t.Errorf("msgWriter failed to write: %s", msgwriter.err)
}
if !strings.Contains(buffer.String(), "Content-Type: multipart/encrypted") {
t.Errorf("expected multipart/encrypted, got: %s", buffer.String())
}
if !strings.Contains(buffer.String(), "--testboundary\r\n") {
t.Errorf("expected boundary, got: %s", buffer.String())
}
})
t.Run("msgWriter writes a application/pgp-signature message", func(t *testing.T) {
buffer := bytes.NewBuffer(nil)
msgwriter.writer = buffer
message := testMessage(t, WithPGPType(PGPSignature), WithBoundary("testboundary"))
msgwriter.writeMsg(message)
if msgwriter.err != nil {
t.Errorf("msgWriter failed to write: %s", msgwriter.err)
}
if !strings.Contains(buffer.String(), "Content-Type: multipart/signed") {
t.Errorf("expected multipart/signed, got: %s", buffer.String())
}
if !strings.Contains(buffer.String(), "--testboundary\r\n") {
t.Errorf("expected boundary, got: %s", buffer.String())
}
})
t.Run("msgWriter should ignore NoPGP", func(t *testing.T) {
buffer := bytes.NewBuffer(nil)
msgwriter.writer = buffer
message := testMessage(t, WithBoundary("testboundary"))
message.pgptype = 9
msgwriter.writeMsg(message)
if msgwriter.err != nil {
t.Errorf("msgWriter failed to write: %s", msgwriter.err)
}
if !strings.Contains(buffer.String(), "--testboundary\r\n") {
t.Errorf("expected boundary, got: %s", buffer.String())
}
})
} }
// TestMsgWriter_writeMsg_PGP tests the writeMsg method of the msgWriter with PGP types set func TestMsgWriter_writePreformattedGenHeader(t *testing.T) {
func TestMsgWriter_writeMsg_PGP(t *testing.T) { t.Run("message with no preformatted headerset", func(t *testing.T) {
m := NewMsg(WithPGPType(PGPEncrypt)) buffer := bytes.NewBuffer(nil)
_ = m.From(`"Toni Tester" <test@example.com>`) msgwriter := &msgWriter{
_ = m.To(`"Toni Receiver" <receiver@example.com>`) writer: buffer,
m.Subject("This is a subject") charset: CharsetUTF8,
m.SetBodyString(TypeTextPlain, "This is the body") encoder: getEncoder(EncodingQP),
buf := bytes.Buffer{}
mw := &msgWriter{writer: &buf, charset: CharsetUTF8, encoder: mime.QEncoding}
mw.writeMsg(m)
ms := buf.String()
if !strings.Contains(ms, `encrypted; protocol="application/pgp-encrypted"`) {
t.Errorf("writeMsg failed. Expected PGP encoding header but didn't find it in message output")
} }
message := testMessage(t)
m = NewMsg(WithPGPType(PGPSignature)) message.SetGenHeaderPreformatted(HeaderContentID, "This is a content id")
_ = m.From(`"Toni Tester" <test@example.com>`) msgwriter.writeMsg(message)
_ = m.To(`"Toni Receiver" <receiver@example.com>`) if !strings.Contains(buffer.String(), "Content-ID: This is a content id\r\n") {
m.Subject("This is a subject") t.Errorf("expected preformatted header, got: %s", buffer.String())
m.SetBodyString(TypeTextPlain, "This is the body")
buf = bytes.Buffer{}
mw = &msgWriter{writer: &buf, charset: CharsetUTF8, encoder: mime.QEncoding}
mw.writeMsg(m)
ms = buf.String()
if !strings.Contains(ms, `signed; protocol="application/pgp-signature"`) {
t.Errorf("writeMsg failed. Expected PGP encoding header but didn't find it in message output")
} }
})
} }