gophish/mailer/mockmailer.go

177 lines
3.9 KiB
Go

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