From b35b25c11c362ae8a8749cb4e77a85e70ac95330 Mon Sep 17 00:00:00 2001 From: MrMelon54 Date: Mon, 11 Sep 2023 01:33:08 +0100 Subject: [PATCH] Start writing tests --- api/interfaces.go | 4 +- api/send-message.go | 4 +- api/send-message_test.go | 54 +++++++++++++------- cmd/lotus/conf.go | 10 ++-- {smtp => sendmail}/fake/fake-smtp.go | 0 {smtp => sendmail}/json.go | 2 +- smtp/smtp.go => sendmail/sendmail.go | 14 +++--- sendmail/sendmail_test.go | 67 +++++++++++++++++++++++++ smtp/smtp_test.go | 74 ---------------------------- 9 files changed, 120 insertions(+), 109 deletions(-) rename {smtp => sendmail}/fake/fake-smtp.go (100%) rename {smtp => sendmail}/json.go (99%) rename smtp/smtp.go => sendmail/sendmail.go (62%) create mode 100644 sendmail/sendmail_test.go delete mode 100644 smtp/smtp_test.go diff --git a/api/interfaces.go b/api/interfaces.go index 2ec9239..6cb26f6 100644 --- a/api/interfaces.go +++ b/api/interfaces.go @@ -2,11 +2,11 @@ package api import ( "github.com/1f349/lotus/imap" - "github.com/1f349/lotus/smtp" + "github.com/1f349/lotus/sendmail" ) type Smtp interface { - Send(mail *smtp.Mail) error + Send(mail *sendmail.Mail) error } type Imap interface { diff --git a/api/send-message.go b/api/send-message.go index 87e4f8a..f5ace66 100644 --- a/api/send-message.go +++ b/api/send-message.go @@ -5,7 +5,7 @@ import ( "errors" "fmt" postfixLookup "github.com/1f349/lotus/postfix-lookup" - "github.com/1f349/lotus/smtp" + "github.com/1f349/lotus/sendmail" "github.com/julienschmidt/httprouter" "log" "net/http" @@ -26,7 +26,7 @@ func MessageSender(send Smtp) func(rw http.ResponseWriter, req *http.Request, pa } // parse json body - var j smtp.Json + var j sendmail.Json err := json.NewDecoder(req.Body).Decode(&j) if err != nil { apiError(rw, http.StatusBadRequest, "Invalid JSON body") diff --git a/api/send-message_test.go b/api/send-message_test.go index 9b19e80..8b6b215 100644 --- a/api/send-message_test.go +++ b/api/send-message_test.go @@ -1,14 +1,16 @@ package api import ( + "bufio" "bytes" "encoding/json" "errors" "fmt" postfixLookup "github.com/1f349/lotus/postfix-lookup" - "github.com/1f349/lotus/smtp" + "github.com/1f349/lotus/sendmail" "github.com/MrMelon54/mjwt/auth" "github.com/MrMelon54/mjwt/claims" + "github.com/emersion/go-message/mail" "github.com/golang-jwt/jwt/v4" "github.com/julienschmidt/httprouter" "github.com/stretchr/testify/assert" @@ -37,16 +39,31 @@ func init() { } type fakeSmtp struct { - from string - deliver []string - body []byte + from *mail.Address + body []byte } -func (f *fakeSmtp) Send(mail *smtp.Mail) error { - if mail.From != f.from { +func (f *fakeSmtp) Send(mail *sendmail.Mail) error { + // remove the Bcc header line + s := bufio.NewScanner(bytes.NewReader(mail.Body)) + b := new(bytes.Buffer) + b.Grow(len(mail.Body)) + for s.Scan() { + a := s.Text() + if strings.HasPrefix(a, "Bcc:") { + continue + } + b.WriteString(a + "\r\n") + } + if err := s.Err(); err != nil { + return err + } + + // check values are the same + if mail.From.String() != f.from.String() { return fmt.Errorf("test fail: invalid from address") } - if !slices.Equal(mail.Body, f.body) { + if !slices.Equal(b.Bytes(), f.body) { return fmt.Errorf("test fail: invalid message body") } return nil @@ -54,7 +71,7 @@ func (f *fakeSmtp) Send(mail *smtp.Mail) error { type fakeFailedSmtp struct{} -func (f *fakeFailedSmtp) Send(mail *smtp.Mail) error { +func (f *fakeFailedSmtp) Send(mail *sendmail.Mail) error { return errors.New("sending failed") } @@ -85,7 +102,7 @@ var messageSenderTestData = []struct { }, { req: func() (*http.Request, error) { - j, err := json.Marshal(smtp.Json{ + j, err := json.Marshal(sendmail.Json{ From: "noreply2@example.com", ReplyTo: "admin@example.com", To: "user@example.com", @@ -105,7 +122,7 @@ var messageSenderTestData = []struct { }, { req: func() (*http.Request, error) { - j, err := json.Marshal(smtp.Json{ + j, err := json.Marshal(sendmail.Json{ From: "user@example.com", ReplyTo: "admin@example.com", To: "user@example.com", @@ -125,7 +142,7 @@ var messageSenderTestData = []struct { }, { req: func() (*http.Request, error) { - j, err := json.Marshal(smtp.Json{ + j, err := json.Marshal(sendmail.Json{ From: "noreply@example.com, user2@example.com", ReplyTo: "admin@example.com", To: "user@example.com", @@ -145,7 +162,7 @@ var messageSenderTestData = []struct { }, { req: func() (*http.Request, error) { - j, err := json.Marshal(smtp.Json{ + j, err := json.Marshal(sendmail.Json{ From: "noreply@example.com", ReplyTo: "admin@example.com", To: "user@example.com", @@ -165,7 +182,7 @@ var messageSenderTestData = []struct { }, { req: func() (*http.Request, error) { - j, err := json.Marshal(smtp.Json{ + j, err := json.Marshal(sendmail.Json{ From: "noreply@example.com", ReplyTo: "admin@example.com", To: "a ", @@ -205,7 +222,7 @@ var messageSenderTestData = []struct { }, { req: func() (*http.Request, error) { - j, err := json.Marshal(smtp.Json{ + j, err := json.Marshal(sendmail.Json{ From: "noreply@example.com", ReplyTo: "admin@example.com", To: "user@example.com", @@ -225,7 +242,7 @@ var messageSenderTestData = []struct { }, { req: func() (*http.Request, error) { - j, err := json.Marshal(smtp.Json{ + j, err := json.Marshal(sendmail.Json{ From: "noreply@example.com", ReplyTo: "admin@example.com", To: "user@example.com", @@ -241,8 +258,7 @@ var messageSenderTestData = []struct { return http.NewRequest(http.MethodPost, "https://api.example.com/v1/mail/message", bytes.NewReader(j)) }, smtp: &fakeSmtp{ - from: "noreply@example.com", - deliver: []string{"user@example.com", "user2@example.com", "user3@example.com"}, + from: &mail.Address{Address: "noreply@example.com"}, body: []byte("Mime-Version: 1.0\r\n" + "Content-Type: text/plain; charset=utf-8\r\n" + "Cc: \r\n" + @@ -252,7 +268,7 @@ var messageSenderTestData = []struct { "Subject: Test Subject\r\n" + "Date: Sat, 01 Jan 2000 00:00:00 +0000\r\n" + "\r\n" + - "Some plain text"), + "Some plain text\r\n"), }, claims: makeFakeAuthClaims("admin@example.com"), status: http.StatusAccepted, diff --git a/cmd/lotus/conf.go b/cmd/lotus/conf.go index dd2daee..58650aa 100644 --- a/cmd/lotus/conf.go +++ b/cmd/lotus/conf.go @@ -2,12 +2,12 @@ package main import ( "github.com/1f349/lotus/imap" - "github.com/1f349/lotus/smtp" + "github.com/1f349/lotus/sendmail" ) type Conf struct { - Listen string `yaml:"listen"` - Audience string `yaml:"audience"` - Smtp *smtp.Smtp `yaml:"smtp"` - Imap *imap.Imap `yaml:"imap"` + Listen string `yaml:"listen"` + Audience string `yaml:"audience"` + Smtp *sendmail.Smtp `yaml:"sendmail"` + Imap *imap.Imap `yaml:"imap"` } diff --git a/smtp/fake/fake-smtp.go b/sendmail/fake/fake-smtp.go similarity index 100% rename from smtp/fake/fake-smtp.go rename to sendmail/fake/fake-smtp.go diff --git a/smtp/json.go b/sendmail/json.go similarity index 99% rename from smtp/json.go rename to sendmail/json.go index 4042b11..145d4e5 100644 --- a/smtp/json.go +++ b/sendmail/json.go @@ -1,4 +1,4 @@ -package smtp +package sendmail import ( "bytes" diff --git a/smtp/smtp.go b/sendmail/sendmail.go similarity index 62% rename from smtp/smtp.go rename to sendmail/sendmail.go index 9e2b3b9..35c26de 100644 --- a/smtp/smtp.go +++ b/sendmail/sendmail.go @@ -1,4 +1,4 @@ -package smtp +package sendmail import ( "github.com/emersion/go-message/mail" @@ -6,7 +6,7 @@ import ( ) type Smtp struct { - Server string `yaml:"server"` + SendMailCommand string `json:"send_mail_command"` } type Mail struct { @@ -14,13 +14,14 @@ type Mail struct { Body []byte } -var execSendMail = func(from string) *exec.Cmd { - return exec.Command("/usr/lib/sendmail", "-f", from, "-t") -} +var execCommand = exec.Command func (s *Smtp) Send(mail *Mail) error { // start sendmail caller - sendMail := execSendMail(mail.From.String()) + if s.SendMailCommand == "" { + s.SendMailCommand = "/usr/sbin/sendmail" + } + sendMail := execCommand(s.SendMailCommand, "-f", mail.From.Address, "-t") inPipe, err := sendMail.StdinPipe() if err != nil { return err @@ -36,5 +37,6 @@ func (s *Smtp) Send(mail *Mail) error { return err } + // run command return sendMail.Run() } diff --git a/sendmail/sendmail_test.go b/sendmail/sendmail_test.go new file mode 100644 index 0000000..81421da --- /dev/null +++ b/sendmail/sendmail_test.go @@ -0,0 +1,67 @@ +package sendmail + +import ( + "bytes" + "github.com/emersion/go-message" + "github.com/emersion/go-message/mail" + "github.com/stretchr/testify/assert" + "log" + "net" + "os" + "os/exec" + "strings" + "testing" + "time" +) + +var sendTestMessage []byte + +func init() { + var h mail.Header + h.SetDate(time.Date(2000, time.January, 1, 0, 0, 0, 0, time.Local)) + h.SetSubject("Happy Millennium") + h.SetAddressList("From", []*mail.Address{{Name: "Test", Address: "test@localhost"}}) + h.SetAddressList("To", []*mail.Address{{Name: "A", Address: "a@localhost"}}) + h.Set("Content-Type", "text/plain; charset=utf-8") + entity, err := message.New(h.Header, strings.NewReader("Thanks")) + if err != nil { + log.Fatal(err) + } + out := new(bytes.Buffer) + if entity.WriteTo(out) != nil { + log.Fatal(err) + } + sendTestMessage = out.Bytes() +} + +func TestSmtp_Send(t *testing.T) { + execCommand = func(name string, arg ...string) *exec.Cmd { + cs := append([]string{"-test.run=TestHelperProcess", "--", name}, arg...) + cmd := exec.Command(os.Args[0], cs...) + cmd.Env = []string{"GO_WANT_HELPER_PROCESS=1"} + return cmd + } + defer func() { execCommand = exec.Command }() + + m := &Mail{From: &mail.Address{Address: "test@localhost"}, Body: sendTestMessage} + + temp, err := os.CreateTemp("", "sendmail") + if err != nil { + return + } + + addr, err := net.ResolveUnixAddr("") + assert.NoError(t, err) + listen, err := net.ListenUnix("", addr) + assert.NoError(t, err) + + s := &Smtp{SendMailCommand: "/tmp/sendmailXXXXX"} + assert.NoError(t, s.Send(m)) +} + +func TestSmtpHelperProcess(t *testing.T) { + if os.Getenv("GO_WANT_HELPER_PROCESS") != "1" { + return + } + +} diff --git a/smtp/smtp_test.go b/smtp/smtp_test.go deleted file mode 100644 index ce74fb3..0000000 --- a/smtp/smtp_test.go +++ /dev/null @@ -1,74 +0,0 @@ -package smtp - -import ( - "bytes" - "github.com/1f349/lotus/smtp/fake" - "github.com/emersion/go-message" - "github.com/emersion/go-message/mail" - "github.com/emersion/go-smtp" - "github.com/hydrogen18/memlistener" - "github.com/stretchr/testify/assert" - "log" - "strings" - "testing" - "time" -) - -var sendTestMessage []byte - -func init() { - var h mail.Header - h.SetDate(time.Date(2000, time.January, 1, 0, 0, 0, 0, time.Local)) - h.SetSubject("Happy Millennium") - h.SetAddressList("From", []*mail.Address{{Name: "Test", Address: "test@localhost"}}) - h.SetAddressList("To", []*mail.Address{{Name: "A", Address: "a@localhost"}}) - h.Set("Content-Type", "text/plain; charset=utf-8") - entity, err := message.New(h.Header, strings.NewReader("Thanks")) - if err != nil { - log.Fatal(err) - } - out := new(bytes.Buffer) - if entity.WriteTo(out) != nil { - log.Fatal(err) - } - sendTestMessage = out.Bytes() -} - -func TestSmtp_Send(t *testing.T) { - listener := memlistener.NewMemoryListener() - serverData := make(chan []byte, 4) - server := smtp.NewServer(&fake.SmtpBackend{Debug: serverData}) - go func() { - _ = server.Serve(listener) - }() - - defaultDialer = func(addr string) (*smtp.Client, error) { - dial, err := listener.Dial("", "") - if err != nil { - return nil, err - } - return smtp.NewClient(dial, "localhost") - } - - s := &Smtp{Server: "localhost:25"} - err := s.Send(&Mail{From: "test@localhost", Deliver: []string{"a@localhost", "b@localhost"}, Body: sendTestMessage}) - assert.NoError(t, err) - assert.Equal(t, []byte("MAIL test@localhost\n"), <-serverData) - assert.Equal(t, []byte("RCPT a@localhost\n"), <-serverData) - assert.Equal(t, []byte("RCPT b@localhost\n"), <-serverData) - assert.Equal(t, append(sendTestMessage, '\r', '\n'), <-serverData) -} - -func TestCreateSenderSlice(t *testing.T) { - a := []*mail.Address{{Address: "a@example.com"}, {Address: "b@example.com"}} - b := []*mail.Address{{Address: "a@example.com"}, {Address: "c@example.com"}} - c := []*mail.Address{{Address: "a@example.com"}, {Address: "d@example.com"}} - assert.Equal(t, []string{ - "a@example.com", - "b@example.com", - "a@example.com", - "c@example.com", - "a@example.com", - "d@example.com", - }, CreateSenderSlice(a, b, c)) -}