Implement middleware concept and their test

This commit is contained in:
dhia gharsallaoui 2022-09-22 18:05:47 +02:00
parent 7b030473a6
commit a4733f0618
2 changed files with 162 additions and 37 deletions

25
msg.go
View file

@ -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
@ -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
@ -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
@ -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
}

View file

@ -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 {
@ -626,12 +695,18 @@ func TestMsg_FromFormat(t *testing.T) {
want string
fail bool
}{
{"valid name and addr", "Toni Tester", "tester@example.com",
`"Toni Tester" <tester@example.com>`, false},
{"no name with valid addr", "", "tester@example.com",
`<tester@example.com>`, false},
{"valid name with invalid addr", "Toni Tester", "@example.com",
``, true},
{
"valid name and addr", "Toni Tester", "tester@example.com",
`"Toni Tester" <tester@example.com>`, false,
},
{
"no name with valid addr", "", "tester@example.com",
`<tester@example.com>`, false,
},
{
"valid name with invalid addr", "Toni Tester", "@example.com",
``, true,
},
}
m := NewMsg()
@ -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
@ -732,12 +807,18 @@ func TestMsg_ReplyTo(t *testing.T) {
want string
sf bool
}{
{"valid name and addr", "Toni Tester", "tester@example.com",
`"Toni Tester" <tester@example.com>`, false},
{"no name with valid addr", "", "tester@example.com",
`<tester@example.com>`, false},
{"valid name with invalid addr", "Toni Tester", "@example.com",
``, true},
{
"valid name and addr", "Toni Tester", "tester@example.com",
`"Toni Tester" <tester@example.com>`, false,
},
{
"no name with valid addr", "", "tester@example.com",
`<tester@example.com>`, false,
},
{
"valid name with invalid addr", "Toni Tester", "@example.com",
``, true,
},
}
m := NewMsg()
for _, tt := range tests {
@ -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 {
@ -868,7 +953,6 @@ func TestMsg_SetImportance(t *testing.T) {
m.genHeader = make(map[Header][]string)
})
}
}
// TestMsg_SetOrganization tests the Msg.SetOrganization method
@ -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()
@ -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},
}
@ -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},
}
@ -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},
}
@ -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},
}
@ -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},
}
@ -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},
}