Skip to content

Commit 7e0e484

Browse files
authored
Merge pull request #422 from wneessen/chore/420_improve-tests-for-multipart-messages
chore: improve tests for multipart messages
2 parents f781a50 + 267da1a commit 7e0e484

File tree

3 files changed

+310
-27
lines changed

3 files changed

+310
-27
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,7 @@ jobs:
153153
cancel-in-progress: true
154154
strategy:
155155
matrix:
156-
osver: ['14.1', '14.0', 13.4']
156+
osver: ['14.2', '14.1', 13.4']
157157
env:
158158
TEST_BASEPORT: ${{ vars.TEST_BASEPORT }}
159159
TEST_BASEPORT_SMTP: ${{ vars.TEST_BASEPORT_SMTP }}

msg_test.go

Lines changed: 292 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,21 @@ import (
1919
"net"
2020
"os"
2121
"reflect"
22+
"runtime"
2223
"strings"
2324
"testing"
2425
ttpl "text/template"
2526
"time"
2627
)
2728

29+
type msgContentTest struct {
30+
line int
31+
data string
32+
exact bool
33+
dataIsPrefix bool
34+
dataIsSuffix bool
35+
}
36+
2837
var (
2938
charsetTests = []struct {
3039
name string
@@ -5939,6 +5948,260 @@ func TestMsg_WriteTo(t *testing.T) {
59395948
t.Errorf("expected S/MIME signing error to contain: %q, got: %s", expErr, err)
59405949
}
59415950
})
5951+
t.Run("WriteTo Multipart plain text with attachment", func(t *testing.T) {
5952+
message := testMessage(t)
5953+
message.AttachFile("testdata/attachment.txt")
5954+
buffer := bytes.NewBuffer(nil)
5955+
if _, err := message.WriteTo(buffer); err != nil {
5956+
t.Fatalf("failed to write message to buffer: %s", err)
5957+
}
5958+
fileContentType := "text/plain; charset=utf-8"
5959+
if runtime.GOOS == "freebsd" {
5960+
fileContentType = "application/octet-stream"
5961+
}
5962+
wants := []msgContentTest{
5963+
{0, "Date:", false, true, false},
5964+
{1, "MIME-Version: 1.0", true, true, false},
5965+
{2, "Message-ID: <", false, true, false},
5966+
{2, ">", false, false, true},
5967+
{8, "Content-Type: multipart/mixed;", true, true, false},
5968+
{9, " boundary=", false, true, false},
5969+
{10, "", true, false, false},
5970+
{11, "--", false, true, false},
5971+
{12, "Content-Transfer-Encoding: quoted-printable", true, true, false},
5972+
{13, "Content-Type: text/plain; charset=UTF-8", true, true, false},
5973+
{14, "", true, false, false},
5974+
{15, "Testmail", true, true, false},
5975+
{16, "--", false, true, false},
5976+
{17, `Content-Disposition: attachment; filename="attachment.txt"`, true, true, false},
5977+
{18, `Content-Transfer-Encoding: base64`, true, true, false},
5978+
{19, `Content-Type: ` + fileContentType + `; name="attachment.txt"`, true, true, false},
5979+
{20, "", true, false, false},
5980+
{21, "VGhpcyBpcyBhIHRlc3Qg", false, true, false},
5981+
{22, "", true, false, false},
5982+
{23, "--", false, true, true},
5983+
}
5984+
checkMessageContent(t, buffer, wants)
5985+
})
5986+
t.Run("WriteTo Multipart plain text with alternative", func(t *testing.T) {
5987+
message := testMessage(t)
5988+
message.AddAlternativeString(TypeTextHTML, "<p>HTML alternative</p>")
5989+
buffer := bytes.NewBuffer(nil)
5990+
if _, err := message.WriteTo(buffer); err != nil {
5991+
t.Fatalf("failed to write message to buffer: %s", err)
5992+
}
5993+
wants := []msgContentTest{
5994+
{0, "Date:", false, true, false},
5995+
{1, "MIME-Version: 1.0", true, true, false},
5996+
{2, "Message-ID: <", false, true, false},
5997+
{2, ">", false, false, true},
5998+
{8, "Content-Type: multipart/alternative;", true, true, false},
5999+
{9, " boundary=", false, true, false},
6000+
{10, "", true, false, false},
6001+
{11, "--", false, true, false},
6002+
{12, "Content-Transfer-Encoding: quoted-printable", true, true, false},
6003+
{13, "Content-Type: text/plain; charset=UTF-8", true, true, false},
6004+
{14, "", true, false, false},
6005+
{15, "Testmail", true, true, false},
6006+
{16, "--", false, true, false},
6007+
{17, `Content-Transfer-Encoding: quoted-printable`, true, true, false},
6008+
{18, `Content-Type: text/html; charset=UTF-8`, true, true, false},
6009+
{19, "", true, false, false},
6010+
{20, `<p>HTML alternative</p>`, true, true, false},
6011+
{21, "--", false, true, true},
6012+
}
6013+
checkMessageContent(t, buffer, wants)
6014+
})
6015+
t.Run("WriteTo Multipart two alternative parts", func(t *testing.T) {
6016+
message := NewMsg()
6017+
if message == nil {
6018+
t.Fatal("failed to create new message")
6019+
}
6020+
if err := message.From(TestSenderValid); err != nil {
6021+
t.Errorf("failed to set sender address: %s", err)
6022+
}
6023+
if err := message.To(TestRcptValid); err != nil {
6024+
t.Errorf("failed to set recipient address: %s", err)
6025+
}
6026+
message.Subject("Testmail")
6027+
message.AddAlternativeString(TypeTextPlain, "Plain alternative")
6028+
message.AddAlternativeString(TypeTextHTML, "<p>HTML main part</p>")
6029+
buffer := bytes.NewBuffer(nil)
6030+
if _, err := message.WriteTo(buffer); err != nil {
6031+
t.Fatalf("failed to write message to buffer: %s", err)
6032+
}
6033+
wants := []msgContentTest{
6034+
{0, "Date:", false, true, false},
6035+
{1, "MIME-Version: 1.0", true, true, false},
6036+
{2, "Message-ID: <", false, true, false},
6037+
{2, ">", false, false, true},
6038+
{8, "Content-Type: multipart/alternative;", true, true, false},
6039+
{9, " boundary=", false, true, false},
6040+
{10, "", true, false, false},
6041+
{11, "--", false, true, false},
6042+
{12, "Content-Transfer-Encoding: quoted-printable", true, true, false},
6043+
{13, "Content-Type: text/plain; charset=UTF-8", true, true, false},
6044+
{14, "", true, false, false},
6045+
{15, "Plain alternative", true, true, false},
6046+
{16, "--", false, true, false},
6047+
{17, "Content-Transfer-Encoding: quoted-printable", true, true, false},
6048+
{18, "Content-Type: text/html; charset=UTF-8", true, true, false},
6049+
{19, "", true, false, false},
6050+
{20, "<p>HTML main part</p>", true, true, false},
6051+
{21, "--", false, true, true},
6052+
}
6053+
checkMessageContent(t, buffer, wants)
6054+
})
6055+
t.Run("WriteTo Multipart plain body, alternative html, attachment and embed", func(t *testing.T) {
6056+
message := testMessage(t)
6057+
message.AddAlternativeString(TypeTextHTML, "<p>HTML alternative part</p>")
6058+
message.AttachFile("testdata/attachment.txt")
6059+
message.EmbedFile("testdata/embed.txt")
6060+
buffer := bytes.NewBuffer(nil)
6061+
if _, err := message.WriteTo(buffer); err != nil {
6062+
t.Fatalf("failed to write message to buffer: %s", err)
6063+
}
6064+
fileContentType := "text/plain; charset=utf-8"
6065+
if runtime.GOOS == "freebsd" {
6066+
fileContentType = "application/octet-stream"
6067+
}
6068+
wants := []msgContentTest{
6069+
{0, "Date:", false, true, false},
6070+
{1, "MIME-Version: 1.0", true, true, false},
6071+
{2, "Message-ID: <", false, true, false},
6072+
{2, ">", false, false, true},
6073+
{6, "From: <valid-from@domain.tld>", true, true, false},
6074+
{7, "To: <valid-to@domain.tld>", true, true, false},
6075+
{8, `Content-Type: multipart/mixed;`, true, true, false},
6076+
{9, ` boundary=`, false, true, false},
6077+
{10, "", true, false, false},
6078+
{11, "--", false, true, false},
6079+
{12, `Content-Type: multipart/related;`, true, true, false},
6080+
{13, ` boundary=`, false, true, false},
6081+
{14, "", true, false, false},
6082+
{15, "--", false, true, false},
6083+
{16, `Content-Type: multipart/alternative;`, true, true, false},
6084+
{17, ` boundary=`, false, true, false},
6085+
{18, "", true, false, false},
6086+
{19, "--", false, true, false},
6087+
{20, "Content-Transfer-Encoding: quoted-printable", true, true, false},
6088+
{21, "Content-Type: text/plain; charset=UTF-8", true, true, false},
6089+
{22, "", true, false, false},
6090+
{23, "Testmail", true, true, false},
6091+
{24, "--", false, true, false},
6092+
{25, "Content-Transfer-Encoding: quoted-printable", true, true, false},
6093+
{26, "Content-Type: text/html; charset=UTF-8", true, true, false},
6094+
{27, "", true, false, false},
6095+
{28, `<p>HTML alternative part</p>`, true, true, false},
6096+
{29, "--", false, true, true},
6097+
{30, "", true, false, false},
6098+
{31, "--", false, true, false},
6099+
{32, `Content-Disposition: inline; filename="embed.txt"`, true, true, false},
6100+
{33, "Content-Id: <embed.txt>", true, true, false},
6101+
{34, "Content-Transfer-Encoding: base64", true, true, false},
6102+
{35, `Content-Type: ` + fileContentType + `; name="embed.txt"`, true, true, false},
6103+
{36, "", true, false, false},
6104+
{37, "VGhp", false, true, false},
6105+
{38, "", true, false, false},
6106+
{39, "--", false, true, true},
6107+
{40, "", true, false, false},
6108+
{41, "--", false, true, false},
6109+
{42, `Content-Disposition: attachment; filename="attachment.txt"`, true, true, false},
6110+
{43, "Content-Transfer-Encoding: base64", true, true, false},
6111+
{44, `Content-Type: ` + fileContentType + `; name="attachment.txt"`, true, true, false},
6112+
{45, "", true, false, false},
6113+
{46, "VGhp", false, true, false},
6114+
{47, "", true, false, false},
6115+
{48, "--", false, true, true},
6116+
}
6117+
checkMessageContent(t, buffer, wants)
6118+
})
6119+
t.Run("WriteTo Multipart plain body, alternative html, attachment, embed and S/MIME signed", func(t *testing.T) {
6120+
message := testMessage(t)
6121+
message.AddAlternativeString(TypeTextHTML, "<p>HTML alternative part</p>")
6122+
message.AttachFile("testdata/attachment.txt")
6123+
message.EmbedFile("testdata/embed.txt")
6124+
keypair, err := getDummyKeyPairTLS()
6125+
if err != nil {
6126+
t.Fatalf("failed to load dummy key material: %s", err)
6127+
}
6128+
if err = message.SignWithTLSCertificate(keypair); err != nil {
6129+
t.Fatalf("failed to initialize S/MIME signing: %s", err)
6130+
}
6131+
buffer := bytes.NewBuffer(nil)
6132+
if _, err := message.WriteTo(buffer); err != nil {
6133+
t.Fatalf("failed to write message to buffer: %s", err)
6134+
}
6135+
fileContentType := "text/plain; charset=utf-8"
6136+
if runtime.GOOS == "freebsd" {
6137+
fileContentType = "application/octet-stream"
6138+
}
6139+
wants := []msgContentTest{
6140+
{0, "Date:", false, true, false},
6141+
{1, "MIME-Version: 1.0", true, true, false},
6142+
{2, "Message-ID: <", false, true, false},
6143+
{2, ">", false, false, true},
6144+
{6, "From: <valid-from@domain.tld>", true, true, false},
6145+
{7, "To: <valid-to@domain.tld>", true, true, false},
6146+
{
6147+
8, `Content-Type: multipart/signed; protocol="application/pkcs7-signature"; micalg=sha-256;`, true,
6148+
true, false,
6149+
},
6150+
{9, ` boundary=`, false, true, false},
6151+
{10, "", true, false, false},
6152+
{11, "--", false, true, false},
6153+
{12, `Content-Type: multipart/mixed;`, true, true, false},
6154+
{13, ` boundary=`, false, true, false},
6155+
{14, "", true, false, false},
6156+
{15, "--", false, true, false},
6157+
{16, `Content-Type: multipart/related;`, true, true, false},
6158+
{17, ` boundary=`, false, true, false},
6159+
{18, "", true, false, false},
6160+
{19, "--", false, true, false},
6161+
{20, `Content-Type: multipart/alternative;`, true, true, false},
6162+
{21, ` boundary=`, false, true, false},
6163+
{22, "", true, false, false},
6164+
{23, "--", false, true, false},
6165+
{24, "Content-Transfer-Encoding: quoted-printable", true, true, false},
6166+
{25, "Content-Type: text/plain; charset=UTF-8", true, true, false},
6167+
{26, "", true, false, false},
6168+
{27, "Testmail", true, true, false},
6169+
{28, "--", false, true, false},
6170+
{29, "Content-Transfer-Encoding: quoted-printable", true, true, false},
6171+
{30, "Content-Type: text/html; charset=UTF-8", true, true, false},
6172+
{31, "", true, false, false},
6173+
{32, `<p>HTML alternative part</p>`, true, true, false},
6174+
{33, "--", false, true, true},
6175+
{34, "", true, false, false},
6176+
{35, "--", false, true, false},
6177+
{36, `Content-Disposition: inline; filename="embed.txt"`, true, true, false},
6178+
{37, "Content-Id: <embed.txt>", true, true, false},
6179+
{38, "Content-Transfer-Encoding: base64", true, true, false},
6180+
{39, `Content-Type: ` + fileContentType + `; name="embed.txt"`, true, true, false},
6181+
{40, "", true, false, false},
6182+
{41, "VGhp", false, true, false},
6183+
{42, "", true, false, false},
6184+
{43, "--", false, true, true},
6185+
{44, "", true, false, false},
6186+
{45, "--", false, true, false},
6187+
{46, `Content-Disposition: attachment; filename="attachment.txt"`, true, true, false},
6188+
{47, "Content-Transfer-Encoding: base64", true, true, false},
6189+
{48, `Content-Type: ` + fileContentType + `; name="attachment.txt"`, true, true, false},
6190+
{49, "", true, false, false},
6191+
{50, "VGhp", false, true, false},
6192+
{51, "", true, false, false},
6193+
{52, "--", false, true, true},
6194+
{53, "", true, false, false},
6195+
{54, "--", false, true, false},
6196+
{55, "Content-Transfer-Encoding: base64", true, true, false},
6197+
{56, `Content-Type: application/pkcs7-signature; name="smime.p7s"`, true, true, false},
6198+
{57, "", true, false, false},
6199+
{58, "MII", false, true, false},
6200+
{121, "", true, false, false},
6201+
{122, "--", false, true, true},
6202+
}
6203+
checkMessageContent(t, buffer, wants)
6204+
})
59426205
}
59436206

59446207
func TestMsg_WriteToFile(t *testing.T) {
@@ -7177,6 +7440,35 @@ func hasSendmail() bool {
71777440
return false
71787441
}
71797442

7443+
func checkMessageContent(t *testing.T, buffer *bytes.Buffer, wants []msgContentTest) {
7444+
t.Helper()
7445+
lines := strings.Split(buffer.String(), "\r\n")
7446+
for _, want := range wants {
7447+
if len(lines) <= want.line {
7448+
t.Errorf("expected line %d to be present, got: %d lines in total", want.line, len(lines)-1)
7449+
continue
7450+
}
7451+
if !strings.Contains(lines[want.line], want.data) {
7452+
t.Errorf("expected line %d to contain %q, got: %q", want.line, want.data, lines[want.line])
7453+
}
7454+
if want.exact {
7455+
if !strings.EqualFold(lines[want.line], want.data) {
7456+
t.Errorf("expected line %d to be exactly %q, got: %q", want.line, want.data, lines[want.line])
7457+
}
7458+
}
7459+
if want.dataIsPrefix {
7460+
if !strings.HasPrefix(lines[want.line], want.data) {
7461+
t.Errorf("expected line %d to start with %q, got: %q", want.line, want.data, lines[want.line])
7462+
}
7463+
}
7464+
if want.dataIsSuffix {
7465+
if !strings.HasSuffix(lines[want.line], want.data) {
7466+
t.Errorf("expected line %d to end with %q, got: %q", want.line, want.data, lines[want.line])
7467+
}
7468+
}
7469+
}
7470+
}
7471+
71807472
// Fuzzing tests
71817473
func FuzzMsg_Subject(f *testing.F) {
71827474
f.Add("Testsubject")

quicksend_test.go

Lines changed: 17 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -97,34 +97,25 @@ func TestQuickSend(t *testing.T) {
9797
t.Fatalf("failed to send email: %s", err)
9898
}
9999

100+
wants := []msgContentTest{
101+
{8, "STARTTLS", true, true, false},
102+
{17, "AUTH PLAIN AHVzZXJuYW1lAHBhc3N3b3Jk", true, true, false},
103+
{21, "MAIL FROM:<valid-from@domain.tld> BODY=8BITMIME SMTPUTF8", true, true, false},
104+
{23, "RCPT TO:<valid-to@domain.tld>", true, true, false},
105+
{30, "Subject: " + subject, true, true, false},
106+
{33, "From: <valid-from@domain.tld>", true, true, false},
107+
{34, "To: <valid-to@domain.tld>", true, true, false},
108+
{35, "Content-Transfer-Encoding: quoted-printable", true, true, false},
109+
{36, "Content-Type: text/plain; charset=UTF-8", true, true, false},
110+
{38, "This is a test body", true, true, false},
111+
{39, "With multiple lines", true, true, false},
112+
{40, "", true, true, false},
113+
{41, "Best,", true, true, false},
114+
{42, " The go-mail team", true, true, false},
115+
}
100116
props.BufferMutex.RLock()
101-
resp := strings.Split(echoBuffer.String(), "\r\n")
117+
checkMessageContent(t, echoBuffer, wants)
102118
props.BufferMutex.RUnlock()
103-
104-
expects := []struct {
105-
line int
106-
data string
107-
}{
108-
{8, "STARTTLS"},
109-
{17, "AUTH PLAIN AHVzZXJuYW1lAHBhc3N3b3Jk"},
110-
{21, "MAIL FROM:<valid-from@domain.tld> BODY=8BITMIME SMTPUTF8"},
111-
{23, "RCPT TO:<valid-to@domain.tld>"},
112-
{30, "Subject: " + subject},
113-
{33, "From: <valid-from@domain.tld>"},
114-
{34, "To: <valid-to@domain.tld>"},
115-
{35, "Content-Transfer-Encoding: quoted-printable"},
116-
{36, "Content-Type: text/plain; charset=UTF-8"},
117-
{38, "This is a test body"},
118-
{39, "With multiple lines"},
119-
{40, ""},
120-
{41, "Best,"},
121-
{42, " The go-mail team"},
122-
}
123-
for _, expect := range expects {
124-
if !strings.EqualFold(resp[expect.line], expect.data) {
125-
t.Errorf("expected %q at line %d, got: %q", expect.data, expect.line, resp[expect.line])
126-
}
127-
}
128119
})
129120
t.Run("QuickSend with authentication and TLS and multiple receipients", func(t *testing.T) {
130121
ctxAuth, cancelAuth := context.WithCancel(context.Background())

0 commit comments

Comments
 (0)