Start writing tests

This commit is contained in:
Melon 2023-09-11 01:33:08 +01:00
parent 34244834b8
commit b35b25c11c
Signed by: melon
GPG Key ID: 6C9D970C50D26A25
9 changed files with 120 additions and 109 deletions

View File

@ -2,11 +2,11 @@ package api
import ( import (
"github.com/1f349/lotus/imap" "github.com/1f349/lotus/imap"
"github.com/1f349/lotus/smtp" "github.com/1f349/lotus/sendmail"
) )
type Smtp interface { type Smtp interface {
Send(mail *smtp.Mail) error Send(mail *sendmail.Mail) error
} }
type Imap interface { type Imap interface {

View File

@ -5,7 +5,7 @@ import (
"errors" "errors"
"fmt" "fmt"
postfixLookup "github.com/1f349/lotus/postfix-lookup" postfixLookup "github.com/1f349/lotus/postfix-lookup"
"github.com/1f349/lotus/smtp" "github.com/1f349/lotus/sendmail"
"github.com/julienschmidt/httprouter" "github.com/julienschmidt/httprouter"
"log" "log"
"net/http" "net/http"
@ -26,7 +26,7 @@ func MessageSender(send Smtp) func(rw http.ResponseWriter, req *http.Request, pa
} }
// parse json body // parse json body
var j smtp.Json var j sendmail.Json
err := json.NewDecoder(req.Body).Decode(&j) err := json.NewDecoder(req.Body).Decode(&j)
if err != nil { if err != nil {
apiError(rw, http.StatusBadRequest, "Invalid JSON body") apiError(rw, http.StatusBadRequest, "Invalid JSON body")

View File

@ -1,14 +1,16 @@
package api package api
import ( import (
"bufio"
"bytes" "bytes"
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
postfixLookup "github.com/1f349/lotus/postfix-lookup" 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/auth"
"github.com/MrMelon54/mjwt/claims" "github.com/MrMelon54/mjwt/claims"
"github.com/emersion/go-message/mail"
"github.com/golang-jwt/jwt/v4" "github.com/golang-jwt/jwt/v4"
"github.com/julienschmidt/httprouter" "github.com/julienschmidt/httprouter"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@ -37,16 +39,31 @@ func init() {
} }
type fakeSmtp struct { type fakeSmtp struct {
from string from *mail.Address
deliver []string
body []byte body []byte
} }
func (f *fakeSmtp) Send(mail *smtp.Mail) error { func (f *fakeSmtp) Send(mail *sendmail.Mail) error {
if mail.From != f.from { // 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") 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 fmt.Errorf("test fail: invalid message body")
} }
return nil return nil
@ -54,7 +71,7 @@ func (f *fakeSmtp) Send(mail *smtp.Mail) error {
type fakeFailedSmtp struct{} type fakeFailedSmtp struct{}
func (f *fakeFailedSmtp) Send(mail *smtp.Mail) error { func (f *fakeFailedSmtp) Send(mail *sendmail.Mail) error {
return errors.New("sending failed") return errors.New("sending failed")
} }
@ -85,7 +102,7 @@ var messageSenderTestData = []struct {
}, },
{ {
req: func() (*http.Request, error) { req: func() (*http.Request, error) {
j, err := json.Marshal(smtp.Json{ j, err := json.Marshal(sendmail.Json{
From: "noreply2@example.com", From: "noreply2@example.com",
ReplyTo: "admin@example.com", ReplyTo: "admin@example.com",
To: "user@example.com", To: "user@example.com",
@ -105,7 +122,7 @@ var messageSenderTestData = []struct {
}, },
{ {
req: func() (*http.Request, error) { req: func() (*http.Request, error) {
j, err := json.Marshal(smtp.Json{ j, err := json.Marshal(sendmail.Json{
From: "user@example.com", From: "user@example.com",
ReplyTo: "admin@example.com", ReplyTo: "admin@example.com",
To: "user@example.com", To: "user@example.com",
@ -125,7 +142,7 @@ var messageSenderTestData = []struct {
}, },
{ {
req: func() (*http.Request, error) { req: func() (*http.Request, error) {
j, err := json.Marshal(smtp.Json{ j, err := json.Marshal(sendmail.Json{
From: "noreply@example.com, user2@example.com", From: "noreply@example.com, user2@example.com",
ReplyTo: "admin@example.com", ReplyTo: "admin@example.com",
To: "user@example.com", To: "user@example.com",
@ -145,7 +162,7 @@ var messageSenderTestData = []struct {
}, },
{ {
req: func() (*http.Request, error) { req: func() (*http.Request, error) {
j, err := json.Marshal(smtp.Json{ j, err := json.Marshal(sendmail.Json{
From: "noreply@example.com", From: "noreply@example.com",
ReplyTo: "admin@example.com", ReplyTo: "admin@example.com",
To: "user@example.com", To: "user@example.com",
@ -165,7 +182,7 @@ var messageSenderTestData = []struct {
}, },
{ {
req: func() (*http.Request, error) { req: func() (*http.Request, error) {
j, err := json.Marshal(smtp.Json{ j, err := json.Marshal(sendmail.Json{
From: "noreply@example.com", From: "noreply@example.com",
ReplyTo: "admin@example.com", ReplyTo: "admin@example.com",
To: "a <user@example.com", To: "a <user@example.com",
@ -185,7 +202,7 @@ var messageSenderTestData = []struct {
}, },
{ {
req: func() (*http.Request, error) { req: func() (*http.Request, error) {
j, err := json.Marshal(smtp.Json{ j, err := json.Marshal(sendmail.Json{
From: "noreply@example.com", From: "noreply@example.com",
ReplyTo: "admin@example.com", ReplyTo: "admin@example.com",
To: "a <user>", To: "a <user>",
@ -205,7 +222,7 @@ var messageSenderTestData = []struct {
}, },
{ {
req: func() (*http.Request, error) { req: func() (*http.Request, error) {
j, err := json.Marshal(smtp.Json{ j, err := json.Marshal(sendmail.Json{
From: "noreply@example.com", From: "noreply@example.com",
ReplyTo: "admin@example.com", ReplyTo: "admin@example.com",
To: "user@example.com", To: "user@example.com",
@ -225,7 +242,7 @@ var messageSenderTestData = []struct {
}, },
{ {
req: func() (*http.Request, error) { req: func() (*http.Request, error) {
j, err := json.Marshal(smtp.Json{ j, err := json.Marshal(sendmail.Json{
From: "noreply@example.com", From: "noreply@example.com",
ReplyTo: "admin@example.com", ReplyTo: "admin@example.com",
To: "user@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)) return http.NewRequest(http.MethodPost, "https://api.example.com/v1/mail/message", bytes.NewReader(j))
}, },
smtp: &fakeSmtp{ smtp: &fakeSmtp{
from: "noreply@example.com", from: &mail.Address{Address: "noreply@example.com"},
deliver: []string{"user@example.com", "user2@example.com", "user3@example.com"},
body: []byte("Mime-Version: 1.0\r\n" + body: []byte("Mime-Version: 1.0\r\n" +
"Content-Type: text/plain; charset=utf-8\r\n" + "Content-Type: text/plain; charset=utf-8\r\n" +
"Cc: <user2@example.com>\r\n" + "Cc: <user2@example.com>\r\n" +
@ -252,7 +268,7 @@ var messageSenderTestData = []struct {
"Subject: Test Subject\r\n" + "Subject: Test Subject\r\n" +
"Date: Sat, 01 Jan 2000 00:00:00 +0000\r\n" + "Date: Sat, 01 Jan 2000 00:00:00 +0000\r\n" +
"\r\n" + "\r\n" +
"Some plain text"), "Some plain text\r\n"),
}, },
claims: makeFakeAuthClaims("admin@example.com"), claims: makeFakeAuthClaims("admin@example.com"),
status: http.StatusAccepted, status: http.StatusAccepted,

View File

@ -2,12 +2,12 @@ package main
import ( import (
"github.com/1f349/lotus/imap" "github.com/1f349/lotus/imap"
"github.com/1f349/lotus/smtp" "github.com/1f349/lotus/sendmail"
) )
type Conf struct { type Conf struct {
Listen string `yaml:"listen"` Listen string `yaml:"listen"`
Audience string `yaml:"audience"` Audience string `yaml:"audience"`
Smtp *smtp.Smtp `yaml:"smtp"` Smtp *sendmail.Smtp `yaml:"sendmail"`
Imap *imap.Imap `yaml:"imap"` Imap *imap.Imap `yaml:"imap"`
} }

View File

@ -1,4 +1,4 @@
package smtp package sendmail
import ( import (
"bytes" "bytes"

View File

@ -1,4 +1,4 @@
package smtp package sendmail
import ( import (
"github.com/emersion/go-message/mail" "github.com/emersion/go-message/mail"
@ -6,7 +6,7 @@ import (
) )
type Smtp struct { type Smtp struct {
Server string `yaml:"server"` SendMailCommand string `json:"send_mail_command"`
} }
type Mail struct { type Mail struct {
@ -14,13 +14,14 @@ type Mail struct {
Body []byte Body []byte
} }
var execSendMail = func(from string) *exec.Cmd { var execCommand = exec.Command
return exec.Command("/usr/lib/sendmail", "-f", from, "-t")
}
func (s *Smtp) Send(mail *Mail) error { func (s *Smtp) Send(mail *Mail) error {
// start sendmail caller // 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() inPipe, err := sendMail.StdinPipe()
if err != nil { if err != nil {
return err return err
@ -36,5 +37,6 @@ func (s *Smtp) Send(mail *Mail) error {
return err return err
} }
// run command
return sendMail.Run() return sendMail.Run()
} }

67
sendmail/sendmail_test.go Normal file
View File

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

View File

@ -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))
}