Smail/backend/utils/send/routing_test.go
smail bf1e69f480
Some checks are pending
Security Checks / security (push) Waiting to run
修复生产审查凭据泄露风险并增加持久投递队列
2026-09-17 10:17:09 +08:00

96 lines
2.9 KiB
Go

package send
import (
"context"
"crypto/tls"
"errors"
"io"
"net"
"net/textproto"
"reflect"
"testing"
"git.pcux.cn/xuans/Smail/utils/smtp"
)
func TestResolveMX(t *testing.T) {
for _, tt := range []struct {
name string
records []*net.MX
err error
want []string
wantErr bool
}{
{name: "priority and duplicate", records: []*net.MX{{Host: "backup.example.", Pref: 20}, {Host: "primary.example.", Pref: 10}, {Host: "PRIMARY.example.", Pref: 30}}, want: []string{"primary.example", "backup.example"}},
{name: "implicit MX", want: []string{"example.com"}},
{name: "no MX record", err: &net.DNSError{IsNotFound: true}, want: []string{"example.com"}},
{name: "temporary DNS", err: &net.DNSError{IsTemporary: true}, wantErr: true},
{name: "timeout DNS", err: &net.DNSError{IsTimeout: true}, wantErr: true},
{name: "null MX", records: []*net.MX{{Host: "."}}, wantErr: true},
{name: "invalid MX", records: []*net.MX{nil}, wantErr: true},
} {
t.Run(tt.name, func(t *testing.T) {
hosts, err := resolveMX("example.com", func(ctx context.Context, domain string) ([]*net.MX, error) {
if _, ok := ctx.Deadline(); !ok || domain != "example.com" {
t.Fatal("unbounded or wrong lookup")
}
return tt.records, tt.err
})
if (err != nil) != tt.wantErr || !reflect.DeepEqual(hosts, tt.want) {
t.Fatalf("hosts=%v error=%v", hosts, err)
}
})
}
}
func TestDeliverMXFallbackAndStop(t *testing.T) {
for _, tt := range []struct {
name string
err error
calls int
}{
{"network fallback", &net.OpError{Op: "dial", Err: io.EOF}, 4},
{"temporary refusal fallback", &textproto.Error{Code: 451, Msg: "later"}, 4},
{"permanent refusal", &textproto.Error{Code: 550, Msg: "rejected"}, 1},
{"certificate failure", &tls.CertificateVerificationError{Err: io.EOF}, 1},
{"uncertain DATA", &smtp.DeliveryUncertainError{Cause: io.EOF}, 1},
{"private MX", smtp.ErrUnsafeRemoteAddress, 1},
} {
t.Run(tt.name, func(t *testing.T) {
var calls []string
err := deliverMX([]string{"primary.example", "backup.example"}, func(host, port string) error {
calls = append(calls, host+":"+port)
if host == "backup.example" {
return nil
}
return tt.err
})
if len(calls) != tt.calls {
t.Fatalf("calls=%v", calls)
}
if tt.calls == 4 && err != nil {
t.Fatal(err)
}
if tt.calls == 1 && !errors.Is(err, tt.err) {
t.Fatalf("error=%v", err)
}
})
}
}
func TestRetryable(t *testing.T) {
for _, tt := range []struct {
err error
want bool
}{
{&textproto.Error{Code: 451}, true}, {&net.DNSError{IsTemporary: true}, true},
{&net.DNSError{IsNotFound: true}, false}, {&textproto.Error{Code: 550}, false},
{&smtp.DeliveryUncertainError{Cause: &net.DNSError{IsTimeout: true}}, false},
{&tls.CertificateVerificationError{Err: io.EOF}, false}, {nil, false},
} {
if got := Retryable(tt.err); got != tt.want {
t.Fatalf("Retryable(%v)=%v", tt.err, got)
}
}
}