Skip to content

Commit cfcb211

Browse files
joeybloggsjoeybloggs
authored andcommitted
complete 100% test coverage for email handler
1 parent b7ddd44 commit cfcb211

File tree

3 files changed

+267
-12
lines changed

3 files changed

+267
-12
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
## log
22
<img align="right" src="https://raw.githubusercontent.com/go-playground/log/master/logo.png">
3-
![Project status](https://img.shields.io/badge/version-2.2-green.svg)
3+
![Project status](https://img.shields.io/badge/version-2.3-green.svg)
44
[![Build Status](https://semaphoreci.com/api/v1/joeybloggs/log/branches/master/badge.svg)](https://semaphoreci.com/joeybloggs/log)
55
[![Coverage Status](https://coveralls.io/repos/github/go-playground/log/badge.svg?branch=master)](https://coveralls.io/github/go-playground/log?branch=master)
66
[![Go Report Card](https://goreportcard.com/badge/github.com/go-playground/log)](https://goreportcard.com/report/github.com/go-playground/log)

handlers/email/email.go

Lines changed: 26 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package email
22

33
import (
44
"bytes"
5+
"fmt"
56
"html/template"
67
stdlog "log"
78
"os"
@@ -15,7 +16,7 @@ import (
1516
// FormatFunc is the function that the workers use to create
1617
// a new Formatter per worker allowing reusable go routine safe
1718
// variable to be used within your Formatter function.
18-
type FormatFunc func() Formatter
19+
type FormatFunc func(email *Email) Formatter
1920

2021
// Formatter is the function used to format the Email entry
2122
type Formatter func(e *log.Entry) *gomail.Message
@@ -64,7 +65,7 @@ type Email struct {
6465
// New returns a new instance of the email logger
6566
func New(host string, port int, username string, password string, from string, to []string) *Email {
6667

67-
e := &Email{
68+
return &Email{
6869
buffer: 0,
6970
numWorkers: 1,
7071
timestampFormat: log.DefaultTimeFormat,
@@ -77,11 +78,8 @@ func New(host string, port int, username string, password string, from string, t
7778
from: from,
7879
to: to,
7980
keepalive: time.Second * 30,
81+
formatFunc: defaultFormatFunc,
8082
}
81-
82-
e.formatFunc = e.defaultFormatFunc
83-
84-
return e
8583
}
8684

8785
// SetKeepAliveTimout tells Email how long to keep the smtp connection
@@ -117,6 +115,16 @@ func (email *Email) SetBuffersAndWorkers(size uint, workers uint) {
117115
email.numWorkers = workers
118116
}
119117

118+
// From returns the Email's From address
119+
func (email *Email) From() string {
120+
return email.from
121+
}
122+
123+
// To returns the Email's To address
124+
func (email *Email) To() []string {
125+
return email.to
126+
}
127+
120128
// SetTimestampFormat sets Email's timestamp output format
121129
// Default is : "2006-01-02T15:04:05.000000000Z07:00"
122130
func (email *Email) SetTimestampFormat(format string) {
@@ -146,8 +154,9 @@ func (email *Email) Run() chan<- *log.Entry {
146154
email.template = template.Must(template.New("email").Funcs(
147155
template.FuncMap{
148156
"display_file": func(e *log.Entry) (file string) {
149-
file = e.File
150157

158+
file = e.File
159+
fmt.Println("HERE")
151160
if email.fileDisplay == log.Lshortfile {
152161

153162
for i := len(file) - 1; i > 0; i-- {
@@ -159,6 +168,7 @@ func (email *Email) Run() chan<- *log.Entry {
159168
} else {
160169
file = file[len(email.gopath):]
161170
}
171+
162172
return
163173
},
164174
"ts": func(e *log.Entry) (ts string) {
@@ -176,7 +186,7 @@ func (email *Email) Run() chan<- *log.Entry {
176186
return ch
177187
}
178188

179-
func (email *Email) defaultFormatFunc() Formatter {
189+
func defaultFormatFunc(email *Email) Formatter {
180190
var err error
181191
b := new(bytes.Buffer)
182192
message := gomail.NewMessage()
@@ -201,17 +211,19 @@ func (email *Email) handleLog(entries <-chan *log.Entry) {
201211
var s gomail.SendCloser
202212
var err error
203213
var open bool
214+
var alreadyTriedSending bool
204215
var message *gomail.Message
205216
var count uint8
206217

207-
formatter := email.formatFunc()
218+
formatter := email.formatFunc(email)
208219

209220
d := gomail.NewDialer(email.host, email.port, email.username, email.password)
210221

211222
for {
212223
select {
213224
case e = <-entries:
214225
count = 0
226+
alreadyTriedSending = false
215227
message = formatter(e)
216228

217229
REOPEN:
@@ -236,14 +248,16 @@ func (email *Email) handleLog(entries <-chan *log.Entry) {
236248
RESEND:
237249
count++
238250
if err = gomail.Send(s, message); err != nil {
251+
239252
log.WithFields(log.F("error", err)).Warn("ERROR sending to smtp server, retrying")
240253

241-
if count == 3 {
254+
if count == 3 && !alreadyTriedSending {
242255
// maybe we got disconnected...
256+
alreadyTriedSending = true
243257
open = false
244258
s.Close()
245259
goto REOPEN
246-
} else if count > 3 {
260+
} else if alreadyTriedSending {
247261
// we reopened and tried 2 more times, can;t say we didn't try
248262
log.WithFields(log.F("error", err)).Alert("ERROR sending log via EMAIL, RETRY and REOPEN failed")
249263
e.Consumed()
@@ -252,6 +266,7 @@ func (email *Email) handleLog(entries <-chan *log.Entry) {
252266

253267
goto RESEND
254268
}
269+
255270
e.Consumed()
256271

257272
case <-time.After(email.keepalive):

handlers/email/email_test.go

Lines changed: 240 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,240 @@
1+
package email
2+
3+
import (
4+
"bufio"
5+
"bytes"
6+
"fmt"
7+
"net"
8+
"strings"
9+
"testing"
10+
"time"
11+
12+
"gopkg.in/gomail.v2"
13+
14+
"github.com/go-playground/log"
15+
)
16+
17+
// NOTES:
18+
// - Run "go test" to run tests
19+
// - Run "gocov test | gocov report" to report on test converage by file
20+
// - Run "gocov test | gocov annotate -" to report on all code and functions, those ,marked with "MISS" were never called
21+
22+
// or
23+
24+
// -- may be a good idea to change to output path to somewherelike /tmp
25+
// go test -coverprofile cover.out && go tool cover -html=cover.out -o cover.html
26+
27+
type Client struct {
28+
conn net.Conn
29+
address string
30+
time int64
31+
bufin *bufio.Reader
32+
bufout *bufio.Writer
33+
}
34+
35+
func (c *Client) w(s string) {
36+
c.bufout.WriteString(s + "\r\n")
37+
c.bufout.Flush()
38+
}
39+
func (c *Client) r() string {
40+
reply, err := c.bufin.ReadString('\n')
41+
if err != nil {
42+
fmt.Println("e ", err)
43+
}
44+
return reply
45+
}
46+
47+
func handleClient(c *Client, closePrematurly bool) string {
48+
49+
var msg []byte
50+
51+
c.w("220 Welcome to the Jungle")
52+
msg = append(msg, c.r()...)
53+
54+
c.w("250 No one says helo anymore")
55+
msg = append(msg, c.r()...)
56+
57+
c.w("250 Sender")
58+
msg = append(msg, c.r()...)
59+
60+
c.w("250 Recipient")
61+
msg = append(msg, c.r()...)
62+
63+
c.w("354 Ok Send data ending with <CRLF>.<CRLF>")
64+
for {
65+
text := c.r()
66+
bytes := []byte(text)
67+
msg = append(msg, bytes...)
68+
69+
// 46 13 10
70+
if bytes[0] == 46 && bytes[1] == 13 && bytes[2] == 10 {
71+
break
72+
}
73+
}
74+
75+
if !closePrematurly {
76+
c.w("250 server has transmitted the message")
77+
}
78+
79+
c.conn.Close()
80+
81+
return string(msg)
82+
}
83+
84+
func TestEmailHandler(t *testing.T) {
85+
86+
tests := []struct {
87+
expected string
88+
}{
89+
{
90+
expected: "[email protected]",
91+
},
92+
{
93+
expected: "[email protected]",
94+
},
95+
{
96+
expected: "Subject: debug",
97+
},
98+
{
99+
expected: "DEBUG",
100+
},
101+
}
102+
103+
email := New("localhost", 3041, "", "", "[email protected]", []string{"[email protected]"})
104+
email.SetBuffersAndWorkers(1, 0)
105+
email.SetTimestampFormat("MST")
106+
email.SetKeepAliveTimout(time.Second * 0)
107+
email.SetEmailTemplate(defaultTemplate)
108+
email.SetFilenameDisplay(log.Llongfile)
109+
// email.SetFormatFunc(testFormatFunc)
110+
log.RegisterHandler(email, log.InfoLevel, log.DebugLevel)
111+
112+
var msg string
113+
114+
server, err := net.Listen("tcp", ":3041")
115+
if err != nil {
116+
t.Errorf("Expected <nil> Got '%s'", err)
117+
}
118+
119+
defer server.Close()
120+
121+
go func() {
122+
for {
123+
conn, err := server.Accept()
124+
if err != nil {
125+
t.Errorf("Expected <nil> Got '%s'", err)
126+
}
127+
128+
if conn == nil {
129+
continue
130+
}
131+
132+
c := &Client{
133+
conn: conn,
134+
address: conn.RemoteAddr().String(),
135+
time: time.Now().Unix(),
136+
bufin: bufio.NewReader(conn),
137+
bufout: bufio.NewWriter(conn),
138+
}
139+
140+
msg = handleClient(c, false)
141+
}
142+
}()
143+
144+
log.Debug("debug")
145+
146+
for i, tt := range tests {
147+
if !strings.Contains(msg, tt.expected) {
148+
t.Errorf("Index %d Expected '%s' Got '%s'", i, tt.expected, msg)
149+
}
150+
}
151+
152+
// this is normally not safe, but in these tests won't cause any issue
153+
// flipping during runtime.
154+
email.SetFilenameDisplay(log.Lshortfile)
155+
156+
log.Debug("debug")
157+
158+
for i, tt := range tests {
159+
if !strings.Contains(msg, tt.expected) {
160+
t.Errorf("Index %d Expected '%s' Got '%s'", i, tt.expected, msg)
161+
}
162+
}
163+
}
164+
165+
func TestBadDial(t *testing.T) {
166+
email := New("localhost", 3041, "", "", "[email protected]", []string{"[email protected]"})
167+
email.SetFormatFunc(testFormatFunc)
168+
log.RegisterHandler(email, log.InfoLevel)
169+
170+
log.Info("info test")
171+
}
172+
173+
func TestBadEmailTemplate(t *testing.T) {
174+
badTemplate := `{{ .NonExistantField }}` // referencing non-existant field
175+
email := New("localhost", 3041, "", "", "[email protected]", []string{"[email protected]"})
176+
email.SetEmailTemplate(badTemplate)
177+
log.RegisterHandler(email, log.InfoLevel)
178+
179+
log.Info("info test")
180+
}
181+
182+
func TestBadSend(t *testing.T) {
183+
184+
email := New("localhost", 3041, "", "", "[email protected]", []string{"[email protected]"})
185+
log.RegisterHandler(email, log.InfoLevel)
186+
187+
var msg string
188+
189+
server, err := net.Listen("tcp", ":3041")
190+
if err != nil {
191+
t.Errorf("Expected <nil> Got '%s'", err)
192+
}
193+
194+
defer server.Close()
195+
196+
go func() {
197+
for {
198+
conn, err := server.Accept()
199+
if err != nil {
200+
t.Errorf("Expected <nil> Got '%s'", err)
201+
}
202+
203+
if conn == nil {
204+
continue
205+
}
206+
207+
c := &Client{
208+
conn: conn,
209+
address: conn.RemoteAddr().String(),
210+
time: time.Now().Unix(),
211+
bufin: bufio.NewReader(conn),
212+
bufout: bufio.NewWriter(conn),
213+
}
214+
215+
msg = handleClient(c, true)
216+
}
217+
}()
218+
219+
log.Info("info")
220+
}
221+
222+
func testFormatFunc(email *Email) Formatter {
223+
var err error
224+
b := new(bytes.Buffer)
225+
message := gomail.NewMessage()
226+
message.SetHeader("From", email.From())
227+
message.SetHeader("To", email.To()...)
228+
229+
return func(e *log.Entry) *gomail.Message {
230+
b.Reset()
231+
if err = email.template.ExecuteTemplate(b, "email", e); err != nil {
232+
log.WithFields(log.F("error", err)).Error("Error parsing Email handler template")
233+
}
234+
235+
message.SetHeader("Subject", e.Message)
236+
message.SetBody(contentType, b.String())
237+
238+
return message
239+
}
240+
}

0 commit comments

Comments
 (0)