From 936c31e635e37735f2c3b0413a0ec0af3df83079 Mon Sep 17 00:00:00 2001 From: Nikola Kovacs Date: Thu, 30 Jul 2015 15:37:43 +0200 Subject: [PATCH 1/3] Don't create multipart if not necessary Add tests Fixes #6 --- main.go | 115 ++++++++++++++++++--------- main_test.go | 214 ++++++++++++++++++++++++++++++++++++++++++++++++--- smtp.go | 11 ++- 3 files changed, 285 insertions(+), 55 deletions(-) diff --git a/main.go b/main.go index cc485b3..21cfc69 100644 --- a/main.go +++ b/main.go @@ -18,8 +18,9 @@ import ( const crlf = "\r\n" -var ErrMissingRecipient = errors.New("No recipient specified. At one To, Cc, or Bcc recipient is required.") +var ErrMissingRecipient = errors.New("No recipient specified. At least one To, Cc, or Bcc recipient is required.") var ErrMissingFromAddress = errors.New("No from address specified.") +var ErrMissingBody = errors.New("No body specified. At least one of Body or HTMLBody is required.") // A Message represents an email message. // Addresses may be of any form permitted by RFC 5322. @@ -140,6 +141,10 @@ func (m *Message) Bytes() ([]byte, error) { return nil, ErrMissingRecipient } + if m.Body == "" && m.HTMLBody == "" { + return nil, ErrMissingBody + } + if hasTo { header.Add("To", toAddrs) } @@ -177,50 +182,68 @@ func (m *Message) Bytes() ([]byte, error) { header[k] = v } - // Top level multipart writer for our `multipart/mixed` body. - mixedw := multipart.NewWriter(buffer) - header.Add("MIME-Version", "1.0") - header.Add("Content-Type", fmt.Sprintf("multipart/mixed;%s boundary=%s", crlf, mixedw.Boundary())) - - err = writeHeader(buffer, header) - if err != nil { - return nil, err - } - // Write the start of our `multipart/mixed` body. - _, err = fmt.Fprintf(buffer, "--%s%s", mixedw.Boundary(), crlf) - if err != nil { - return nil, err + var mixedw *multipart.Writer + var hasAttachments = m.Attachments != nil && len(m.Attachments) > 0 + if hasAttachments { + // Top level multipart writer for our `multipart/mixed` body. + // only needed if we have attachments + mixedw = multipart.NewWriter(buffer) + header.Add("Content-Type", fmt.Sprintf("multipart/mixed;%s boundary=%s", crlf, mixedw.Boundary())) + err = writeHeader(buffer, header) + if err != nil { + return nil, err + } + header = textproto.MIMEHeader{} + // Write the start of our `multipart/mixed` body. + _, err = fmt.Fprintf(buffer, "--%s%s", mixedw.Boundary(), crlf) + if err != nil { + return nil, err + } } // Does the message have a body? if m.Body != "" || m.HTMLBody != "" { - // Nested multipart writer for our `multipart/alternative` body. - altw := multipart.NewWriter(buffer) + var altw *multipart.Writer + if m.Body != "" && m.HTMLBody != "" { + // Nested multipart writer for our `multipart/alternative` body. + altw = multipart.NewWriter(buffer) - header = textproto.MIMEHeader{} - header.Add("Content-Type", fmt.Sprintf("multipart/alternative;%s boundary=%s", crlf, altw.Boundary())) - err := writeHeader(buffer, header) - if err != nil { - return nil, err + header.Add("Content-Type", fmt.Sprintf("multipart/alternative;%s boundary=%s", crlf, altw.Boundary())) + err := writeHeader(buffer, header) + if err != nil { + return nil, err + } } if m.Body != "" { - header = textproto.MIMEHeader{} + if altw != nil { + header = textproto.MIMEHeader{} + } header.Add("Content-Type", "text/plain; charset=utf-8") header.Add("Content-Transfer-Encoding", "quoted-printable") //header.Add("Content-Transfer-Encoding", "base64") - partw, err := altw.CreatePart(header) - if err != nil { - return nil, err + var writer io.Writer + if altw != nil { + partw, err := altw.CreatePart(header) + if err != nil { + return nil, err + } + writer = partw + } else { + writer = buffer + err = writeHeader(buffer, header) + if err != nil { + return nil, err + } } bodyBytes := []byte(m.Body) - //encoder := NewBase64MimeEncoder(partw) - encoder := qprintable.NewEncoder(qprintable.DetectEncoding(m.Body), partw) + //encoder := NewBase64MimeEncoder(writer) + encoder := qprintable.NewEncoder(qprintable.DetectEncoding(m.Body), writer) _, err = encoder.Write(bodyBytes) if err != nil { return nil, err @@ -232,19 +255,31 @@ func (m *Message) Bytes() ([]byte, error) { } if m.HTMLBody != "" { - header = textproto.MIMEHeader{} + if altw != nil { + header = textproto.MIMEHeader{} + } header.Add("Content-Type", "text/html; charset=utf-8") //header.Add("Content-Transfer-Encoding", "quoted-printable") header.Add("Content-Transfer-Encoding", "base64") - partw, err := altw.CreatePart(header) - if err != nil { - return nil, err + var writer io.Writer + if altw != nil { + partw, err := altw.CreatePart(header) + if err != nil { + return nil, err + } + writer = partw + } else { + writer = buffer + err = writeHeader(buffer, header) + if err != nil { + return nil, err + } } htmlBodyBytes := []byte(m.HTMLBody) - encoder := NewBase64MimeEncoder(partw) - //encoder := qprintable.NewEncoder(qprintable.DetectEncoding(m.HTMLBody), partw) + encoder := NewBase64MimeEncoder(writer) + //encoder := qprintable.NewEncoder(qprintable.DetectEncoding(m.HTMLBody), writer) _, err = encoder.Write(htmlBodyBytes) if err != nil { return nil, err @@ -255,10 +290,17 @@ func (m *Message) Bytes() ([]byte, error) { } } - altw.Close() + if altw != nil { + altw.Close() + } else { + _, err = fmt.Fprintf(buffer, "%s", crlf) + if err != nil { + return nil, err + } + } } - if m.Attachments != nil && len(m.Attachments) > 0 { + if hasAttachments { for _, attachment := range m.Attachments { @@ -293,10 +335,9 @@ func (m *Message) Bytes() ([]byte, error) { } } + mixedw.Close() } - mixedw.Close() - return buffer.Bytes(), nil } diff --git a/main_test.go b/main_test.go index 4b006fa..e2036ea 100644 --- a/main_test.go +++ b/main_test.go @@ -1,31 +1,221 @@ package gophermail import ( + "bufio" + "bytes" + "encoding/base64" + "fmt" + . "github.com/onsi/gomega" + "io" + "io/ioutil" + "mime" + "mime/multipart" "net/mail" + "net/textproto" + "strings" "testing" "time" ) -func Test_Bytes(t *testing.T) { +// registerFailHandler registers a gomega fail handler that calls t.Fatal +// gomega.RegisterTestingT calls t.Error, which does not stop the test +func registerFailHandler(t *testing.T) { + RegisterFailHandler(func(message string, callerSkip ...int) { + t.Fatalf("\n%s", message) + }) +} + +// expectNoError fails the test if err is not nil +func expectNoError(err error) { + Expect(err).To(BeNil(), fmt.Sprintf("%v", err)) +} + +// getContentType gets the content type in a header, and parses it +func getContentType(header textproto.MIMEHeader) (string, map[string]string) { + contentType, ok := header["Content-Type"] + Expect(ok).To(BeTrue(), "Content-Type header not found") + Expect(contentType).NotTo(BeEmpty(), "Content-Type header is empty") + Expect(contentType).To(HaveLen(1), "More than one Content-Type header found") + + mediaType, params, err := mime.ParseMediaType(contentType[0]) + expectNoError(err) + + return mediaType, params +} + +func matchBase64(r io.Reader, expected string, msg string) { + base64contents, err := ioutil.ReadAll(r) + expectNoError(err) + + contents, err := base64.StdEncoding.DecodeString(string(base64contents)) + expectNoError(err) + Expect(string(contents)).To(Equal(expected), msg) +} + +// testMail is the main testing function +func testMail(t *testing.T, plain, html, attachment bool) { + registerFailHandler(t) + + // NOTE: QP decoding cuts off trailing whitespace + plainBody := "My Plain Text Body áűőú\n Lorem ipsum dolor sit amet, consectetur adipiscing elit.\n Nunc et purus massa. Maecenas sed ex iaculis, feugiat elit ullamcorper, eleifend elit. Aliquam ultricies libero vitae interdum maximus. Nullam placerat purus dolor, a tempor magna efficitur in. Integer mattis, lacus tempus mattis rutrum, tellus velit ultricies nisl, a elementum dolor nisi sed diam." + htmlBody := "

