package mail import ( "context" "errors" "net" "strings" "testing" "time" "github.com/emersion/go-imap/v2/imapclient" "golang.org/x/net/proxy" "thuanle.me/claw-email-bridge/internal/config" ) func TestConnectAndWatch_UsesDirectDial_WhenProxyUnset(t *testing.T) { watcher := &IMAPWatcher{ cfg: &config.Config{ IMAPHost: "imap.example.com", IMAPPort: "993", IMAPUser: "u", IMAPPass: "p", }, } var directCalled bool watcher.dialIMAP = func(addr string, _ *imapclient.Options) (*imapclient.Client, error) { directCalled = true return nil, errors.New("stop") } watcher.dialIMAPViaProxy = func(addr, proxyURL string, _ *imapclient.Options) (*imapclient.Client, error) { t.Fatalf("did not expect proxy dial, got addr=%s proxy=%s", addr, proxyURL) return nil, nil } err := watcher.connectAndWatch(context.Background()) if err == nil || !strings.Contains(err.Error(), "stop") { t.Fatalf("expected stop error, got %v", err) } if !directCalled { t.Fatal("expected direct dial path") } } func TestConnectAndWatch_UsesProxyDial_WhenProxySet(t *testing.T) { watcher := &IMAPWatcher{ cfg: &config.Config{ IMAPHost: "imap.example.com", IMAPPort: "993", IMAPUser: "u", IMAPPass: "p", IMAPProxyURL: "socks5://127.0.0.1:1080", }, } var proxyCalled bool watcher.dialIMAP = func(addr string, _ *imapclient.Options) (*imapclient.Client, error) { t.Fatalf("did not expect direct dial, got addr=%s", addr) return nil, nil } watcher.dialIMAPViaProxy = func(addr, proxyURL string, _ *imapclient.Options) (*imapclient.Client, error) { proxyCalled = true return nil, errors.New("stop") } err := watcher.connectAndWatch(context.Background()) if err == nil || !strings.Contains(err.Error(), "stop") { t.Fatalf("expected stop error, got %v", err) } if !proxyCalled { t.Fatal("expected proxy dial path") } } func TestDialTLSViaSOCKS5_RejectsNonSocks5Scheme(t *testing.T) { _, err := dialTLSViaSOCKS5("imap.example.com:993", "http://proxy:8080", nil) if err == nil || !strings.Contains(err.Error(), "unsupported proxy scheme") { t.Fatalf("expected unsupported scheme error, got %v", err) } } type errDialer struct{} func (errDialer) Dial(network, address string) (net.Conn, error) { return nil, errors.New("stop") } func TestDialTLSViaSOCKS5_UsesTimeoutForwardDialer(t *testing.T) { orig := socks5DialerFactory t.Cleanup(func() { socks5DialerFactory = orig }) socks5DialerFactory = func(network, address string, auth *proxy.Auth, forward proxy.Dialer) (proxy.Dialer, error) { td, ok := forward.(timeoutDialer) if !ok { t.Fatalf("expected timeoutDialer, got %T", forward) } if td.timeout != 30*time.Second { t.Fatalf("expected timeout 30s, got %s", td.timeout) } return errDialer{}, nil } _, err := dialTLSViaSOCKS5("imap.example.com:993", "socks5://127.0.0.1:1080", nil) if err == nil || !strings.Contains(err.Error(), "proxy dial: stop") { t.Fatalf("expected proxy dial stop error, got %v", err) } }