Switched write functions to return the number of bytes. Due to failing tests in Msg.Write()

This commit is contained in:
Winni Neessen 2022-03-18 17:08:05 +01:00
parent bf755cd944
commit 709b4e6b91
Signed by: wneessen
GPG key ID: 5F3AF39B820C119D
5 changed files with 208 additions and 28 deletions

View file

@ -12,7 +12,7 @@ type FileOption func(*File)
type File struct { type File struct {
Name string Name string
Header textproto.MIMEHeader Header textproto.MIMEHeader
Writer func(w io.Writer) error Writer func(w io.Writer) (int64, error)
} }
// WithFileName sets the filename of the File // WithFileName sets the filename of the File

36
msg.go
View file

@ -389,15 +389,15 @@ func (m *Msg) GetRecipients() ([]string, error) {
// SetBodyString sets the body of the message. // SetBodyString sets the body of the message.
func (m *Msg) SetBodyString(ct ContentType, b string, o ...PartOption) { func (m *Msg) SetBodyString(ct ContentType, b string, o ...PartOption) {
buf := bytes.NewBufferString(b) buf := bytes.NewBufferString(b)
w := func(w io.Writer) error { w := func(w io.Writer) (int64, error) {
_, err := io.Copy(w, buf) nb, err := io.Copy(w, buf)
return err return nb, err
} }
m.SetBodyWriter(ct, w, o...) m.SetBodyWriter(ct, w, o...)
} }
// SetBodyWriter sets the body of the message. // 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 := m.newPart(ct, o...)
p.w = w p.w = w
m.parts = []*Part{p} 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. // AddAlternativeString sets the alternative body of the message.
func (m *Msg) AddAlternativeString(ct ContentType, b string, o ...PartOption) { func (m *Msg) AddAlternativeString(ct ContentType, b string, o ...PartOption) {
buf := bytes.NewBufferString(b) buf := bytes.NewBufferString(b)
w := func(w io.Writer) error { w := func(w io.Writer) (int64, error) {
_, err := io.Copy(w, buf) nb, err := io.Copy(w, buf)
return err return nb, err
} }
m.AddAlternativeWriter(ct, w, o...) m.AddAlternativeWriter(ct, w, o...)
} }
// AddAlternativeWriter sets the body of the message. // 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 := m.newPart(ct, o...)
p.w = w p.w = w
m.parts = append(m.parts, p) m.parts = append(m.parts, p)
@ -601,16 +601,17 @@ func fileFromFS(n string) *File {
return &File{ return &File{
Name: filepath.Base(n), Name: filepath.Base(n),
Header: make(map[string][]string), Header: make(map[string][]string),
Writer: func(w io.Writer) error { Writer: func(w io.Writer) (int64, error) {
h, err := os.Open(n) h, err := os.Open(n)
if err != nil { 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() _ = 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{ return &File{
Name: filepath.Base(n), Name: filepath.Base(n),
Header: make(map[string][]string), Header: make(map[string][]string),
Writer: func(w io.Writer) error { Writer: func(w io.Writer) (int64, error) {
if _, err := io.Copy(w, r); err != nil { nb, err := io.Copy(w, r)
return err if err != nil {
return nb, err
} }
return nil return nb, nil
}, },
} }
} }

View file

@ -1,6 +1,7 @@
package mail package mail
import ( import (
"bufio"
"bytes" "bytes"
"fmt" "fmt"
"io" "io"
@ -855,7 +856,7 @@ func TestMsg_SetBodyString(t *testing.T) {
} }
part := m.parts[0] part := m.parts[0]
res := bytes.Buffer{} 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) t.Errorf("WriteFunc of part failed: %s", err)
} }
if res.String() != tt.want { if res.String() != tt.want {
@ -890,7 +891,7 @@ func TestMsg_AddAlternativeString(t *testing.T) {
} }
apart := m.parts[1] apart := m.parts[1]
res := bytes.Buffer{} 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) t.Errorf("WriteFunc of part failed: %s", err)
} }
if res.String() != tt.want { if res.String() != tt.want {
@ -932,7 +933,7 @@ func TestMsg_AttachFile(t *testing.T) {
file.Name) file.Name)
} }
buf := bytes.Buffer{} 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) t.Errorf("failed to execute WriterFunc: %s", err)
return return
} }
@ -956,11 +957,185 @@ func TestMsg_AttachFileBrokenFunc(t *testing.T) {
t.Errorf("AttachFile() failed. Attachment file pointer is nil") t.Errorf("AttachFile() failed. Attachment file pointer is nil")
return return
} }
file.Writer = func(io.Writer) error { file.Writer = func(io.Writer) (int64, error) {
return fmt.Errorf("failing intentionally") return 0, fmt.Errorf("failing intentionally")
} }
buf := bytes.Buffer{} 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") 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, "<b>HTML</b>")
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())
}
}

View file

@ -241,9 +241,10 @@ func (mw *msgWriter) writeHeader(k Header, v ...string) {
} }
// writeBody writes an io.Reader into an io.Writer using provided Encoding // 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 w io.Writer
var ew io.WriteCloser var ew io.WriteCloser
var n int64
if mw.d == 0 { if mw.d == 0 {
w = mw.w w = mw.w
} }
@ -257,12 +258,14 @@ func (mw *msgWriter) writeBody(f func(io.Writer) error, e Encoding) {
case EncodingB64: case EncodingB64:
ew = base64.NewEncoder(base64.StdEncoding, w) ew = base64.NewEncoder(base64.StdEncoding, w)
case NoEncoding: case NoEncoding:
mw.err = f(w) n, mw.err = f(w)
mw.n += n
return return
default: default:
ew = quotedprintable.NewWriter(w) ew = quotedprintable.NewWriter(w)
} }
mw.err = f(ew) n, mw.err = f(ew)
mw.n += int64(n)
mw.err = ew.Close() mw.err = ew.Close()
} }

View file

@ -9,7 +9,7 @@ type PartOption func(*Part)
type Part struct { type Part struct {
ctype ContentType ctype ContentType
enc Encoding 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 // SetEncoding creates a new mime.WordEncoder based on the encoding setting of the message