My HTML Body

\n

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc et purus massa.

" + filename := "test.txt" + fileContents := "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc et purus massa. Aenean sed enim turpis. Maecenas sed ex iaculis, feugiat elit ullamcorper, eleifend elit. Aliquam ultricies libero vitae interdum maximus. Nullam placerat purus dolor, a tempor magna efficitur in. Integer mattis, lacus tempus mattis rutrum, tellus velit ultricies nisl, a elementum dolor nisi sed diam. Nunc cursus arcu quis sapien dapibus suscipit. Aliquam in dolor ut enim faucibus volutpat vel id ipsum. Aenean blandit ipsum eu bibendum fermentum. Donec sagittis nunc dolor, in bibendum lorem pulvinar et. Nam elementum auctor tempor. Nunc et nisl diam. Pellentesque eget suscipit leo. Sed lacus urna, semper nec tellus a, finibus aliquet odio. Nulla in finibus justo, non congue dui." + m := &Message{} m.SetFrom("Doman Sender ") - m.SetReplyTo("Don't reply ") m.AddTo("First person ") - m.AddTo("to_2@domain.com") - m.AddCc("Less important person ") - m.AddBcc("Sneaky person ") m.Subject = "My Subject (abcdefghijklmnop qrstuvwxyz0123456789 abcdefghijklmnopqrstuvwxyz0123456789_567890)" - m.Body = "My Plain Text Body" - m.HTMLBody = "

