2017-12-09 21:42:07 +00:00
|
|
|
package mailer
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"errors"
|
|
|
|
"io"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/gophish/gomail"
|
|
|
|
)
|
|
|
|
|
|
|
|
// errHostUnreachable is a mock error to represent a host
|
|
|
|
// being unreachable
|
|
|
|
var errHostUnreachable = errors.New("host unreachable")
|
|
|
|
|
|
|
|
// errDialerUnavailable is a mock error to represent a dialer
|
|
|
|
// being unavailable (perhaps an error getting the dialer config
|
|
|
|
// or a database error)
|
|
|
|
var errDialerUnavailable = errors.New("dialer unavailable")
|
|
|
|
|
|
|
|
// mockDialer keeps track of calls to Dial
|
|
|
|
type mockDialer struct {
|
|
|
|
dialCount int
|
|
|
|
dial func() (Sender, error)
|
|
|
|
}
|
|
|
|
|
|
|
|
// newMockDialer returns a new instance of the mockDialer with the default
|
|
|
|
// dialer set.
|
|
|
|
func newMockDialer() *mockDialer {
|
|
|
|
md := &mockDialer{}
|
|
|
|
md.dial = md.defaultDial
|
|
|
|
return md
|
|
|
|
}
|
|
|
|
|
|
|
|
// defaultDial simply returns a mockSender
|
|
|
|
func (md *mockDialer) defaultDial() (Sender, error) {
|
|
|
|
return newMockSender(), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// unreachableDial is to simulate network error conditions in which
|
|
|
|
// a host is unavailable.
|
|
|
|
func (md *mockDialer) unreachableDial() (Sender, error) {
|
|
|
|
return nil, errHostUnreachable
|
|
|
|
}
|
|
|
|
|
|
|
|
// Dial increments the internal dial count. Otherwise, it's a no-op for the mock client.
|
|
|
|
func (md *mockDialer) Dial() (Sender, error) {
|
|
|
|
md.dialCount++
|
|
|
|
return md.dial()
|
|
|
|
}
|
|
|
|
|
|
|
|
// setDial sets the Dial function for the mockDialer
|
|
|
|
func (md *mockDialer) setDial(dial func() (Sender, error)) {
|
|
|
|
md.dial = dial
|
|
|
|
}
|
|
|
|
|
|
|
|
// mockSender is a mock gomail.Sender used for testing.
|
|
|
|
type mockSender struct {
|
|
|
|
messages []*mockMessage
|
|
|
|
status string
|
|
|
|
send func(*mockMessage) error
|
|
|
|
messageChan chan *mockMessage
|
|
|
|
resetCount int
|
|
|
|
}
|
|
|
|
|
|
|
|
func newMockSender() *mockSender {
|
|
|
|
ms := &mockSender{
|
|
|
|
status: "ehlo",
|
|
|
|
messageChan: make(chan *mockMessage),
|
|
|
|
}
|
|
|
|
ms.send = ms.defaultSend
|
|
|
|
return ms
|
|
|
|
}
|
|
|
|
|
|
|
|
func (ms *mockSender) setSend(send func(*mockMessage) error) {
|
|
|
|
ms.send = send
|
|
|
|
}
|
|
|
|
|
|
|
|
func (ms *mockSender) defaultSend(mm *mockMessage) error {
|
|
|
|
ms.messageChan <- mm
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Send just appends the provided message record to the internal slice
|
|
|
|
func (ms *mockSender) Send(from string, to []string, msg io.WriterTo) error {
|
|
|
|
mm := newMockMessage(from, to, msg)
|
|
|
|
ms.messages = append(ms.messages, mm)
|
|
|
|
ms.status = "sent"
|
|
|
|
return ms.send(mm)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Close is a noop for the mock client
|
|
|
|
func (ms *mockSender) Close() error {
|
|
|
|
ms.status = "closed"
|
|
|
|
close(ms.messageChan)
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Reset sets the status to "Reset". In practice, this would reset the connection
|
|
|
|
// to the same state as if the client had just sent an EHLO command.
|
|
|
|
func (ms *mockSender) Reset() error {
|
|
|
|
ms.status = "reset"
|
|
|
|
ms.resetCount++
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// mockMessage holds the information sent via a call to MockClient.Send()
|
|
|
|
type mockMessage struct {
|
|
|
|
from string
|
|
|
|
to []string
|
|
|
|
message []byte
|
|
|
|
sendAt time.Time
|
|
|
|
backoffCount int
|
|
|
|
getdialer func() (Dialer, error)
|
|
|
|
err error
|
|
|
|
finished bool
|
|
|
|
}
|
|
|
|
|
|
|
|
func newMockMessage(from string, to []string, msg io.WriterTo) *mockMessage {
|
|
|
|
buff := &bytes.Buffer{}
|
|
|
|
msg.WriteTo(buff)
|
|
|
|
mm := &mockMessage{
|
|
|
|
from: from,
|
|
|
|
to: to,
|
|
|
|
message: buff.Bytes(),
|
|
|
|
sendAt: time.Now(),
|
|
|
|
}
|
|
|
|
mm.getdialer = mm.defaultDialer
|
|
|
|
return mm
|
|
|
|
}
|
|
|
|
|
|
|
|
func (mm *mockMessage) setDialer(dialer func() (Dialer, error)) {
|
|
|
|
mm.getdialer = dialer
|
|
|
|
}
|
|
|
|
|
|
|
|
func (mm *mockMessage) defaultDialer() (Dialer, error) {
|
|
|
|
return newMockDialer(), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (mm *mockMessage) errorDialer() (Dialer, error) {
|
|
|
|
return nil, errDialerUnavailable
|
|
|
|
}
|
|
|
|
|
|
|
|
func (mm *mockMessage) GetDialer() (Dialer, error) {
|
|
|
|
return mm.getdialer()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (mm *mockMessage) Backoff(reason error) error {
|
|
|
|
mm.backoffCount++
|
2018-04-21 01:33:00 +00:00
|
|
|
mm.err = reason
|
2017-12-09 21:42:07 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (mm *mockMessage) Error(err error) error {
|
|
|
|
mm.err = err
|
|
|
|
mm.finished = true
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (mm *mockMessage) Finish() error {
|
|
|
|
mm.finished = true
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (mm *mockMessage) Generate(message *gomail.Message) error {
|
|
|
|
message.SetHeaders(map[string][]string{
|
|
|
|
"From": {mm.from},
|
|
|
|
"To": mm.to,
|
|
|
|
})
|
|
|
|
message.SetBody("text/html", string(mm.message))
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (mm *mockMessage) Success() error {
|
|
|
|
mm.finished = true
|
|
|
|
return nil
|
|
|
|
}
|