Skip to content

Commit

Permalink
Merge pull request #50 from dhia-gharsallaoui/main
Browse files Browse the repository at this point in the history
Implement middleware concept and their tests
  • Loading branch information
wneessen authored Sep 23, 2022
2 parents 69db3b7 + a4733f0 commit 3a5f639
Show file tree
Hide file tree
Showing 2 changed files with 162 additions and 37 deletions.
25 changes: 24 additions & 1 deletion msg.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,11 @@ const (
errParseMailAddr = "failed to parse mail address %q: %w"
)

// Middleware is an interface to define a function to apply to Msg before sending
type Middleware interface {
Handle(*Msg) *Msg
}

// Msg is the mail message struct
type Msg struct {
// addrHeader is a slice of strings that the different mail AddrHeader fields
Expand Down Expand Up @@ -73,6 +78,9 @@ type Msg struct {

// parts represent the different parts of the Msg
parts []*Part

// middlewares is the list of middlewares to apply to the Msg before sending in FIFO order
middlewares []Middleware
}

// SendmailPath is the default system path to the sendmail binary
Expand Down Expand Up @@ -133,6 +141,13 @@ func WithBoundary(b string) MsgOption {
}
}

// WithMiddleware add the given middleware in the end of the list of the client middlewares
func WithMiddleware(mw Middleware) MsgOption {
return func(m *Msg) {
m.middlewares = append(m.middlewares, mw)
}
}

// SetCharset sets the encoding charset of the Msg
func (m *Msg) SetCharset(c Charset) {
m.charset = c
Expand Down Expand Up @@ -657,10 +672,18 @@ func (m *Msg) Reset() {
m.parts = nil
}

// ApplyMiddlewares apply the list of middlewares to a Msg
func (m *Msg) applyMiddlewares(ms *Msg) *Msg {
for _, mw := range m.middlewares {
ms = mw.Handle(ms)
}
return ms
}

// WriteTo writes the formated Msg into a give io.Writer and satisfies the io.WriteTo interface
func (m *Msg) WriteTo(w io.Writer) (int64, error) {
mw := &msgWriter{w: w, c: m.charset, en: m.encoder}
mw.writeMsg(m)
mw.writeMsg(m.applyMiddlewares(m))
return mw.n, mw.err
}

Expand Down
174 changes: 138 additions & 36 deletions msg_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,75 @@ func TestNewMsgWithBoundary(t *testing.T) {
}
}

type uppercaseMiddleware struct{}

func (mw uppercaseMiddleware) Handle(m *Msg) *Msg {
s, ok := m.genHeader[HeaderSubject]
if !ok {
fmt.Println("can't find the subject header")
}
m.Subject(strings.ToUpper(s[0]))
return m
}

type encodeMiddleware struct{}

func (mw encodeMiddleware) Handle(m *Msg) *Msg {
s, ok := m.genHeader[HeaderSubject]
if !ok {
fmt.Println("can't find the subject header")
}
m.Subject(strings.Replace(s[0], "a", "@", -1))
return m
}

// TestNewMsgWithMiddleware tests WithMiddleware
func TestNewMsgWithMiddleware(t *testing.T) {
m := NewMsg()
if len(m.middlewares) != 0 {
t.Errorf("empty middlewares failed. m.middlewares expected to be: empty, got: %d middleware", len(m.middlewares))
}
m = NewMsg(WithMiddleware(uppercaseMiddleware{}))
if len(m.middlewares) != 1 {
t.Errorf("empty middlewares failed. m.middlewares expected to be: 1, got: %d middleware", len(m.middlewares))
}
m = NewMsg(WithMiddleware(uppercaseMiddleware{}), WithMiddleware(encodeMiddleware{}))
if len(m.middlewares) != 2 {
t.Errorf("empty middlewares failed. m.middlewares expected to be: 2, got: %d middleware", len(m.middlewares))
}
}

// TestApplyMiddlewares tests the applyMiddlewares for the Msg object
func TestApplyMiddlewares(t *testing.T) {
tests := []struct {
name string
sub string
want string
}{
{"normal subject", "This is a test subject", "THIS IS @ TEST SUBJECT"},
{"subject with one middleware effect", "This is test subject", "THIS IS TEST SUBJECT"},
{"subject with one middleware effect", "This is A test subject", "THIS IS A TEST SUBJECT"},
}
m := NewMsg(WithMiddleware(encodeMiddleware{}), WithMiddleware(uppercaseMiddleware{}))
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
m.Subject(tt.sub)
if m.genHeader[HeaderSubject] == nil {
t.Errorf("Subject() method failed in applyMiddlewares() test. Generic header for subject is empty")
return
}
m = m.applyMiddlewares(m)
s, ok := m.genHeader[HeaderSubject]
if !ok {
t.Errorf("failed to get subject header")
}
if s[0] != tt.want {
t.Errorf("applyMiddlewares() method failed. Expected: %s, got: %s", tt.want, s[0])
}
})
}
}