My HTML Body

" + if plain { + m.Body = plainBody + } + if html { + m.HTMLBody = htmlBody + } + + if attachment { + m.Attachments = []Attachment{Attachment{ + Name: filename, + ContentType: "text/plain", + Data: strings.NewReader(fileContents), + }} + } + m.Headers = mail.Header{} m.Headers["Date"] = []string{time.Now().UTC().Format(time.RFC822)} - bytes, err := m.Bytes() - if err != nil { - t.Log(err) - t.Fail() + b, err := m.Bytes() + + if !plain && !html { + Expect(err).To(Equal(ErrMissingBody), "No error message on missing body") + return + } + + expectNoError(err) + + t.Logf("Bytes: \n%s", b) + + byteReader := bytes.NewReader(b) + bufReader := bufio.NewReader(byteReader) + headerReader := textproto.NewReader(bufReader) + header, err := headerReader.ReadMIMEHeader() + expectNoError(err) + + mediaType, params := getContentType(header) + + if !attachment && !(plain && html) { + if plain { + Expect(mediaType).To(Equal("text/plain"), "Content-Type is not text/plain") + } else { + Expect(mediaType).To(Equal("text/html"), "Content-Type is not text/html") + } + return + } + if attachment { + Expect(mediaType).To(Equal("multipart/mixed"), "Content-Type is not multipart/mixed") + } else { + Expect(mediaType).To(Equal("multipart/alternative"), "Content-Type is not multipart/alternative") + } + Expect(params).To(HaveKey("boundary"), "boundary is missing from Content-Type") + boundary := params["boundary"] + + plainFound := false + htmlFound := false + attachmentFound := false + + var readParts func(string, bool) + readParts = func(boundary string, toplevel bool) { + multipartReader := multipart.NewReader(bufReader, boundary) + + for { + part, err := multipartReader.NextPart() + if err == io.EOF { + break + } + expectNoError(err) + + t.Logf("part: %v", part) + + mediaType, params := getContentType(part.Header) + + dispositions, ok := part.Header["Content-Disposition"] + if ok && len(dispositions) == 1 { + attachmentMediaType, attachmentParams, err := mime.ParseMediaType(dispositions[0]) + expectNoError(err) + Expect(attachmentMediaType).To(Equal("attachment"), "attachment media type is not \"attachment\"") + Expect(attachmentParams).To(HaveKey("filename"), "filename missing from attachment") + Expect(attachmentParams["filename"]).To(Equal(filename), "filename is incorrect") + Expect(toplevel).To(BeTrue(), "attachment found in multipart/alternative") + + matchBase64(part, fileContents, "attachment does not match") + + attachmentFound = true + } else { + switch mediaType { + case "text/plain": + rawContents, err := ioutil.ReadAll(part) + expectNoError(err) + + contents := strings.Replace(string(rawContents), "\r\n", "\n", -1) + + t.Logf("\n\n%#v\n\n%#v\n\n", contents, plainBody) + Expect(contents).To(Equal(plainBody), "plain text body does not match") + + plainFound = true + case "text/html": + matchBase64(part, htmlBody, "html body does not match") + + htmlFound = true + case "multipart/alternative": + Expect(params).To(HaveKey("boundary"), "boundary is missing from Content-Type") + boundary := params["boundary"] + readParts(boundary, false) + default: + t.Logf("unexpected media type: %v", mediaType) + } + } + } + } + + readParts(boundary, true) + if plain { + Expect(plainFound).To(BeTrue(), "plain text body not found") + } else { + Expect(plainFound).NotTo(BeTrue(), "plain text body found") } + if html { + Expect(htmlFound).To(BeTrue(), "html text body not found") + } else { + Expect(htmlFound).NotTo(BeTrue(), "html text body found") + } + if attachment { + Expect(attachmentFound).To(BeTrue(), "attachment not found") + } else { + Expect(attachmentFound).NotTo(BeTrue(), "attachment found") + } +} + +func TestPlainBody(t *testing.T) { + testMail(t, true, false, false) +} + +func TestHtmlBody(t *testing.T) { + testMail(t, false, true, false) +} + +func TestAlternativeBody(t *testing.T) { + testMail(t, true, true, false) +} + +func TestPlainAttachment(t *testing.T) { + testMail(t, true, false, true) +} + +func TestHtmlPlainAttachment(t *testing.T) { + testMail(t, true, true, true) +} - t.Logf("%s", bytes) +func TestNoBody(t *testing.T) { + testMail(t, false, false, false) } diff --git a/smtp.go b/smtp.go index 9b2ab7d..67048a3 100644 --- a/smtp.go +++ b/smtp.go @@ -1,7 +1,7 @@ package gophermail import ( - "crypto/tls" + "crypto/tls" "net/smtp" ) @@ -31,7 +31,6 @@ func SendMail(addr string, a smtp.Auth, msg *Message) error { return smtp.SendMail(addr, a, msg.From.Address, to, msgBytes) } - // SendTLSMail does the same thing as SendMail, except with the added // option of providing a tls.Config func SendTLSMail(addr string, a smtp.Auth, msg *Message, cfg tls.Config) error { @@ -53,7 +52,7 @@ func SendTLSMail(addr string, a smtp.Auth, msg *Message, cfg tls.Config) error { to = append(to, address.Address) } - from := msg.From.String() + from := msg.From.String() c, err := smtp.Dial(addr) if err != nil { @@ -62,9 +61,9 @@ func SendTLSMail(addr string, a smtp.Auth, msg *Message, cfg tls.Config) error { defer c.Close() if ok, _ := c.Extension("STARTTLS"); ok { - if err = c.StartTLS(&cfg); err != nil { - return err - } + if err = c.StartTLS(&cfg); err != nil { + return err + } } if a != nil { From 73e58b0370dfddcd4090027e24b9454ff92cdaea Mon Sep 17 00:00:00 2001 From: Nikola Kovacs Date: Mon, 10 Aug 2015 16:53:58 +0200 Subject: [PATCH 2/3] Allow sending empty message. If both bodies are empty, send an empty plain text body. --- main.go | 146 ++++++++++++++++++++++++--------------------------- main_test.go | 23 ++++---- 2 files changed, 82 insertions(+), 87 deletions(-) diff --git a/main.go b/main.go index 21cfc69..e6e9a94 100644 --- a/main.go +++ b/main.go @@ -20,7 +20,6 @@ const crlf = "\r\n" var ErrMissingRecipient = errors.New("No recipient specified. At least one To, Cc, or Bcc recipient is required.") var ErrMissingFromAddress = errors.New("No from address specified.") -var ErrMissingBody = errors.New("No body specified. At least one of Body or HTMLBody is required.") // A Message represents an email message. // Addresses may be of any form permitted by RFC 5322. @@ -141,10 +140,6 @@ func (m *Message) Bytes() ([]byte, error) { return nil, ErrMissingRecipient } - if m.Body == "" && m.HTMLBody == "" { - return nil, ErrMissingBody - } - if hasTo { header.Add("To", toAddrs) } @@ -203,100 +198,97 @@ func (m *Message) Bytes() ([]byte, error) { } } - // Does the message have a body? - if m.Body != "" || m.HTMLBody != "" { + var altw *multipart.Writer + if m.Body != "" && m.HTMLBody != "" { + // Nested multipart writer for our `multipart/alternative` body. + altw = multipart.NewWriter(buffer) - var altw *multipart.Writer - if m.Body != "" && m.HTMLBody != "" { - // Nested multipart writer for our `multipart/alternative` body. - altw = multipart.NewWriter(buffer) - - header.Add("Content-Type", fmt.Sprintf("multipart/alternative;%s boundary=%s", crlf, altw.Boundary())) - err := writeHeader(buffer, header) - if err != nil { - return nil, err - } + header.Add("Content-Type", fmt.Sprintf("multipart/alternative;%s boundary=%s", crlf, altw.Boundary())) + err := writeHeader(buffer, header) + if err != nil { + return nil, err } + } - if m.Body != "" { - if altw != nil { - header = textproto.MIMEHeader{} - } - header.Add("Content-Type", "text/plain; charset=utf-8") - header.Add("Content-Transfer-Encoding", "quoted-printable") - //header.Add("Content-Transfer-Encoding", "base64") - - var writer io.Writer - if altw != nil { - partw, err := altw.CreatePart(header) - if err != nil { - return nil, err - } - writer = partw - } else { - writer = buffer - err = writeHeader(buffer, header) - if err != nil { - return nil, err - } - } + // Only include an empty plain text body if the html body is also empty. + if m.Body != "" || m.Body == "" && m.HTMLBody == "" { + if altw != nil { + header = textproto.MIMEHeader{} + } + header.Add("Content-Type", "text/plain; charset=utf-8") + header.Add("Content-Transfer-Encoding", "quoted-printable") + //header.Add("Content-Transfer-Encoding", "base64") - bodyBytes := []byte(m.Body) - //encoder := NewBase64MimeEncoder(writer) - encoder := qprintable.NewEncoder(qprintable.DetectEncoding(m.Body), writer) - _, err = encoder.Write(bodyBytes) + var writer io.Writer + if altw != nil { + partw, err := altw.CreatePart(header) if err != nil { return nil, err } - err = encoder.Close() + writer = partw + } else { + writer = buffer + err = writeHeader(buffer, header) if err != nil { return nil, err } } - if m.HTMLBody != "" { - if altw != nil { - header = textproto.MIMEHeader{} - } - header.Add("Content-Type", "text/html; charset=utf-8") - //header.Add("Content-Transfer-Encoding", "quoted-printable") - header.Add("Content-Transfer-Encoding", "base64") + bodyBytes := []byte(m.Body) + //encoder := NewBase64MimeEncoder(writer) + encoder := qprintable.NewEncoder(qprintable.DetectEncoding(m.Body), writer) + _, err = encoder.Write(bodyBytes) + if err != nil { + return nil, err + } + err = encoder.Close() + if err != nil { + return nil, err + } + } - var writer io.Writer - if altw != nil { - partw, err := altw.CreatePart(header) - if err != nil { - return nil, err - } - writer = partw - } else { - writer = buffer - err = writeHeader(buffer, header) - if err != nil { - return nil, err - } - } + if m.HTMLBody != "" { + if altw != nil { + header = textproto.MIMEHeader{} + } + header.Add("Content-Type", "text/html; charset=utf-8") + //header.Add("Content-Transfer-Encoding", "quoted-printable") + header.Add("Content-Transfer-Encoding", "base64") - htmlBodyBytes := []byte(m.HTMLBody) - encoder := NewBase64MimeEncoder(writer) - //encoder := qprintable.NewEncoder(qprintable.DetectEncoding(m.HTMLBody), writer) - _, err = encoder.Write(htmlBodyBytes) + var writer io.Writer + if altw != nil { + partw, err := altw.CreatePart(header) if err != nil { return nil, err } - err = encoder.Close() + writer = partw + } else { + writer = buffer + err = writeHeader(buffer, header) if err != nil { return nil, err } } - if altw != nil { - altw.Close() - } else { - _, err = fmt.Fprintf(buffer, "%s", crlf) - if err != nil { - return nil, err - } + htmlBodyBytes := []byte(m.HTMLBody) + encoder := NewBase64MimeEncoder(writer) + //encoder := qprintable.NewEncoder(qprintable.DetectEncoding(m.HTMLBody), writer) + _, err = encoder.Write(htmlBodyBytes) + if err != nil { + return nil, err + } + err = encoder.Close() + if err != nil { + return nil, err + } + } + + if altw != nil { + altw.Close() + } else { + _, err = fmt.Fprintf(buffer, "%s", crlf) + if err != nil { + return nil, err } } diff --git a/main_test.go b/main_test.go index e2036ea..a36f73b 100644 --- a/main_test.go +++ b/main_test.go @@ -87,11 +87,6 @@ func testMail(t *testing.T, plain, html, attachment bool) { b, err := m.Bytes() - if !plain && !html { - Expect(err).To(Equal(ErrMissingBody), "No error message on missing body") - return - } - expectNoError(err) t.Logf("Bytes: \n%s", b) @@ -105,10 +100,10 @@ func testMail(t *testing.T, plain, html, attachment bool) { mediaType, params := getContentType(header) if !attachment && !(plain && html) { - if plain { - Expect(mediaType).To(Equal("text/plain"), "Content-Type is not text/plain") - } else { + if html { Expect(mediaType).To(Equal("text/html"), "Content-Type is not text/html") + } else { + Expect(mediaType).To(Equal("text/plain"), "Content-Type is not text/plain") } return } @@ -160,7 +155,11 @@ func testMail(t *testing.T, plain, html, attachment bool) { contents := strings.Replace(string(rawContents), "\r\n", "\n", -1) t.Logf("\n\n%#v\n\n%#v\n\n", contents, plainBody) - Expect(contents).To(Equal(plainBody), "plain text body does not match") + expectedBody := plainBody + if !plain { + expectedBody = "" + } + Expect(contents).To(Equal(expectedBody), "plain text body does not match") plainFound = true case "text/html": @@ -179,7 +178,7 @@ func testMail(t *testing.T, plain, html, attachment bool) { } readParts(boundary, true) - if plain { + if plain || !plain && !html { Expect(plainFound).To(BeTrue(), "plain text body not found") } else { Expect(plainFound).NotTo(BeTrue(), "plain text body found") @@ -219,3 +218,7 @@ func TestHtmlPlainAttachment(t *testing.T) { func TestNoBody(t *testing.T) { testMail(t, false, false, false) } + +func TestNoBodyAttachment(t *testing.T) { + testMail(t, false, false, true) +} From dd571e9879a2980a394ee221be1a6febc99253f8 Mon Sep 17 00:00:00 2001 From: Nikola Kovacs Date: Wed, 30 Nov 2016 16:57:31 +0100 Subject: [PATCH 3/3] Fix html+plain+attachment test under go 1.7 The test used the multipart reader incorrectly, which broke under go 1.7. When reading a part, the returned `multipart.Part` should be used as the source, not the original buffer. --- main_test.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/main_test.go b/main_test.go index a36f73b..5f6116b 100644 --- a/main_test.go +++ b/main_test.go @@ -119,9 +119,9 @@ func testMail(t *testing.T, plain, html, attachment bool) { htmlFound := false attachmentFound := false - var readParts func(string, bool) - readParts = func(boundary string, toplevel bool) { - multipartReader := multipart.NewReader(bufReader, boundary) + var readParts func(io.Reader, string, bool) + readParts = func(r io.Reader, boundary string, toplevel bool) { + multipartReader := multipart.NewReader(r, boundary) for { part, err := multipartReader.NextPart() @@ -169,7 +169,7 @@ func testMail(t *testing.T, plain, html, attachment bool) { case "multipart/alternative": Expect(params).To(HaveKey("boundary"), "boundary is missing from Content-Type") boundary := params["boundary"] - readParts(boundary, false) + readParts(part, boundary, false) default: t.Logf("unexpected media type: %v", mediaType) } @@ -177,7 +177,7 @@ func testMail(t *testing.T, plain, html, attachment bool) { } } - readParts(boundary, true) + readParts(bufReader, boundary, true) if plain || !plain && !html { Expect(plainFound).To(BeTrue(), "plain text body not found") } else {