From 709b4e6b916170ca18f22a43d9e52e61794b9ec2 Mon Sep 17 00:00:00 2001 From: Winni Neessen Date: Fri, 18 Mar 2022 17:08:05 +0100 Subject: [PATCH] Switched write functions to return the number of bytes. Due to failing tests in Msg.Write() --- file.go | 2 +- msg.go | 36 +++++----- msg_test.go | 187 +++++++++++++++++++++++++++++++++++++++++++++++++-- msgwriter.go | 9 ++- part.go | 2 +- 5 files changed, 208 insertions(+), 28 deletions(-) diff --git a/file.go b/file.go index 25e7c41..b2e3936 100644 --- a/file.go +++ b/file.go @@ -12,7 +12,7 @@ type FileOption func(*File) type File struct { Name string Header textproto.MIMEHeader - Writer func(w io.Writer) error + Writer func(w io.Writer) (int64, error) } // WithFileName sets the filename of the File diff --git a/msg.go b/msg.go index 00041f0..65cd101 100644 --- a/msg.go +++ b/msg.go @@ -389,15 +389,15 @@ func (m *Msg) GetRecipients() ([]string, error) { // SetBodyString sets the body of the message. func (m *Msg) SetBodyString(ct ContentType, b string, o ...PartOption) { buf := bytes.NewBufferString(b) - w := func(w io.Writer) error { - _, err := io.Copy(w, buf) - return err + w := func(w io.Writer) (int64, error) { + nb, err := io.Copy(w, buf) + return nb, err } m.SetBodyWriter(ct, w, o...) } // SetBodyWriter sets the body of the message. -func (m *Msg) SetBodyWriter(ct ContentType, w func(io.Writer) error, o ...PartOption) { +func (m *Msg) SetBodyWriter(ct ContentType, w func(io.Writer) (int64, error), o ...PartOption) { p := m.newPart(ct, o...) p.w = w m.parts = []*Part{p} @@ -406,15 +406,15 @@ func (m *Msg) SetBodyWriter(ct ContentType, w func(io.Writer) error, o ...PartOp // AddAlternativeString sets the alternative body of the message. func (m *Msg) AddAlternativeString(ct ContentType, b string, o ...PartOption) { buf := bytes.NewBufferString(b) - w := func(w io.Writer) error { - _, err := io.Copy(w, buf) - return err + w := func(w io.Writer) (int64, error) { + nb, err := io.Copy(w, buf) + return nb, err } m.AddAlternativeWriter(ct, w, o...) } // AddAlternativeWriter sets the body of the message. -func (m *Msg) AddAlternativeWriter(ct ContentType, w func(io.Writer) error, o ...PartOption) { +func (m *Msg) AddAlternativeWriter(ct ContentType, w func(io.Writer) (int64, error), o ...PartOption) { p := m.newPart(ct, o...) p.w = w m.parts = append(m.parts, p) @@ -601,16 +601,17 @@ func fileFromFS(n string) *File { return &File{ Name: filepath.Base(n), Header: make(map[string][]string), - Writer: func(w io.Writer) error { + Writer: func(w io.Writer) (int64, error) { h, err := os.Open(n) if err != nil { - return err + return 0, err } - if _, err := io.Copy(w, h); err != nil { + nb, err := io.Copy(w, h) + if err != nil { _ = h.Close() - return fmt.Errorf("failed to copy file to io.Writer: %w", err) + return nb, fmt.Errorf("failed to copy file to io.Writer: %w", err) } - return h.Close() + return nb, h.Close() }, } } @@ -620,11 +621,12 @@ func fileFromReader(n string, r io.Reader) *File { return &File{ Name: filepath.Base(n), Header: make(map[string][]string), - Writer: func(w io.Writer) error { - if _, err := io.Copy(w, r); err != nil { - return err + Writer: func(w io.Writer) (int64, error) { + nb, err := io.Copy(w, r) + if err != nil { + return nb, err } - return nil + return nb, nil }, } } diff --git a/msg_test.go b/msg_test.go index b2a6af9..a7fb77a 100644 --- a/msg_test.go +++ b/msg_test.go @@ -1,6 +1,7 @@ package mail import ( + "bufio" "bytes" "fmt" "io" @@ -855,7 +856,7 @@ func TestMsg_SetBodyString(t *testing.T) { } part := m.parts[0] res := bytes.Buffer{} - if err := part.w(&res); err != nil && !tt.sf { + if _, err := part.w(&res); err != nil && !tt.sf { t.Errorf("WriteFunc of part failed: %s", err) } if res.String() != tt.want { @@ -890,7 +891,7 @@ func TestMsg_AddAlternativeString(t *testing.T) { } apart := m.parts[1] res := bytes.Buffer{} - if err := apart.w(&res); err != nil && !tt.sf { + if _, err := apart.w(&res); err != nil && !tt.sf { t.Errorf("WriteFunc of part failed: %s", err) } if res.String() != tt.want { @@ -932,7 +933,7 @@ func TestMsg_AttachFile(t *testing.T) { file.Name) } buf := bytes.Buffer{} - if err := file.Writer(&buf); err != nil { + if _, err := file.Writer(&buf); err != nil { t.Errorf("failed to execute WriterFunc: %s", err) return } @@ -956,11 +957,185 @@ func TestMsg_AttachFileBrokenFunc(t *testing.T) { t.Errorf("AttachFile() failed. Attachment file pointer is nil") return } - file.Writer = func(io.Writer) error { - return fmt.Errorf("failing intentionally") + file.Writer = func(io.Writer) (int64, error) { + return 0, fmt.Errorf("failing intentionally") } buf := bytes.Buffer{} - if err := file.Writer(&buf); err == nil { + if _, err := file.Writer(&buf); err == nil { t.Errorf("execute WriterFunc did not fail, but was expected to fail") } } + +// TestMsg_AttachReader tests the Msg.AttachReader method +func TestMsg_AttachReader(t *testing.T) { + m := NewMsg() + ts := "This is a test string" + rbuf := bytes.Buffer{} + rbuf.WriteString(ts) + r := bufio.NewReader(&rbuf) + m.AttachReader("testfile.txt", r) + if len(m.attachments) != 1 { + t.Errorf("AttachReader() failed. Number of attachments expected: %d, got: %d", 1, + len(m.attachments)) + return + } + file := m.attachments[0] + if file == nil { + t.Errorf("AttachReader() failed. Attachment file pointer is nil") + return + } + if file.Name != "testfile.txt" { + t.Errorf("AttachReader() failed. Expected file name: %s, got: %s", "testfile.txt", + file.Name) + } + wbuf := bytes.Buffer{} + if _, err := file.Writer(&wbuf); err != nil { + t.Errorf("execute WriterFunc failed: %s", err) + } + if wbuf.String() != ts { + t.Errorf("AttachReader() failed. Expected string: %q, got: %q", ts, wbuf.String()) + } +} + +// TestMsg_EmbedFile tests the Msg.EmbedFile and the WithFilename FileOption method +func TestMsg_EmbedFile(t *testing.T) { + tests := []struct { + name string + file string + fn string + sf bool + }{ + {"File: README.md", "README.md", "README.md", false}, + {"File: doc.go", "doc.go", "foo.go", false}, + {"File: nonexisting", "", "invalid.file", true}, + } + m := NewMsg() + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + m.EmbedFile(tt.file, WithFileName(tt.fn), nil) + if len(m.embeds) != 1 && !tt.sf { + t.Errorf("EmbedFile() failed. Number of embeds expected: %d, got: %d", 1, + len(m.embeds)) + return + } + if !tt.sf { + file := m.embeds[0] + if file == nil { + t.Errorf("EmbedFile() failed. Embedded file pointer is nil") + return + } + if file.Name != tt.fn { + t.Errorf("EmbedFile() failed. Filename of embeds expected: %s, got: %s", tt.fn, + file.Name) + } + buf := bytes.Buffer{} + if _, err := file.Writer(&buf); err != nil { + t.Errorf("failed to execute WriterFunc: %s", err) + return + } + } + m.Reset() + }) + } +} + +// TestMsg_EmbedFileBrokenFunc tests WriterFunc of the Msg.EmbedFile method +func TestMsg_EmbedFileBrokenFunc(t *testing.T) { + m := NewMsg() + m.EmbedFile("README.md") + if len(m.embeds) != 1 { + t.Errorf("EmbedFile() failed. Number of embeds expected: %d, got: %d", 1, + len(m.embeds)) + return + } + file := m.embeds[0] + if file == nil { + t.Errorf("EmbedFile() failed. Embedded file pointer is nil") + return + } + file.Writer = func(io.Writer) (int64, error) { + return 0, fmt.Errorf("failing intentionally") + } + buf := bytes.Buffer{} + if _, err := file.Writer(&buf); err == nil { + t.Errorf("execute WriterFunc did not fail, but was expected to fail") + } +} + +// TestMsg_EmbedReader tests the Msg.EmbedReader method +func TestMsg_EmbedReader(t *testing.T) { + m := NewMsg() + ts := "This is a test string" + rbuf := bytes.Buffer{} + rbuf.WriteString(ts) + r := bufio.NewReader(&rbuf) + m.EmbedReader("testfile.txt", r) + if len(m.embeds) != 1 { + t.Errorf("EmbedReader() failed. Number of embeds expected: %d, got: %d", 1, + len(m.embeds)) + return + } + file := m.embeds[0] + if file == nil { + t.Errorf("EmbedReader() failed. Embedded file pointer is nil") + return + } + if file.Name != "testfile.txt" { + t.Errorf("EmbedReader() failed. Expected file name: %s, got: %s", "testfile.txt", + file.Name) + } + wbuf := bytes.Buffer{} + if _, err := file.Writer(&wbuf); err != nil { + t.Errorf("execute WriterFunc failed: %s", err) + } + if wbuf.String() != ts { + t.Errorf("EmbedReader() failed. Expected string: %q, got: %q", ts, wbuf.String()) + } +} + +// TestMsg_hasAlt tests the hasAlt() method of the Msg +func TestMsg_hasAlt(t *testing.T) { + m := NewMsg() + m.SetBodyString(TypeTextPlain, "Plain") + m.AddAlternativeString(TypeTextHTML, "HTML") + if !m.hasAlt() { + t.Errorf("mail has alternative parts but hasAlt() returned true") + } +} + +// TestMsg_hasRelated tests the hasRelated() method of the Msg +func TestMsg_hasRelated(t *testing.T) { + m := NewMsg() + m.SetBodyString(TypeTextPlain, "Plain") + m.EmbedFile("README.md") + if !m.hasRelated() { + t.Errorf("mail has related parts but hasRelated() returned true") + } +} + +// TestMsg_hasMixed tests the hasMixed() method of the Msg +func TestMsg_hasMixed(t *testing.T) { + m := NewMsg() + m.SetBodyString(TypeTextPlain, "Plain") + m.AttachFile("README.md") + if !m.hasMixed() { + t.Errorf("mail has mixed parts but hasMixed() returned true") + } +} + +// TestMsg_Write tests the Write() method of the Msg +func TestMsg_Write(t *testing.T) { + m := NewMsg() + m.SetBodyString(TypeTextPlain, "Plain") + wbuf := bytes.Buffer{} + n, err := m.Write(&wbuf) + if err != nil { + t.Errorf("Write() failed: %s", err) + return + } + if n != int64(wbuf.Len()) { + t.Errorf("Write() failed: expected written byte length: %d, got: %d", n, wbuf.Len()) + fmt.Printf("%s", wbuf.String()) + } + +} diff --git a/msgwriter.go b/msgwriter.go index eb69cee..7effe41 100644 --- a/msgwriter.go +++ b/msgwriter.go @@ -241,9 +241,10 @@ func (mw *msgWriter) writeHeader(k Header, v ...string) { } // writeBody writes an io.Reader into an io.Writer using provided Encoding -func (mw *msgWriter) writeBody(f func(io.Writer) error, e Encoding) { +func (mw *msgWriter) writeBody(f func(io.Writer) (int64, error), e Encoding) { var w io.Writer var ew io.WriteCloser + var n int64 if mw.d == 0 { w = mw.w } @@ -257,12 +258,14 @@ func (mw *msgWriter) writeBody(f func(io.Writer) error, e Encoding) { case EncodingB64: ew = base64.NewEncoder(base64.StdEncoding, w) case NoEncoding: - mw.err = f(w) + n, mw.err = f(w) + mw.n += n return default: ew = quotedprintable.NewWriter(w) } - mw.err = f(ew) + n, mw.err = f(ew) + mw.n += int64(n) mw.err = ew.Close() } diff --git a/part.go b/part.go index a6ad094..941af9b 100644 --- a/part.go +++ b/part.go @@ -9,7 +9,7 @@ type PartOption func(*Part) type Part struct { ctype ContentType enc Encoding - w func(io.Writer) error + w func(io.Writer) (int64, error) } // SetEncoding creates a new mime.WordEncoder based on the encoding setting of the message