Compare commits

...

11 commits

Author SHA1 Message Date
c9f8a2acdd
Fix newline character in test attachment string for Windows
Updated the base64-encoded string for attachments in the tests to reflect the correct newline character for Windows. This ensures that the test cases correctly validate the presence of attachments in the mail messages.
2024-10-28 22:28:23 +01:00
b63a3dab9a
Adjust tests to handle platform-specific differences.
Added platform-specific checks for header and attachment content in `msgwriter_test.go`. This ensures compatibility and correct behavior on different operating systems, such as Windows and FreeBSD.
2024-10-28 22:20:09 +01:00
3a046d728e
Add test attachment and its license file
Introduced a new test attachment file for testing purposes. Also added a corresponding license file indicating copyright and licensing information for transparency and compliance.
2024-10-28 22:08:35 +01:00
a30215ebce
Add tests for writeHeader and writeBody functions
Introduce comprehensive test cases for the writeHeader and writeBody functions in msgWriter. These tests cover various scenarios including handling of multiple header values, edge cases, and error conditions in body writing operations. This concludes all tests for msgwriter.go.
2024-10-28 22:02:43 +01:00
b7a87fb15b
Add tests for MsgWriter_writeString function
Implemented tests to validate the MsgWriter writeString method for successful writes, failure scenarios, and handling pre-existing errors in the writer. This improves coverage and ensures robustness of the MsgWriter component.
2024-10-28 21:30:50 +01:00
dedb0e36c8
Add tests for MsgWriter writePart functionality
Introduce tests to validate MsgWriter's handling of message parts. This includes ensuring charset defaulting behavior and proper inclusion of part descriptions.
2024-10-28 21:25:22 +01:00
8e11fabbaf
Remove unused TestRcpt constant from client_test.go
The TestRcpt constant was defined but never used within the test file, leading to unnecessary clutter. Removing it enhances code readability and maintainability.
2024-10-28 21:14:05 +01:00
5c8d07407f
Add tests for file attachments in msgWriter
This commit introduces unit tests covering various scenarios for attaching files in email messages using msgWriter. These tests check for proper encoding, custom content types, different transfer encodings, custom descriptions, and handling attachments without body parts.
2024-10-28 21:13:36 +01:00
61244a541e
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.
2024-10-28 20:52:38 +01:00
7bbcee7d48
Add tests for consecutive attachment and embedding
Added tests for `AttachReader`, `AttachReadSeeker`, `EmbedReader`, and `EmbedReadSeeker` methods with consecutive `WriteTo` calls to ensure attachments are not lost. This addresses issue #110 on GitHub. Also, added tests for `SetBodyWriter` with a nil option and improved existing tests for `hasAlt` and `hasMixed` methods. This commit concludes the tests for msg.go. We have achieved the highest possible coverage.
2024-10-28 19:47:00 +01:00
d0280ea9ad
Add test coverage for WriteToTempFile method
Implemented unit tests for WriteToTempFile, including successful writing to a temporary file and failure cases due to an invalid TMPDIR. These tests ensure the method behaves correctly under different conditions and enhances code robustness.
2024-10-28 18:52:02 +01:00
6 changed files with 1076 additions and 851 deletions

View file

