Smail/backend/utils/smtp/context_test.go
smail eba1b9d101
Some checks are pending
Security Checks / security (push) Waiting to run
修正邮件外域多地址连接预算并保留失败原因
2026-10-02 14:15:18 +08:00

106 lines
3.2 KiB
Go

package smtp
import (
"context"
"errors"
"net"
"net/netip"
"testing"
"time"
)
func TestDialResolvedAddressesFallsBackAfterTimeout(t *testing.T) {
client, server := net.Pipe()
defer client.Close()
defer server.Close()
addresses := []netip.Addr{netip.MustParseAddr("192.0.2.1"), netip.MustParseAddr("2001:db8::1")}
calls := 0
conn, err := dialResolvedAddresses(context.Background(), addresses, "25", 10*time.Millisecond, func(ctx context.Context, network, address string) (net.Conn, error) {
calls++
if network != "tcp" || address != net.JoinHostPort(addresses[calls-1].String(), "25") {
t.Fatalf("unexpected dial target: %s %s", network, address)
}
if calls == 1 {
<-ctx.Done()
return nil, ctx.Err()
}
if ctx.Err() != nil {
t.Fatal("fallback inherited exhausted attempt context")
}
return client, nil
})
if err != nil || conn != client || calls != 2 {
t.Fatalf("fallback failed: conn=%v calls=%d err=%v", conn, calls, err)
}
}
func TestDialResolvedAddressesHonorsCancellation(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
addresses := []netip.Addr{netip.MustParseAddr("192.0.2.1"), netip.MustParseAddr("192.0.2.2")}
calls := 0
_, err := dialResolvedAddresses(ctx, addresses, "25", time.Second, func(context.Context, string, string) (net.Conn, error) {
calls++
cancel()
return nil, context.Canceled
})
if calls != 1 || !errors.Is(err, context.Canceled) {
t.Fatalf("canceled parent was ignored: calls=%d err=%v", calls, err)
}
}
func TestDialResolvedAddressesRetainsFailures(t *testing.T) {
first := errors.New("first address failed")
second := errors.New("second address failed")
addresses := []netip.Addr{netip.MustParseAddr("192.0.2.1"), netip.MustParseAddr("192.0.2.2")}
calls := 0
_, err := dialResolvedAddresses(context.Background(), addresses, "25", time.Second, func(context.Context, string, string) (net.Conn, error) {
calls++
if calls == 1 {
return nil, first
}
return nil, second
})
if calls != 2 || !errors.Is(err, first) || !errors.Is(err, second) {
t.Fatalf("connection failures were lost: calls=%d err=%v", calls, err)
}
}
func TestSMTPContextCancelsGreeting(t *testing.T) {
client, server := net.Pipe()
defer server.Close()
ctx, cancel := context.WithTimeout(context.Background(), 50*time.Millisecond)
defer cancel()
started := time.Now()
if _, err := newClientWithContext(ctx, client, "localhost", "example.com"); err == nil {
t.Fatal("blocked greeting succeeded")
}
if time.Since(started) > time.Second {
t.Fatal("context did not cancel greeting")
}
}
func TestSMTPContextCancelsCommand(t *testing.T) {
client, server := net.Pipe()
defer server.Close()
go func() {
_, _ = server.Write([]byte("220 local ready\r\n"))
var command [512]byte
_, _ = server.Read(command[:])
}()
ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
defer cancel()
smtpClient, err := newClientWithContext(ctx, client, "localhost", "example.com")
if err != nil {
t.Fatal(err)
}
defer smtpClient.Close()
started := time.Now()
if err := smtpClient.Hello("example.com"); err == nil {
t.Fatal("blocked command succeeded")
}
if time.Since(started) > time.Second {
t.Fatal("context did not cancel command")
}
}