// TestMsg_SetHEader tests Msg.SetHeader
func TestMsg_SetHeader(t *testing.T) {
tests := []struct {
Expand Down Expand Up @@ -626,12 +695,18 @@ func TestMsg_FromFormat(t *testing.T) {
want string
fail bool
}{
{"valid name and addr", "Toni Tester", "[email protected]",
`"Toni Tester" <[email protected]>`, false},
{"no name with valid addr", "", "[email protected]",
`<[email protected]>`, false},
{"valid name with invalid addr", "Toni Tester", "@example.com",
``, true},
{
"valid name and addr", "Toni Tester", "[email protected]",
`"Toni Tester" <[email protected]>`, false,
},
{
"no name with valid addr", "", "[email protected]",
`<[email protected]>`, false,
},
{
"valid name with invalid addr", "Toni Tester", "@example.com",
``, true,
},
}

m := NewMsg()
Expand Down Expand Up @@ -697,7 +772,7 @@ func TestMsg_GetRecipients(t *testing.T) {
return
}

var tf, cf, bf = false, false, false
tf, cf, bf := false, false, false
for _, r := range al {
if r == a[0] {
tf = true
Expand Down Expand Up @@ -732,12 +807,18 @@ func TestMsg_ReplyTo(t *testing.T) {
want string
sf bool
}{
{"valid name and addr", "Toni Tester", "[email protected]",
`"Toni Tester" <[email protected]>`, false},
{"no name with valid addr", "", "[email protected]",
`<[email protected]>`, false},
{"valid name with invalid addr", "Toni Tester", "@example.com",
``, true},
{
"valid name and addr", "Toni Tester", "[email protected]",
`"Toni Tester" <[email protected]>`, false,
},
{
"no name with valid addr", "", "[email protected]",
`<[email protected]>`, false,
},
{
"valid name with invalid addr", "Toni Tester", "@example.com",
``, true,
},
}
m := NewMsg()
for _, tt := range tests {
Expand Down Expand Up @@ -792,10 +873,14 @@ func TestMsg_Subject(t *testing.T) {
want string
}{
{"normal subject", "This is a test subject", "This is a test subject"},
{"subject with umlauts", "This is a test subject with umlauts: üäöß",
"=?UTF-8?q?This_is_a_test_subject_with_umlauts:_=C3=BC=C3=A4=C3=B6=C3=9F?="},
{"subject with emoji", "This is a test subject with emoji: 📧",
"=?UTF-8?q?This_is_a_test_subject_with_emoji:_=F0=9F=93=A7?="},
{
"subject with umlauts", "This is a test subject with umlauts: üäöß",
"=?UTF-8?q?This_is_a_test_subject_with_umlauts:_=C3=BC=C3=A4=C3=B6=C3=9F?=",
},
{
"subject with emoji", "This is a test subject with emoji: 📧",
"=?UTF-8?q?This_is_a_test_subject_with_emoji:_=F0=9F=93=A7?=",
},
}
m := NewMsg()
for _, tt := range tests {
Expand Down Expand Up @@ -868,7 +953,6 @@ func TestMsg_SetImportance(t *testing.T) {
m.genHeader = make(map[Header][]string)
})
}

}

// TestMsg_SetOrganization tests the Msg.SetOrganization method
Expand Down Expand Up @@ -1021,8 +1105,10 @@ func TestMsg_SetBodyString(t *testing.T) {
sf bool
}{
{"Body: test", TypeTextPlain, "test", "test", false},
{"Body: with Umlauts", TypeTextHTML, "<strong>üäöß</strong>",
"<strong>üäöß</strong>", false},
{
"Body: with Umlauts", TypeTextHTML, "<strong>üäöß</strong>",
"<strong>üäöß</strong>", false,
},
{"Body: with emoji", TypeTextPlain, "📧", "📧", false},
}
m := NewMsg()
Expand Down Expand Up @@ -1654,8 +1740,10 @@ func TestMsg_SetBodyHTMLTemplate(t *testing.T) {
sf bool
}{
{"normal HTML", "<p>This is a {{.Placeholder}}</p>", "TemplateTest", "TemplateTest", false},
{"HTML with HTML", "<p>This is a {{.Placeholder}}</p>", "<script>alert(1)</script>",
"&lt;script&gt;alert(1)&lt;/script&gt;", false},
{
"HTML with HTML", "<p>This is a {{.Placeholder}}</p>", "<script>alert(1)</script>",
"&lt;script&gt;alert(1)&lt;/script&gt;", false,
},
{"invalid tpl", "<p>This is a {{ foo .Placeholder}}</p>", "TemplateTest", "", true},
}

Expand Down Expand Up @@ -1738,8 +1826,10 @@ func TestMsg_AddAlternativeHTMLTemplate(t *testing.T) {
sf bool
}{
{"normal HTML", "<p>This is a {{.Placeholder}}</p>", "TemplateTest", "TemplateTest", false},
{"HTML with HTML", "<p>This is a {{.Placeholder}}</p>", "<script>alert(1)</script>",
"&lt;script&gt;alert(1)&lt;/script&gt;", false},
{
"HTML with HTML", "<p>This is a {{.Placeholder}}</p>", "<script>alert(1)</script>",
"&lt;script&gt;alert(1)&lt;/script&gt;", false,
},
{"invalid tpl", "<p>This is a {{ foo .Placeholder}}</p>", "TemplateTest", "", true},
}

Expand Down Expand Up @@ -1782,8 +1872,10 @@ func TestMsg_AttachTextTemplate(t *testing.T) {
ac int
sf bool
}{
{"normal text", "This is a {{.Placeholder}}", "TemplateTest",
"VGhpcyBpcyBhIFRlbXBsYXRlVGVzdA==", 1, false},
{
"normal text", "This is a {{.Placeholder}}", "TemplateTest",
"VGhpcyBpcyBhIFRlbXBsYXRlVGVzdA==", 1, false,
},
{"invalid tpl", "This is a {{ foo .Placeholder}}", "TemplateTest", "", 0, true},
}

Expand Down Expand Up @@ -1829,10 +1921,14 @@ func TestMsg_AttachHTMLTemplate(t *testing.T) {
ac int
sf bool
}{
{"normal HTML", "<p>This is a {{.Placeholder}}</p>", "TemplateTest",
"PHA+VGhpcyBpcyBhIFRlbXBsYXRlVGVzdDwvcD4=", 1, false},
{"HTML with HTML", "<p>This is a {{.Placeholder}}</p>", "<script>alert(1)</script>",
"PHA+VGhpcyBpcyBhICZsdDtzY3JpcHQmZ3Q7YWxlcnQoMSkmbHQ7L3NjcmlwdCZndDs8L3A+", 1, false},
{
"normal HTML", "<p>This is a {{.Placeholder}}</p>", "TemplateTest",
"PHA+VGhpcyBpcyBhIFRlbXBsYXRlVGVzdDwvcD4=", 1, false,
},
{
"HTML with HTML", "<p>This is a {{.Placeholder}}</p>", "<script>alert(1)</script>",
"PHA+VGhpcyBpcyBhICZsdDtzY3JpcHQmZ3Q7YWxlcnQoMSkmbHQ7L3NjcmlwdCZndDs8L3A+", 1, false,
},
{"invalid tpl", "<p>This is a {{ foo .Placeholder}}</p>", "TemplateTest", "", 0, true},
}

Expand Down Expand Up @@ -1878,8 +1974,10 @@ func TestMsg_EmbedTextTemplate(t *testing.T) {
ec int
sf bool
}{
{"normal text", "This is a {{.Placeholder}}", "TemplateTest",
"VGhpcyBpcyBhIFRlbXBsYXRlVGVzdA==", 1, false},
{
"normal text", "This is a {{.Placeholder}}", "TemplateTest",
"VGhpcyBpcyBhIFRlbXBsYXRlVGVzdA==", 1, false,
},
{"invalid tpl", "This is a {{ foo .Placeholder}}", "TemplateTest", "", 0, true},
}

Expand Down Expand Up @@ -1925,10 +2023,14 @@ func TestMsg_EmbedHTMLTemplate(t *testing.T) {
ec int
sf bool
}{
{"normal HTML", "<p>This is a {{.Placeholder}}</p>", "TemplateTest",
"PHA+VGhpcyBpcyBhIFRlbXBsYXRlVGVzdDwvcD4=", 1, false},
{"HTML with HTML", "<p>This is a {{.Placeholder}}</p>", "<script>alert(1)</script>",
"PHA+VGhpcyBpcyBhICZsdDtzY3JpcHQmZ3Q7YWxlcnQoMSkmbHQ7L3NjcmlwdCZndDs8L3A+", 1, false},
{
"normal HTML", "<p>This is a {{.Placeholder}}</p>", "TemplateTest",
"PHA+VGhpcyBpcyBhIFRlbXBsYXRlVGVzdDwvcD4=", 1, false,
},
{
"HTML with HTML", "<p>This is a {{.Placeholder}}</p>", "<script>alert(1)</script>",
"PHA+VGhpcyBpcyBhICZsdDtzY3JpcHQmZ3Q7YWxlcnQoMSkmbHQ7L3NjcmlwdCZndDs8L3A+", 1, false,
},
{"invalid tpl", "<p>This is a {{ foo .Placeholder}}</p>", "TemplateTest", "", 0, true},
}

Expand Down

0 comments on commit 3a5f639

Please sign in to comment.