@ -30,8 +30,6 @@ import (
const (
// DefaultHost is used as default hostname for the Client
DefaultHost = "127.0.0.1"
// TestRcpt is a trash mail address to send test mails to
TestRcpt = "couttifaddebro-1473@yopmail.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
@ -3528,9 +3526,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.
func testMessage(t *testing.T) *Msg {
func testMessage(t *testing.T, opts ...MsgOption) *Msg {
t.Helper()
message := NewMsg()
message := NewMsg(opts...)
if message == nil {
t.Fatal("failed to create new message")
}

File diff suppressed because it is too large Load diff

View file

@ -120,28 +120,27 @@ func TestMsg_WriteToFile_unixOnly(t *testing.T) {
})
}
/*
func TestMsg_WriteToTempFileFailed(t *testing.T) {
m := NewMsg()
_ = m.From("Toni Tester <tester@example.com>")
_ = m.To("Ellenor Tester <ellinor@example.com>")
m.SetBodyString(TypeTextPlain, "This is a test")
func TestMsg_WriteToTempFile_unixOnly(t *testing.T) {
if os.Getenv("PERFORM_UNIX_OPEN_WRITE_TESTS") != "true" {
t.Skipf("PERFORM_UNIX_OPEN_WRITE_TESTS variable is not set. Skipping unix open/write tests")
}
t.Run("WriteToTempFile fails on invalid TMPDIR", func(t *testing.T) {
// We store the current TMPDIR variable so we can set it back when the test is over
curTmpDir := os.Getenv("TMPDIR")
defer func() {
t.Cleanup(func() {
if err := os.Setenv("TMPDIR", curTmpDir); err != nil {
t.Errorf("failed to set TMPDIR environment variable: %s", err)
}
}()
})
if err := os.Setenv("TMPDIR", "/invalid/directory/that/does/not/exist"); err != nil {
t.Errorf("failed to set TMPDIR environment variable: %s", err)
t.Fatalf("failed to set TMPDIR environment variable: %s", err)
}
_, err := m.WriteToTempFile()
message := testMessage(t)
_, err := message.WriteToTempFile()
if err == nil {
t.Errorf("WriteToTempFile() did not fail as expected")
t.Errorf("expected writing to invalid TMPDIR to fail, got: %s", err)
}
})
}
*/

View file

@ -6,151 +6,673 @@ package mail
import (
"bytes"
"errors"
"fmt"
"io"
"mime"
"runtime"
"strings"
"testing"
"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) {
bw := &brokenWriter{}
mw := &msgWriter{writer: bw, charset: CharsetUTF8, encoder: mime.QEncoding}
_, 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 charsets", func(t *testing.T) {
for _, tt := range charsetTests {
t.Run(tt.name, func(t *testing.T) {
buffer := bytes.NewBuffer(nil)
msgwriter := &msgWriter{
writer: buffer,
charset: tt.value,
encoder: mime.QEncoding,
}
// Also test the part when a previous error happened
mw.err = fmt.Errorf("broken")
_, err = mw.Write([]byte("test"))
if err == nil {
t.Errorf("msgWriter WriteTo() with brokenWriter should fail, but didn't")
_, err := msgwriter.Write([]byte("test"))
if err != nil {
t.Errorf("msgWriter failed to write: %s", err)
}
})
}
})
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) {
m := NewMsg()
_ = m.From(`"Toni Tester" <test@example.com>`)
_ = m.To(`"Toni Receiver" <receiver@example.com>`)
m.Subject("This is a subject")
m.SetBulk()
msgwriter := &msgWriter{
charset: CharsetUTF8,
encoder: getEncoder(EncodingQP),
}
t.Run("msgWriter writes a simple message", func(t *testing.T) {
buffer := bytes.NewBuffer(nil)
now := time.Now()
m.SetDateWithValue(now)
m.SetMessageIDWithValue("message@id.com")
m.SetBodyString(TypeTextPlain, "This is the body")
m.AddAlternativeString(TypeTextHTML, "This is the alternative body")
buf := bytes.Buffer{}
mw := &msgWriter{writer: &buf, charset: CharsetUTF8, encoder: mime.QEncoding}
mw.writeMsg(m)
ms := buf.String()
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")
msgwriter.writer = buffer
message := testMessage(t)
message.SetDateWithValue(now)
message.SetMessageIDWithValue("message@id.com")
message.SetBulk()
msgwriter.writeMsg(message)
if msgwriter.err != nil {
t.Errorf("msgWriter failed to write: %s", msgwriter.err)
}
pl := m.GetParts()
if len(pl) <= 0 {
t.Errorf("expected multiple parts but got none")
return
var incorrectFields []string
if !strings.Contains(buffer.String(), "MIME-Version: 1.0\r\n") {
incorrectFields = append(incorrectFields, "MIME-Version")
}
if len(pl) == 2 {
ap := pl[1]
ap.SetCharset(CharsetISO88591)
if !strings.Contains(buffer.String(), fmt.Sprintf("Date: %s\r\n", now.Format(time.RFC1123Z))) {
incorrectFields = append(incorrectFields, "Date")
}
buf.Reset()
mw.writeMsg(m)
ms = buf.String()
if !strings.Contains(ms, "\r\n\r\nThis is the alternative body") {
ea = append(ea, "Message alternative body")
if !strings.Contains(buffer.String(), "Message-ID: <message@id.com>\r\n") {
incorrectFields = append(incorrectFields, "Message-ID")
}
if !strings.Contains(ms, `Content-Type: text/html; charset=ISO-8859-1`) {
ea = append(ea, "alternative body charset")
if !strings.Contains(buffer.String(), "Precedence: bulk\r\n") {
incorrectFields = append(incorrectFields, "Precedence")
}
if len(ea) > 0 {
em := "writeMsg() failed. The following errors occurred:\n"
for e := range ea {
em += fmt.Sprintf("* incorrect %q field", ea[e])
if !strings.Contains(buffer.String(), "X-Auto-Response-Suppress: All\r\n") {
incorrectFields = append(incorrectFields, "X-Auto-Response-Suppress")
}
em += fmt.Sprintf("\n\nFull message:\n%s", ms)
t.Error(em)
if !strings.Contains(buffer.String(), "Subject: Testmail\r\n") {
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_writeMsg_PGP(t *testing.T) {
m := NewMsg(WithPGPType(PGPEncrypt))
_ = m.From(`"Toni Tester" <test@example.com>`)
_ = m.To(`"Toni Receiver" <receiver@example.com>`)
m.Subject("This is a subject")
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, `encrypted; protocol="application/pgp-encrypted"`) {
t.Errorf("writeMsg failed. Expected PGP encoding header but didn't find it in message output")
func TestMsgWriter_writePreformattedGenHeader(t *testing.T) {
t.Run("message with no preformatted headerset", func(t *testing.T) {
buffer := bytes.NewBuffer(nil)
msgwriter := &msgWriter{
writer: buffer,
charset: CharsetUTF8,
encoder: getEncoder(EncodingQP),
}
m = NewMsg(WithPGPType(PGPSignature))
_ = m.From(`"Toni Tester" <test@example.com>`)
_ = m.To(`"Toni Receiver" <receiver@example.com>`)
m.Subject("This is a subject")
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")
message := testMessage(t)
message.SetGenHeaderPreformatted(HeaderContentID, "This is a content id")
msgwriter.writeMsg(message)
if !strings.Contains(buffer.String(), "Content-ID: This is a content id\r\n") {
t.Errorf("expected preformatted header, got: %s", buffer.String())
}
})
}
func TestMsgWriter_addFiles(t *testing.T) {
msgwriter := &msgWriter{
charset: CharsetUTF8,
encoder: getEncoder(EncodingQP),
}
t.Run("message with a single file attached", func(t *testing.T) {
buffer := bytes.NewBuffer(nil)
msgwriter.writer = buffer
message := testMessage(t)
message.AttachFile("testdata/attachment.txt")
msgwriter.writeMsg(message)
if msgwriter.err != nil {
t.Errorf("msgWriter failed to write: %s", msgwriter.err)
}
switch runtime.GOOS {
case "windows":
if !strings.Contains(buffer.String(), "VGhpcyBpcyBhIHRlc3QgYXR0YWNobWVudA0K") {
t.Errorf("attachment not found in mail message. Mail: %s", buffer.String())
}
default:
if !strings.Contains(buffer.String(), "VGhpcyBpcyBhIHRlc3QgYXR0YWNobWVudAo=") {
t.Errorf("attachment not found in mail message. Mail: %s", buffer.String())
}
}
if !strings.Contains(buffer.String(), `Content-Disposition: attachment; filename="attachment.txt"`) {
t.Errorf("Content-Dispositon header not found for attachment. Mail: %s", buffer.String())
}
switch runtime.GOOS {
case "freebsd":
if !strings.Contains(buffer.String(), `Content-Type: application/octet-stream; name="attachment.txt"`) {
t.Errorf("Content-Type header not found for attachment. Mail: %s", buffer.String())
}
default:
if !strings.Contains(buffer.String(), `Content-Type: text/plain; charset=utf-8; name="attachment.txt"`) {
t.Errorf("Content-Type header not found for attachment. Mail: %s", buffer.String())
}
}
})
t.Run("message with a single file attached no extension", func(t *testing.T) {
buffer := bytes.NewBuffer(nil)
msgwriter.writer = buffer
message := testMessage(t)
message.AttachFile("testdata/attachment")
msgwriter.writeMsg(message)
if msgwriter.err != nil {
t.Errorf("msgWriter failed to write: %s", msgwriter.err)
}
switch runtime.GOOS {
case "windows":
if !strings.Contains(buffer.String(), "VGhpcyBpcyBhIHRlc3QgYXR0YWNobWVudA0K") {
t.Errorf("attachment not found in mail message. Mail: %s", buffer.String())
}
default:
if !strings.Contains(buffer.String(), "VGhpcyBpcyBhIHRlc3QgYXR0YWNobWVudAo=") {
t.Errorf("attachment not found in mail message. Mail: %s", buffer.String())
}
}
if !strings.Contains(buffer.String(), `Content-Disposition: attachment; filename="attachment"`) {
t.Errorf("Content-Dispositon header not found for attachment. Mail: %s", buffer.String())
}
if !strings.Contains(buffer.String(), `Content-Type: application/octet-stream; name="attachment"`) {
t.Errorf("Content-Type header not found for attachment. Mail: %s", buffer.String())
}
})
t.Run("message with a single file attached custom content-type", func(t *testing.T) {
buffer := bytes.NewBuffer(nil)
msgwriter.writer = buffer
message := testMessage(t)
message.AttachFile("testdata/attachment.txt", WithFileContentType(TypeAppOctetStream))
msgwriter.writeMsg(message)
if msgwriter.err != nil {
t.Errorf("msgWriter failed to write: %s", msgwriter.err)
}
switch runtime.GOOS {
case "windows":
if !strings.Contains(buffer.String(), "VGhpcyBpcyBhIHRlc3QgYXR0YWNobWVudA0K") {
t.Errorf("attachment not found in mail message. Mail: %s", buffer.String())
}
default:
if !strings.Contains(buffer.String(), "VGhpcyBpcyBhIHRlc3QgYXR0YWNobWVudAo=") {
t.Errorf("attachment not found in mail message. Mail: %s", buffer.String())
}
}
if !strings.Contains(buffer.String(), `Content-Disposition: attachment; filename="attachment.txt"`) {
t.Errorf("Content-Dispositon header not found for attachment. Mail: %s", buffer.String())
}
if !strings.Contains(buffer.String(), `Content-Type: application/octet-stream; name="attachment.txt"`) {
t.Errorf("Content-Type header not found for attachment. Mail: %s", buffer.String())
}
})
t.Run("message with a single file attached custom transfer-encoding", func(t *testing.T) {
buffer := bytes.NewBuffer(nil)
msgwriter.writer = buffer
message := testMessage(t)
message.AttachFile("testdata/attachment.txt", WithFileEncoding(EncodingUSASCII))
msgwriter.writeMsg(message)
if msgwriter.err != nil {
t.Errorf("msgWriter failed to write: %s", msgwriter.err)
}
if !strings.Contains(buffer.String(), "\r\n\r\nThis is a test attachment") {
t.Errorf("attachment not found in mail message. Mail: %s", buffer.String())
}
if !strings.Contains(buffer.String(), `Content-Disposition: attachment; filename="attachment.txt"`) {
t.Errorf("Content-Dispositon header not found for attachment. Mail: %s", buffer.String())
}
switch runtime.GOOS {
case "freebsd":
if !strings.Contains(buffer.String(), `Content-Type: application/octet-stream; name="attachment.txt"`) {
t.Errorf("Content-Type header not found for attachment. Mail: %s", buffer.String())
}
default:
if !strings.Contains(buffer.String(), `Content-Type: text/plain; charset=utf-8; name="attachment.txt"`) {
t.Errorf("Content-Type header not found for attachment. Mail: %s", buffer.String())
}
}
if !strings.Contains(buffer.String(), `Content-Transfer-Encoding: 7bit`) {
t.Errorf("Content-Transfer-Encoding header not found for attachment. Mail: %s", buffer.String())
}
})
t.Run("message with a single file attached custom description", func(t *testing.T) {
buffer := bytes.NewBuffer(nil)
msgwriter.writer = buffer
message := testMessage(t)
message.AttachFile("testdata/attachment.txt", WithFileDescription("Testdescription"))
msgwriter.writeMsg(message)
if msgwriter.err != nil {
t.Errorf("msgWriter failed to write: %s", msgwriter.err)
}
switch runtime.GOOS {
case "windows":
if !strings.Contains(buffer.String(), "VGhpcyBpcyBhIHRlc3QgYXR0YWNobWVudA0K") {
t.Errorf("attachment not found in mail message. Mail: %s", buffer.String())
}
default:
if !strings.Contains(buffer.String(), "VGhpcyBpcyBhIHRlc3QgYXR0YWNobWVudAo=") {
t.Errorf("attachment not found in mail message. Mail: %s", buffer.String())
}
}
if !strings.Contains(buffer.String(), `Content-Disposition: attachment; filename="attachment.txt"`) {
t.Errorf("Content-Dispositon header not found for attachment. Mail: %s", buffer.String())
}
switch runtime.GOOS {
case "freebsd":
if !strings.Contains(buffer.String(), `Content-Type: application/octet-stream; name="attachment.txt"`) {
t.Errorf("Content-Type header not found for attachment. Mail: %s", buffer.String())
}
default:
if !strings.Contains(buffer.String(), `Content-Type: text/plain; charset=utf-8; name="attachment.txt"`) {
t.Errorf("Content-Type header not found for attachment. Mail: %s", buffer.String())
}
}
if !strings.Contains(buffer.String(), `Content-Transfer-Encoding: base64`) {
t.Errorf("Content-Transfer-Encoding header not found for attachment. Mail: %s", buffer.String())
}
if !strings.Contains(buffer.String(), `Content-Description: Testdescription`) {
t.Errorf("Content-Description header not found for attachment. Mail: %s", buffer.String())
}
})
t.Run("message with attachment but no body part", func(t *testing.T) {
buffer := bytes.NewBuffer(nil)
msgwriter.writer = buffer
message := testMessage(t)
message.parts = nil
message.AttachFile("testdata/attachment.txt")
msgwriter.writeMsg(message)
if msgwriter.err != nil {
t.Errorf("msgWriter failed to write: %s", msgwriter.err)
}
switch runtime.GOOS {
case "windows":
if !strings.Contains(buffer.String(), "VGhpcyBpcyBhIHRlc3QgYXR0YWNobWVudA0K") {
t.Errorf("attachment not found in mail message. Mail: %s", buffer.String())
}
default:
if !strings.Contains(buffer.String(), "VGhpcyBpcyBhIHRlc3QgYXR0YWNobWVudAo=") {
t.Errorf("attachment not found in mail message. Mail: %s", buffer.String())
}
}
if !strings.Contains(buffer.String(), `Content-Disposition: attachment; filename="attachment.txt"`) {
t.Errorf("Content-Dispositon header not found for attachment. Mail: %s", buffer.String())
}
switch runtime.GOOS {
case "freebsd":
if !strings.Contains(buffer.String(), `Content-Type: application/octet-stream; name="attachment.txt"`) {
t.Errorf("Content-Type header not found for attachment. Mail: %s", buffer.String())
}
default:
if !strings.Contains(buffer.String(), `Content-Type: text/plain; charset=utf-8; name="attachment.txt"`) {
t.Errorf("Content-Type header not found for attachment. Mail: %s", buffer.String())
}
}
if !strings.Contains(buffer.String(), `Content-Transfer-Encoding: base64`) {
t.Errorf("Content-Transfer-Encoding header not found for attachment. Mail: %s", buffer.String())
}
})
}
func TestMsgWriter_writePart(t *testing.T) {
msgwriter := &msgWriter{
charset: CharsetUTF8,
encoder: getEncoder(EncodingQP),
}
t.Run("message with no part charset should use default message charset", func(t *testing.T) {
buffer := bytes.NewBuffer(nil)
msgwriter.writer = buffer
message := testMessage(t, WithCharset(CharsetUTF7))
message.AddAlternativeString(TypeTextPlain, "thisisatest")
message.parts[1].charset = ""
msgwriter.writeMsg(message)
if msgwriter.err != nil {
t.Errorf("msgWriter failed to write: %s", msgwriter.err)
}
if !strings.Contains(buffer.String(), "ontent-Type: text/plain; charset=UTF-7\r\n\r\nTestmail") {
t.Errorf("part not found in mail message. Mail: %s", buffer.String())
}
if !strings.Contains(buffer.String(), "ontent-Type: text/plain; charset=UTF-7\r\n\r\nthisisatest") {
t.Errorf("part not found in mail message. Mail: %s", buffer.String())
}
})
t.Run("message with parts that have a description", func(t *testing.T) {
buffer := bytes.NewBuffer(nil)
msgwriter.writer = buffer
message := testMessage(t)
message.AddAlternativeString(TypeTextPlain, "thisisatest")
message.parts[1].description = "thisisadescription"
msgwriter.writeMsg(message)
if msgwriter.err != nil {
t.Errorf("msgWriter failed to write: %s", msgwriter.err)
}
if !strings.Contains(buffer.String(), "Content-Description: thisisadescription") {
t.Errorf("part description not found in mail message. Mail: %s", buffer.String())
}
})
}
func TestMsgWriter_writeString(t *testing.T) {
msgwriter := &msgWriter{
charset: CharsetUTF8,
encoder: getEncoder(EncodingQP),
}
t.Run("writeString succeeds", func(t *testing.T) {
buffer := bytes.NewBuffer(nil)
msgwriter.writer = buffer
msgwriter.writeString("thisisatest")
if !strings.EqualFold(buffer.String(), "thisisatest") {
t.Errorf("writeString failed, expected: thisisatest got: %s", buffer.String())
}
})
t.Run("writeString fails", func(t *testing.T) {
msgwriter.writer = failReadWriteSeekCloser{}
msgwriter.writeString("thisisatest")
if msgwriter.err == nil {
t.Errorf("writeString succeeded, expected error")
}
})
t.Run("writeString on errored writer should return", func(t *testing.T) {
buffer := bytes.NewBuffer(nil)
msgwriter.writer = buffer
msgwriter.err = errors.New("intentional error")
msgwriter.writeString("thisisatest")
if !strings.EqualFold(buffer.String(), "") {
t.Errorf("writeString succeeded, expected: empty string, got: %s", buffer.String())
}
})
}
func TestMsgWriter_writeHeader(t *testing.T) {
msgwriter := &msgWriter{
charset: CharsetUTF8,
encoder: getEncoder(EncodingQP),
}
t.Run("writeHeader with single value", func(t *testing.T) {
buffer := bytes.NewBuffer(nil)
msgwriter.writer = buffer
msgwriter.writeHeader(HeaderMessageID, "this.is.a.test")
if !strings.EqualFold(buffer.String(), "Message-ID: this.is.a.test\r\n") {
t.Errorf("writeHeader failed, expected: %s, got: %s", "Message-ID: this.is.a.test",
buffer.String())
}
})
t.Run("writeHeader with multiple values", func(t *testing.T) {
buffer := bytes.NewBuffer(nil)
msgwriter.writer = buffer
msgwriter.writeHeader(HeaderMessageID, "this.is.a.test", "this.as.well")
if !strings.EqualFold(buffer.String(), "Message-ID: this.is.a.test, this.as.well\r\n") {
t.Errorf("writeHeader failed, expected: %s, got: %s", "Message-ID: this.is.a.test, this.as.well",
buffer.String())
}
})
t.Run("writeHeader with no values", func(t *testing.T) {
buffer := bytes.NewBuffer(nil)
msgwriter.writer = buffer
msgwriter.writeHeader(HeaderMessageID)
// While technically it is permitted to have empty headers, it's recommend to omit them if
// no value is present. We follow this recommendation.
if !strings.EqualFold(buffer.String(), "") {
t.Errorf("writeHeader failed, expected: %s, got: %s", "", buffer.String())
}
})
t.Run("writeHeader with very long value", func(t *testing.T) {
buffer := bytes.NewBuffer(nil)
msgwriter.writer = buffer
msgwriter.writeHeader(HeaderMessageID, strings.Repeat("a", MaxHeaderLength-13), "next-row")
want := "Message-ID:\r\n " + strings.Repeat("a", MaxHeaderLength-13) + ",\r\n next-row\r\n"
if !strings.EqualFold(buffer.String(), want) {
t.Errorf("writeHeader failed, expected: %s, got: %s", want, buffer.String())
}
})
}
func TestMsgWriter_writeBody(t *testing.T) {
t.Log("We only cover some edge-cases here, most of the functionality is tested already very thoroughly.")
msgwriter := &msgWriter{
charset: CharsetUTF8,
encoder: getEncoder(EncodingQP),
}
t.Run("writeBody on NoEncoding", func(t *testing.T) {
buffer := bytes.NewBuffer(nil)
msgwriter.writer = buffer
message := testMessage(t)
msgwriter.writeBody(message.parts[0].writeFunc, NoEncoding)
if msgwriter.err != nil {
t.Errorf("writeBody failed to write: %s", msgwriter.err)
}
})
t.Run("writeBody on NoEncoding fails on write", func(t *testing.T) {
msgwriter.writer = failReadWriteSeekCloser{}
message := testMessage(t)
msgwriter.writeBody(message.parts[0].writeFunc, NoEncoding)
if msgwriter.err == nil {
t.Errorf("writeBody succeeded, expected error")
}
if !strings.EqualFold(msgwriter.err.Error(), "bodyWriter io.Copy: intentional write failure") {
t.Errorf("expected error: bodyWriter io.Copy: intentional write failure, got: %s", msgwriter.err)
}
})
t.Run("writeBody on NoEncoding fails on writeFunc", func(t *testing.T) {
buffer := bytes.NewBuffer(nil)
msgwriter.writer = buffer
writeFunc := func(io.Writer) (int64, error) {
return 0, errors.New("intentional write failure")
}
msgwriter.writeBody(writeFunc, NoEncoding)
if msgwriter.err == nil {
t.Errorf("writeBody succeeded, expected error")
}
if !strings.EqualFold(msgwriter.err.Error(), "bodyWriter function: intentional write failure") {
t.Errorf("expected error: bodyWriter function: intentional write failure, got: %s", msgwriter.err)
}
})
t.Run("writeBody Quoted-Printable fails on write", func(t *testing.T) {
msgwriter.writer = failReadWriteSeekCloser{}
message := testMessage(t)
msgwriter.writeBody(message.parts[0].writeFunc, EncodingQP)
if msgwriter.err == nil {
t.Errorf("writeBody succeeded, expected error")
}
if !strings.EqualFold(msgwriter.err.Error(), "bodyWriter function: intentional write failure") {
t.Errorf("expected error: bodyWriter function: intentional write failure, got: %s", msgwriter.err)
}
})
t.Run("writeBody Quoted-Printable fails on writeFunc", func(t *testing.T) {
buffer := bytes.NewBuffer(nil)
msgwriter.writer = buffer
writeFunc := func(io.Writer) (int64, error) {
return 0, errors.New("intentional write failure")
}
msgwriter.writeBody(writeFunc, EncodingQP)
if msgwriter.err == nil {
t.Errorf("writeBody succeeded, expected error")
}
if !strings.EqualFold(msgwriter.err.Error(), "bodyWriter function: intentional write failure") {
t.Errorf("expected error: bodyWriter function: intentional write failure, got: %s", msgwriter.err)
}
})
}

1
testdata/attachment vendored Normal file
View file

@ -0,0 +1 @@
This is a test attachment

3
testdata/attachment.license vendored Normal file
View file

@ -0,0 +1,3 @@
// SPDX-FileCopyrightText: Copyright (c) 2022-2024 The go-mail Authors
//
// SPDX-License-Identifier: MIT