mirror of
https://github.com/foxcpp/maddy.git
synced 2025-04-04 21:47:40 +03:00
343 lines
8.7 KiB
Go
343 lines
8.7 KiB
Go
/*
|
|
Maddy Mail Server - Composable all-in-one email server.
|
|
Copyright © 2019-2020 Max Mazurov <fox.cpp@disroot.org>, Maddy Mail Server contributors
|
|
|
|
This program is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
package testutils
|
|
|
|
import (
|
|
"context"
|
|
"crypto/sha1"
|
|
"encoding/hex"
|
|
"errors"
|
|
"io"
|
|
"reflect"
|
|
"sort"
|
|
"testing"
|
|
|
|
"github.com/emersion/go-message/textproto"
|
|
"github.com/foxcpp/maddy/framework/buffer"
|
|
"github.com/foxcpp/maddy/framework/config"
|
|
"github.com/foxcpp/maddy/framework/exterrors"
|
|
"github.com/foxcpp/maddy/framework/module"
|
|
)
|
|
|
|
type Msg struct {
|
|
MsgMeta *module.MsgMetadata
|
|
MailFrom string
|
|
RcptTo []string
|
|
Body []byte
|
|
Header textproto.Header
|
|
}
|
|
|
|
type Target struct {
|
|
Messages []Msg
|
|
DiscardMessages bool
|
|
|
|
StartErr error
|
|
RcptErr map[string]error
|
|
BodyErr error
|
|
PartialBodyErr map[string]error
|
|
AbortErr error
|
|
CommitErr error
|
|
|
|
InstName string
|
|
}
|
|
|
|
/*
|
|
module.Module is implemented with dummy functions for logging done by MsgPipeline code.
|
|
*/
|
|
|
|
func (dt Target) Init(*config.Map) error {
|
|
return nil
|
|
}
|
|
|
|
func (dt Target) InstanceName() string {
|
|
if dt.InstName != "" {
|
|
return dt.InstName
|
|
}
|
|
return "test_instance"
|
|
}
|
|
|
|
func (dt Target) Name() string {
|
|
return "test_target"
|
|
}
|
|
|
|
type testTargetDelivery struct {
|
|
msg Msg
|
|
tgt *Target
|
|
}
|
|
|
|
type testTargetDeliveryPartial struct {
|
|
testTargetDelivery
|
|
}
|
|
|
|
func (dt *Target) Start(ctx context.Context, msgMeta *module.MsgMetadata, mailFrom string) (module.Delivery, error) {
|
|
if dt.PartialBodyErr != nil {
|
|
return &testTargetDeliveryPartial{
|
|
testTargetDelivery: testTargetDelivery{
|
|
tgt: dt,
|
|
msg: Msg{MsgMeta: msgMeta, MailFrom: mailFrom},
|
|
},
|
|
}, dt.StartErr
|
|
}
|
|
return &testTargetDelivery{
|
|
tgt: dt,
|
|
msg: Msg{MsgMeta: msgMeta, MailFrom: mailFrom},
|
|
}, dt.StartErr
|
|
}
|
|
|
|
func (dtd *testTargetDelivery) AddRcpt(ctx context.Context, to string) error {
|
|
if dtd.tgt.RcptErr != nil {
|
|
if err := dtd.tgt.RcptErr[to]; err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
dtd.msg.RcptTo = append(dtd.msg.RcptTo, to)
|
|
return nil
|
|
}
|
|
|
|
func (dtd *testTargetDeliveryPartial) BodyNonAtomic(ctx context.Context, c module.StatusCollector, header textproto.Header, buf buffer.Buffer) {
|
|
if dtd.tgt.PartialBodyErr != nil {
|
|
for rcpt, err := range dtd.tgt.PartialBodyErr {
|
|
c.SetStatus(rcpt, err)
|
|
}
|
|
return
|
|
}
|
|
|
|
dtd.msg.Header = header
|
|
|
|
body, err := buf.Open()
|
|
if err != nil {
|
|
for rcpt, err := range dtd.tgt.PartialBodyErr {
|
|
c.SetStatus(rcpt, err)
|
|
}
|
|
return
|
|
}
|
|
defer body.Close()
|
|
|
|
dtd.msg.Body, err = io.ReadAll(body)
|
|
if err != nil {
|
|
for rcpt, err := range dtd.tgt.PartialBodyErr {
|
|
c.SetStatus(rcpt, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (dtd *testTargetDelivery) Body(ctx context.Context, header textproto.Header, buf buffer.Buffer) error {
|
|
if dtd.tgt.PartialBodyErr != nil {
|
|
return errors.New("partial failure occurred, no additional information available")
|
|
}
|
|
if dtd.tgt.BodyErr != nil {
|
|
return dtd.tgt.BodyErr
|
|
}
|
|
|
|
dtd.msg.Header = header
|
|
|
|
body, err := buf.Open()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer body.Close()
|
|
|
|
if dtd.tgt.DiscardMessages {
|
|
// Don't bother.
|
|
_, err = io.Copy(io.Discard, body)
|
|
return err
|
|
}
|
|
|
|
dtd.msg.Body, err = io.ReadAll(body)
|
|
return err
|
|
}
|
|
|
|
func (dtd *testTargetDelivery) Abort(ctx context.Context) error {
|
|
return dtd.tgt.AbortErr
|
|
}
|
|
|
|
func (dtd *testTargetDelivery) Commit(ctx context.Context) error {
|
|
if dtd.tgt.CommitErr != nil {
|
|
return dtd.tgt.CommitErr
|
|
}
|
|
if dtd.tgt.DiscardMessages {
|
|
return nil
|
|
}
|
|
dtd.tgt.Messages = append(dtd.tgt.Messages, dtd.msg)
|
|
return nil
|
|
}
|
|
|
|
func DoTestDelivery(t *testing.T, tgt module.DeliveryTarget, from string, to []string) string {
|
|
t.Helper()
|
|
return DoTestDeliveryMeta(t, tgt, from, to, &module.MsgMetadata{
|
|
OriginalFrom: from,
|
|
})
|
|
}
|
|
|
|
func DoTestDeliveryMeta(t *testing.T, tgt module.DeliveryTarget, from string, to []string, msgMeta *module.MsgMetadata) string {
|
|
t.Helper()
|
|
|
|
id, err := DoTestDeliveryErrMeta(t, tgt, from, to, msgMeta)
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
return id
|
|
}
|
|
|
|
func DoTestDeliveryNonAtomic(t *testing.T, c module.StatusCollector, tgt module.DeliveryTarget, from string, to []string) string {
|
|
t.Helper()
|
|
|
|
IDRaw := sha1.Sum([]byte(t.Name()))
|
|
encodedID := hex.EncodeToString(IDRaw[:])
|
|
|
|
testCtx := context.Background()
|
|
|
|
body := buffer.MemoryBuffer{Slice: []byte("foobar\r\n")}
|
|
msgMeta := module.MsgMetadata{
|
|
DontTraceSender: true,
|
|
ID: encodedID,
|
|
OriginalFrom: from,
|
|
}
|
|
t.Log("-- tgt.Start", from)
|
|
delivery, err := tgt.Start(testCtx, &msgMeta, from)
|
|
if err != nil {
|
|
t.Log("-- ... tgt.Start", from, err, exterrors.Fields(err))
|
|
t.Fatalf("Unexpected err: %v %+v", err, exterrors.Fields(err))
|
|
return encodedID
|
|
}
|
|
for _, rcpt := range to {
|
|
t.Log("-- delivery.AddRcpt", rcpt)
|
|
if err := delivery.AddRcpt(testCtx, rcpt); err != nil {
|
|
t.Log("-- ... delivery.AddRcpt", rcpt, err, exterrors.Fields(err))
|
|
t.Log("-- delivery.Abort")
|
|
if err := delivery.Abort(testCtx); err != nil {
|
|
t.Log("-- delivery.Abort:", err, exterrors.Fields(err))
|
|
}
|
|
t.Fatalf("Unexpected err: %v %+v", err, exterrors.Fields(err))
|
|
return encodedID
|
|
}
|
|
}
|
|
t.Log("-- delivery.BodyNonAtomic")
|
|
hdr := textproto.Header{}
|
|
hdr.Add("B", "2")
|
|
hdr.Add("A", "1")
|
|
delivery.(module.PartialDelivery).BodyNonAtomic(testCtx, c, hdr, body)
|
|
t.Log("-- delivery.Commit")
|
|
if err := delivery.Commit(testCtx); err != nil {
|
|
t.Fatalf("Unexpected err: %v %+v", err, exterrors.Fields(err))
|
|
}
|
|
|
|
return encodedID
|
|
}
|
|
|
|
const DeliveryData = "A: 1\r\n" +
|
|
"B: 2\r\n" +
|
|
"\r\n" +
|
|
"foobar\r\n"
|
|
|
|
func DoTestDeliveryErr(t *testing.T, tgt module.DeliveryTarget, from string, to []string) (string, error) {
|
|
return DoTestDeliveryErrMeta(t, tgt, from, to, &module.MsgMetadata{})
|
|
}
|
|
|
|
func DoTestDeliveryErrMeta(t *testing.T, tgt module.DeliveryTarget, from string, to []string, msgMeta *module.MsgMetadata) (string, error) {
|
|
t.Helper()
|
|
|
|
IDRaw := sha1.Sum([]byte(t.Name()))
|
|
encodedID := hex.EncodeToString(IDRaw[:])
|
|
testCtx := context.Background()
|
|
|
|
body := buffer.MemoryBuffer{Slice: []byte("foobar\r\n")}
|
|
msgMeta.DontTraceSender = true
|
|
msgMeta.ID = encodedID
|
|
t.Log("-- tgt.Start", from)
|
|
delivery, err := tgt.Start(testCtx, msgMeta, from)
|
|
if err != nil {
|
|
t.Log("-- ... tgt.Start", from, err, exterrors.Fields(err))
|
|
return encodedID, err
|
|
}
|
|
for _, rcpt := range to {
|
|
t.Log("-- delivery.AddRcpt", rcpt)
|
|
if err := delivery.AddRcpt(testCtx, rcpt); err != nil {
|
|
t.Log("-- ... delivery.AddRcpt", rcpt, err, exterrors.Fields(err))
|
|
t.Log("-- delivery.Abort")
|
|
if err := delivery.Abort(testCtx); err != nil {
|
|
t.Log("-- delivery.Abort:", err, exterrors.Fields(err))
|
|
}
|
|
return encodedID, err
|
|
}
|
|
}
|
|
t.Log("-- delivery.Body")
|
|
hdr := textproto.Header{}
|
|
hdr.Add("B", "2")
|
|
hdr.Add("A", "1")
|
|
if err := delivery.Body(testCtx, hdr, body); err != nil {
|
|
t.Log("-- ... delivery.Body", err, exterrors.Fields(err))
|
|
t.Log("-- delivery.Abort")
|
|
if err := delivery.Abort(testCtx); err != nil {
|
|
t.Log("-- ... delivery.Abort:", err, exterrors.Fields(err))
|
|
}
|
|
return encodedID, err
|
|
}
|
|
t.Log("-- delivery.Commit")
|
|
if err := delivery.Commit(testCtx); err != nil {
|
|
t.Log("-- ... delivery.Commit", err, exterrors.Fields(err))
|
|
return encodedID, err
|
|
}
|
|
|
|
return encodedID, err
|
|
}
|
|
|
|
func CheckTestMessage(t *testing.T, tgt *Target, indx int, sender string, rcpt []string) {
|
|
t.Helper()
|
|
|
|
if len(tgt.Messages) <= indx {
|
|
t.Errorf("wrong amount of messages received, want at least %d, got %d", indx+1, len(tgt.Messages))
|
|
return
|
|
}
|
|
msg := tgt.Messages[indx]
|
|
|
|
CheckMsg(t, &msg, sender, rcpt)
|
|
}
|
|
|
|
func CheckMsg(t *testing.T, msg *Msg, sender string, rcpt []string) {
|
|
t.Helper()
|
|
|
|
idRaw := sha1.Sum([]byte(t.Name()))
|
|
encodedId := hex.EncodeToString(idRaw[:])
|
|
|
|
CheckMsgID(t, msg, sender, rcpt, encodedId)
|
|
}
|
|
|
|
func CheckMsgID(t *testing.T, msg *Msg, sender string, rcpt []string, id string) string {
|
|
t.Helper()
|
|
|
|
if msg.MsgMeta.ID != id && id != "" {
|
|
t.Errorf("empty or wrong delivery context for passed message? %+v", msg.MsgMeta)
|
|
}
|
|
if msg.MailFrom != sender {
|
|
t.Errorf("wrong sender, want %s, got %s", sender, msg.MailFrom)
|
|
}
|
|
|
|
sort.Strings(rcpt)
|
|
sort.Strings(msg.RcptTo)
|
|
if !reflect.DeepEqual(msg.RcptTo, rcpt) {
|
|
t.Errorf("wrong recipients, want %v, got %v", rcpt, msg.RcptTo)
|
|
}
|
|
if string(msg.Body) != "foobar\r\n" {
|
|
t.Errorf("wrong body, want '%s', got '%s' (%v)", "foobar\r\n", string(msg.Body), msg.Body)
|
|
}
|
|
|
|
return msg.MsgMeta.ID
|
|
}
|