Skip to content

Commit

Permalink
update harness and add tests to validate mail server functionality
Browse files Browse the repository at this point in the history
  • Loading branch information
mastercactapus committed Oct 4, 2024
1 parent c9f3447 commit f15b06f
Show file tree
Hide file tree
Showing 4 changed files with 308 additions and 132 deletions.
188 changes: 56 additions & 132 deletions test/smoke/harness/email.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,8 @@ import (
"strings"
"time"

"github.com/mailhog/MailHog-Server/smtp"
"github.com/mailhog/data"
"github.com/mailhog/storage"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

type EmailServer interface {
Expand All @@ -19,163 +18,88 @@ type EmailServer interface {
type emailServer struct {
h *Harness

store *storage.InMemory
l net.Listener

expected []emailExpect
}

type emailExpect struct {
address string
keywords []string
mp *mailpit
}

func newEmailServer(h *Harness) *emailServer {
store := storage.CreateInMemory()
msgChan := make(chan *data.Message)
go func() {
// drain channel, TODO: check for leak
for range msgChan {
func findOpenPorts(num int) ([]string, error) {
var listeners []net.Listener
for range num {
ln, err := net.Listen("tcp", "localhost:0")
if err != nil {
for _, l := range listeners {
l.Close()
}
return nil, err
}
}()
listeners = append(listeners, ln)
}

var addrs []string
for _, l := range listeners {
addrs = append(addrs, l.Addr().String())
l.Close()
}

return addrs, nil
}

ln, err := net.Listen("tcp", "localhost:0")
func isListening(addr string) bool {
c, err := net.Dial("tcp", addr)
if err != nil {
panic(err)
return false
}
c.Close()
return true
}

go func() {
for {
conn, err := ln.Accept()
if err != nil {
return
}
func newEmailServer(h *Harness) *emailServer {
mp, err := newMailpit(5)
require.NoError(h.t, err)

go smtp.Accept(
conn.(*net.TCPConn).RemoteAddr().String(),
conn,
store,
msgChan,
"goalet-test.local",
nil,
)
}
}()
h.t.Logf("mailpit: smtp: %s", mp.smtpAddr)
h.t.Logf("mailpit: api: %s", mp.apiAddr)

return &emailServer{
h: h,
store: store,
l: ln,
h: h,
mp: mp,
}
}
func (e *emailServer) Close() error { return e.l.Close() }
func (e *emailServer) Addr() string { return e.l.Addr().String() }
func (e *emailServer) Close() error { return e.mp.Close() }
func (e *emailServer) Addr() string { return e.mp.smtpAddr }

func (h *Harness) Email(id string) string { return h.emailG.Get(id) }

func (h *Harness) SMTP() EmailServer { return h.email }

func (e *emailServer) ExpectMessage(address string, keywords ...string) {
e.expected = append(e.expected, emailExpect{address: address, keywords: keywords})
e.h.t.Helper()

gotMessage := assert.Eventuallyf(e.h.t, func() bool {
found, err := e.mp.ReadMessage(address, keywords...)
require.NoError(e.h.t, err)
return found
}, 15*time.Second, 10*time.Millisecond, "expected to find email: address=%s; keywords=%v", address, keywords)
if gotMessage {
return
}

msgs, err := e.mp.UnreadMessages()
assert.NoError(e.h.t, err)
e.h.t.Fatalf("timeout waiting for email; Got:\n%v", msgs)
}

type emailMessage struct {
address []string
body string
}

func containsStr(s []string, search string) bool {
for _, str := range s {
if strings.Contains(str, search) {
return true
}
}
return false
}
func (e *emailServer) messages() []emailMessage {
_msgs, err := e.store.List(0, 1000)
if err != nil {
panic(err)
}
msgs := []data.Message(*_msgs)

var result []emailMessage
for _, msg := range msgs {
var addrs []string
for _, p := range msg.To {
addrs = append(addrs, p.Mailbox+"@"+p.Domain)
}

for _, part := range msg.MIME.Parts {
if !containsStr(part.Headers["Content-Type"], "text/plain") {
continue
}
result = append(result, emailMessage{
body: part.Body,
address: addrs,
})
}
}

return result
}

func (e *emailServer) waitAndAssert(timeout <-chan time.Time) bool {
msgs := e.messages()

check := func(address string, keywords []string) bool {

msgLoop:
for i, msg := range msgs {
var destMatch bool
for _, addr := range msg.address {
if addr == address {
destMatch = true
break
}
}
if !destMatch {
break
}
for _, w := range keywords {
if !strings.Contains(msg.body, w) {
continue msgLoop
}
}
msgs = append(msgs[:i], msgs[i+1:]...)
return true
}
return false
}
func (e *emailServer) WaitAndAssert() {
e.h.t.Helper()

for i, exp := range e.expected {
select {
case <-timeout:
e.h.t.Fatalf("timeout waiting for email: address=%s; message=%d keywords=%v\nGot: %s", exp.address, i, exp.keywords, msgs)
default:
}
if !check(exp.address, exp.keywords) {
return false
}
}
msgs, err := e.mp.UnreadMessages()
require.NoError(e.h.t, err)

for _, msg := range msgs {
e.h.t.Errorf("unexpected message: to=%s; body=%s", strings.Join(msg.address, ","), msg.body)
}

return true
}

func (e *emailServer) WaitAndAssert() {
timeout := time.NewTimer(15 * time.Second)
defer timeout.Stop()

t := time.NewTicker(time.Millisecond)
defer t.Stop()

for !e.waitAndAssert(timeout.C) {
<-t.C
}

e.expected = nil
}
29 changes: 29 additions & 0 deletions test/smoke/harness/email_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package harness

import (
"net"
"strings"
"testing"

"github.com/stretchr/testify/require"
)

func TestFindOpenPorts(t *testing.T) {
// This test is more of a sanity check than anything else.
//
// Finding an open port is dependent on the system's network state, so it's
// difficult to write a deterministic test for it. This test is just to
// ensure that the function doesn't panic and returns a valid port.
ports, err := findOpenPorts(1)
if err != nil {
t.Fatal(err)
}
if !strings.Contains(ports[0], ":") {
t.Fatalf("expected port to contain colon, got %s", ports[0])
}

// Ensure the port is actually open
l, err := net.Listen("tcp", ports[0])
require.NoError(t, err)
defer l.Close()
}
Loading

0 comments on commit f15b06f

Please sign in to comment.