96 lines
2.9 KiB
Go
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)
|
|
}
|
|
}
|
|
}
|