added JMAP (not tested yet)
This commit is contained in:
@@ -1,11 +1,15 @@
|
||||
package relay
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net"
|
||||
"os"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/emersion/go-smtp"
|
||||
|
||||
"mailgoserver/internal/db"
|
||||
"mailgoserver/internal/toolbox"
|
||||
)
|
||||
@@ -190,3 +194,78 @@ func TestProcessQueueOnceExhaustsRetriesAndBounces(t *testing.T) {
|
||||
t.Errorf("expected email_log status 'failed', got %q", log.Status)
|
||||
}
|
||||
}
|
||||
|
||||
// rejectingBackend is a stand-in "receiving MTA" that rejects every RCPT with a fixed
|
||||
// SMTP status/message — used to prove a permanent (5xx) rejection bounces on the first
|
||||
// attempt instead of working through retrySchedule.
|
||||
type rejectingBackend struct{ code int }
|
||||
|
||||
func (b *rejectingBackend) NewSession(*smtp.Conn) (smtp.Session, error) {
|
||||
return &rejectingSession{code: b.code}, nil
|
||||
}
|
||||
|
||||
type rejectingSession struct{ code int }
|
||||
|
||||
func (s *rejectingSession) Mail(from string, opts *smtp.MailOptions) error { return nil }
|
||||
func (s *rejectingSession) Rcpt(to string, opts *smtp.RcptOptions) error {
|
||||
return &smtp.SMTPError{Code: s.code, EnhancedCode: smtp.NoEnhancedCode, Message: "poor reputation"}
|
||||
}
|
||||
func (s *rejectingSession) Data(r io.Reader) error { _, err := io.ReadAll(r); return err }
|
||||
func (s *rejectingSession) Reset() {}
|
||||
func (s *rejectingSession) Logout() error { return nil }
|
||||
|
||||
func TestProcessQueueOnceBouncesImmediatelyOnPermanentRejection(t *testing.T) {
|
||||
database := testDB(t)
|
||||
s := smtp.NewServer(&rejectingBackend{code: 554})
|
||||
s.Domain = "mx.example.com"
|
||||
s.AllowInsecureAuth = true
|
||||
l, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Cleanup(func() { s.Close() })
|
||||
go s.Serve(l)
|
||||
_, portStr, _ := net.SplitHostPort(l.Addr().String())
|
||||
port, err := strconv.Atoi(portStr)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
r := &Relay{
|
||||
DB: database, Hostname: "sender.example.com", Timeout: 5 * time.Second,
|
||||
Logger: toolbox.GetLogger("relay_test"), port: port, mxLookup: fixedMXLookup,
|
||||
}
|
||||
|
||||
logID, err := database.InsertEmailLog(db.EmailLog{MailFrom: "from@example.com", ToAddress: "to@dest.example", Status: "queued"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := database.InsertEmailRecipientLog(db.EmailRecipientLog{EmailLogID: logID, Recipient: "to@dest.example", RecipientType: "to", Status: "queued"}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := r.EnqueueForDelivery(logID, "from@example.com", []string{"to@dest.example"}, []string{"to"}, "Subject: hi\r\n\r\nbody"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// One tick. If this were misclassified as a transient failure it would still be
|
||||
// pending (rescheduled per retrySchedule), not gone.
|
||||
r.ProcessQueueOnce(5, 10)
|
||||
|
||||
pending, err := database.CountPendingRelayQueueItemsForEmailLog(logID)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if pending != 0 {
|
||||
t.Fatalf("expected the queue row to be gone after a single permanent (5xx) rejection, got %d pending", pending)
|
||||
}
|
||||
recipients, err := database.ListRecipientLogsForEmail(logID)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if recipients[0].Status != "failed" {
|
||||
t.Errorf("expected recipient status 'failed' immediately on a 554, got %q", recipients[0].Status)
|
||||
}
|
||||
if recipients[0].ErrorMessage == "" {
|
||||
t.Error("expected the recipient log to carry the server's rejection reason")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user