diff --git a/msg.go b/msg.go index 029ae65..db019e6 100644 --- a/msg.go +++ b/msg.go @@ -483,6 +483,11 @@ func (m *Msg) GetGenHeader(h Header) []string { return m.genHeader[h] } +// GetParts returns the message parts of the Msg +func (m *Msg) GetParts() []*Part { + return m.parts +} + // SetBodyString sets the body of the message. func (m *Msg) SetBodyString(ct ContentType, b string, o ...PartOption) { buf := bytes.NewBufferString(b) diff --git a/part.go b/part.go index d1996d6..e25eeeb 100644 --- a/part.go +++ b/part.go @@ -4,7 +4,10 @@ package mail -import "io" +import ( + "bytes" + "io" +) // PartOption returns a function that can be used for grouping Part options type PartOption func(*Part) @@ -16,11 +19,51 @@ type Part struct { w func(io.Writer) (int64, error) } +// GetContent executes the WriteFunc of the Part and returns the content as byte slice +func (p *Part) GetContent() ([]byte, error) { + var b bytes.Buffer + if _, err := p.w(&b); err != nil { + return nil, err + } + return b.Bytes(), nil +} + +// GetContentType returns the currently set ContentType of the Part +func (p *Part) GetContentType() ContentType { + return p.ctype +} + +// GetEncoding returns the currently set Encoding of the Part +func (p *Part) GetEncoding() Encoding { + return p.enc +} + +// GetWriteFunc returns the currently set WriterFunc of the Part +func (p *Part) GetWriteFunc() func(io.Writer) (int64, error) { + return p.w +} + +// SetContent overrides the content of the Part with the given string +func (p *Part) SetContent(c string) { + buf := bytes.NewBufferString(c) + p.w = writeFuncFromBuffer(buf) +} + +// SetContentType overrides the ContentType of the Part +func (p *Part) SetContentType(c ContentType) { + p.ctype = c +} + // SetEncoding creates a new mime.WordEncoder based on the encoding setting of the message func (p *Part) SetEncoding(e Encoding) { p.enc = e } +// SetWriteFunc overrides the WriteFunc of the Part +func (p *Part) SetWriteFunc(w func(io.Writer) (int64, error)) { + p.w = w +} + // WithPartEncoding overrides the default Part encoding func WithPartEncoding(e Encoding) PartOption { return func(p *Part) { diff --git a/part_test.go b/part_test.go index 3a567b8..eeaa611 100644 --- a/part_test.go +++ b/part_test.go @@ -4,7 +4,13 @@ package mail -import "testing" +import ( + "bytes" + "fmt" + "io" + "strings" + "testing" +) // TestPartEncoding tests the WithPartEncoding and Part.SetEncoding methods func TestPartEncoding(t *testing.T) { @@ -38,3 +44,208 @@ func TestPartEncoding(t *testing.T) { }) } } + +// TestPartContentType tests Part.SetContentType +func TestPart_SetContentType(t *testing.T) { + tests := []struct { + name string + ct ContentType + want string + }{ + {"ContentType: text/plain", TypeTextPlain, "text/plain"}, + {"ContentType: text/html", TypeTextHTML, "text/html"}, + {"ContentType: application/json", "application/json", "application/json"}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + m := NewMsg() + m.SetBodyString(TypeTextPlain, "This is a test with ümläutß") + pl, err := getPartList(m) + if err != nil { + t.Errorf("failed: %s", err) + return + } + pl[0].SetContentType(tt.ct) + ct := pl[0].GetContentType() + if string(ct) != tt.want { + t.Errorf("SetContentType failed. Got: %s, expected: %s", string(ct), tt.want) + } + }) + } +} + +// TestPartEncoding tests Part.GetEncoding +func TestPart_GetEncoding(t *testing.T) { + tests := []struct { + name string + enc Encoding + want string + }{ + {"Part encoding: Base64", EncodingB64, "base64"}, + {"Part encoding: Quoted-Printable", EncodingQP, "quoted-printable"}, + {"Part encoding: 8bit", NoEncoding, "8bit"}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + m := NewMsg() + m.SetBodyString(TypeTextPlain, "This is a test with ümläutß", WithPartEncoding(tt.enc)) + pl, err := getPartList(m) + if err != nil { + t.Errorf("failed: %s", err) + return + } + e := pl[0].GetEncoding() + if e.String() != tt.want { + t.Errorf("Part.GetEncoding failed. Expected: %s, got: %s", tt.want, e.String()) + } + }) + } +} + +// TestPart_GetContentType tests Part.GetContentType +func TestPart_GetContentType(t *testing.T) { + tests := []struct { + name string + ct ContentType + want string + }{ + {"ContentType: text/plain", TypeTextPlain, "text/plain"}, + {"ContentType: text/html", TypeTextHTML, "text/html"}, + {"ContentType: application/json", "application/json", "application/json"}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + m := NewMsg() + m.SetBodyString(tt.ct, "This is a test with ümläutß") + pl, err := getPartList(m) + if err != nil { + t.Errorf("failed: %s", err) + return + } + c := pl[0].GetContentType() + if string(c) != tt.want { + t.Errorf("Part.GetContentType failed. Expected: %s, got: %s", tt.want, string(c)) + } + }) + } +} + +// TestPart_GetWriteFunc tests Part.GetWriteFunc +func TestPart_GetWriteFunc(t *testing.T) { + c := "This is a test with ümläutß" + m := NewMsg() + m.SetBodyString(TypeTextPlain, c) + pl, err := getPartList(m) + if err != nil { + t.Errorf("failed: %s", err) + return + } + wf := pl[0].GetWriteFunc() + var b bytes.Buffer + if _, err := wf(&b); err != nil { + t.Errorf("failed to execute writefunc: %s", err) + } + if b.String() != c { + t.Errorf("GetWriteFunc failed. Expected: %s, got: %s", c, b.String()) + } +} + +// TestPart_GetContent tests Part.GetContent +func TestPart_GetContent(t *testing.T) { + c := "This is a test with ümläutß" + m := NewMsg() + m.SetBodyString(TypeTextPlain, c) + pl, err := getPartList(m) + if err != nil { + t.Errorf("failed: %s", err) + return + } + cb, err := pl[0].GetContent() + if err != nil { + t.Errorf("Part.GetContent failed: %s", err) + } + if string(cb) != c { + t.Errorf("Part.GetContent failed. Expected: %s, got: %s", c, string(cb)) + } +} + +// TestPart_GetContentBroken tests Part.GetContent +func TestPart_GetContentBroken(t *testing.T) { + c := "This is a test with ümläutß" + m := NewMsg() + m.SetBodyString(TypeTextPlain, c) + pl, err := getPartList(m) + if err != nil { + t.Errorf("failed: %s", err) + return + } + pl[0].w = func(io.Writer) (int64, error) { + return 0, fmt.Errorf("broken") + } + _, err = pl[0].GetContent() + if err == nil { + t.Errorf("Part.GetContent was supposed to failed, but didn't") + } +} + +// TestPart_SetWriteFunc tests Part.SetWriteFunc +func TestPart_SetWriteFunc(t *testing.T) { + c := "This is a test with ümläutß" + m := NewMsg() + m.SetBodyString(TypeTextPlain, c) + pl, err := getPartList(m) + if err != nil { + t.Errorf("failed: %s", err) + return + } + cb, err := pl[0].GetContent() + if err != nil { + t.Errorf("Part.GetContent failed: %s", err) + } + pl[0].SetWriteFunc(func(w io.Writer) (int64, error) { + ns := strings.ToUpper(string(cb)) + buf := bytes.NewBufferString(ns) + nb, err := w.Write(buf.Bytes()) + return int64(nb), err + }) + nc, err := pl[0].GetContent() + if err != nil { + t.Errorf("Part.GetContent failed: %s", err) + } + if string(nc) != strings.ToUpper(c) { + t.Errorf("SetWriteFunc failed. Expected: %s, got: %s", strings.ToUpper(c), string(nc)) + } +} + +// TestPart_SetContent tests Part.SetContent +func TestPart_SetContent(t *testing.T) { + c := "This is a test with ümläutß" + m := NewMsg() + m.SetBodyString(TypeTextPlain, c) + pl, err := getPartList(m) + if err != nil { + t.Errorf("failed: %s", err) + return + } + cb, err := pl[0].GetContent() + if err != nil { + t.Errorf("Part.GetContent failed: %s", err) + } + pl[0].SetContent(strings.ToUpper(string(cb))) + nc, err := pl[0].GetContent() + if err != nil { + t.Errorf("Part.GetContent failed: %s", err) + } + if string(nc) != strings.ToUpper(c) { + t.Errorf("SetContent failed. Expected: %s, got: %s", strings.ToUpper(c), string(nc)) + } +} + +// getPartList is a helper function +func getPartList(m *Msg) ([]*Part, error) { + pl := m.GetParts() + if len(pl) <= 0 { + return nil, fmt.Errorf("Msg.GetParts failed. Part list is empty") + } + return pl, nil +}