package transport import ( "errors" "fmt" "log" "sync" "time" "allaboutapps.dev/aw/go-starter/internal/util" "github.com/jordan-wright/email" ) const defaultWaitTimeout = time.Second * 10 type MockMailTransport struct { sync.RWMutex mails []*email.Email OnMailSent func(mail email.Email) // non pointer to prevent concurrent read errors wg sync.WaitGroup expected int } func NewMock() *MockMailTransport { return &MockMailTransport{ RWMutex: sync.RWMutex{}, mails: make([]*email.Email, 0), OnMailSent: func(_ email.Email) {}, } } func (m *MockMailTransport) Send(mail *email.Email) error { m.Lock() defer m.Unlock() // Calling wg.Done might panic leaving a user clueless what was the reason of test failure. // We will add more information before exiting. defer func() { rcp := recover() if rcp == nil { return } err, ok := rcp.(error) if !ok { err = fmt.Errorf("%v", rcp) } log.Fatalf("Unexpected email sent! MockMailTransport panicked: %s", err) }() m.mails = append(m.mails, mail) m.OnMailSent(*mail) if m.expected > 0 { m.wg.Done() } return nil } func (m *MockMailTransport) GetLastSentMail() *email.Email { m.RLock() defer m.RUnlock() if len(m.mails) == 0 { return nil } return m.mails[len(m.mails)-1] } func (m *MockMailTransport) GetSentMails() []*email.Email { m.RLock() defer m.RUnlock() return m.mails } // Expect adds the mailCnt to a waitgroup. Done() is called by Send func (m *MockMailTransport) Expect(mailCnt int) { m.expected = mailCnt m.wg.Add(mailCnt) } // Wait until all expected mails have arrived func (m *MockMailTransport) Wait() { m.WaitWithTimeout(defaultWaitTimeout) } func (m *MockMailTransport) WaitWithTimeout(timeout time.Duration) { if err := util.WaitTimeout(&m.wg, timeout); errors.Is(err, util.ErrWaitTimeout) { log.Fatalf("Timeout waiting for %d mails to be sent", m.expected) } }