Files
xinghuoapi/backend/internal/pkg/proxyutil/dialer_test.go
shaw 70e9329e64 feat(proxy): 统一代理配置并支持 SOCKS5H 协议
- 新增 proxyutil 包,统一 HTTP/HTTPS/SOCKS5/SOCKS5H 代理配置逻辑
- SOCKS5H 支持服务端 DNS 解析,避免本地 DNS 泄露
- 移除 ProxyStrict 宽松模式,代理失败直接返回错误不回退直连
- 前端代理管理页面支持 SOCKS5H 协议的添加/编辑/批量导入
- 补充 IPv6 地址和特殊字符密码的边界测试
2026-01-04 11:43:58 +08:00

205 lines
6.1 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package proxyutil
import (
"net/http"
"net/url"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestConfigureTransportProxy_Nil(t *testing.T) {
transport := &http.Transport{}
err := ConfigureTransportProxy(transport, nil)
require.NoError(t, err)
assert.Nil(t, transport.Proxy, "nil proxy should not set Proxy")
assert.Nil(t, transport.DialContext, "nil proxy should not set DialContext")
}
func TestConfigureTransportProxy_HTTP(t *testing.T) {
transport := &http.Transport{}
proxyURL, _ := url.Parse("http://proxy.example.com:8080")
err := ConfigureTransportProxy(transport, proxyURL)
require.NoError(t, err)
assert.NotNil(t, transport.Proxy, "HTTP proxy should set Proxy")
assert.Nil(t, transport.DialContext, "HTTP proxy should not set DialContext")
}
func TestConfigureTransportProxy_HTTPS(t *testing.T) {
transport := &http.Transport{}
proxyURL, _ := url.Parse("https://secure-proxy.example.com:8443")
err := ConfigureTransportProxy(transport, proxyURL)
require.NoError(t, err)
assert.NotNil(t, transport.Proxy, "HTTPS proxy should set Proxy")
assert.Nil(t, transport.DialContext, "HTTPS proxy should not set DialContext")
}
func TestConfigureTransportProxy_SOCKS5(t *testing.T) {
transport := &http.Transport{}
proxyURL, _ := url.Parse("socks5://socks.example.com:1080")
err := ConfigureTransportProxy(transport, proxyURL)
require.NoError(t, err)
assert.Nil(t, transport.Proxy, "SOCKS5 proxy should not set Proxy")
assert.NotNil(t, transport.DialContext, "SOCKS5 proxy should set DialContext")
}
func TestConfigureTransportProxy_SOCKS5H(t *testing.T) {
transport := &http.Transport{}
proxyURL, _ := url.Parse("socks5h://socks.example.com:1080")
err := ConfigureTransportProxy(transport, proxyURL)
require.NoError(t, err)
assert.Nil(t, transport.Proxy, "SOCKS5H proxy should not set Proxy")
assert.NotNil(t, transport.DialContext, "SOCKS5H proxy should set DialContext")
}
func TestConfigureTransportProxy_CaseInsensitive(t *testing.T) {
testCases := []struct {
scheme string
useProxy bool // true = uses Transport.Proxy, false = uses DialContext
}{
{"HTTP://proxy.example.com:8080", true},
{"Http://proxy.example.com:8080", true},
{"HTTPS://proxy.example.com:8443", true},
{"Https://proxy.example.com:8443", true},
{"SOCKS5://socks.example.com:1080", false},
{"Socks5://socks.example.com:1080", false},
{"SOCKS5H://socks.example.com:1080", false},
{"Socks5h://socks.example.com:1080", false},
}
for _, tc := range testCases {
t.Run(tc.scheme, func(t *testing.T) {
transport := &http.Transport{}
proxyURL, _ := url.Parse(tc.scheme)
err := ConfigureTransportProxy(transport, proxyURL)
require.NoError(t, err)
if tc.useProxy {
assert.NotNil(t, transport.Proxy)
assert.Nil(t, transport.DialContext)
} else {
assert.Nil(t, transport.Proxy)
assert.NotNil(t, transport.DialContext)
}
})
}
}
func TestConfigureTransportProxy_Unsupported(t *testing.T) {
testCases := []string{
"ftp://ftp.example.com",
"file:///path/to/file",
"unknown://example.com",
}
for _, tc := range testCases {
t.Run(tc, func(t *testing.T) {
transport := &http.Transport{}
proxyURL, _ := url.Parse(tc)
err := ConfigureTransportProxy(transport, proxyURL)
require.Error(t, err)
assert.Contains(t, err.Error(), "unsupported proxy scheme")
})
}
}
func TestConfigureTransportProxy_WithAuth(t *testing.T) {
transport := &http.Transport{}
proxyURL, _ := url.Parse("socks5://user:password@socks.example.com:1080")
err := ConfigureTransportProxy(transport, proxyURL)
require.NoError(t, err)
assert.NotNil(t, transport.DialContext, "SOCKS5 with auth should set DialContext")
}
func TestConfigureTransportProxy_EmptyScheme(t *testing.T) {
transport := &http.Transport{}
// 空 scheme 的 URL
proxyURL := &url.URL{Host: "proxy.example.com:8080"}
err := ConfigureTransportProxy(transport, proxyURL)
require.Error(t, err)
assert.Contains(t, err.Error(), "unsupported proxy scheme")
}
func TestConfigureTransportProxy_PreservesExistingConfig(t *testing.T) {
// 验证代理配置不会覆盖 Transport 的其他配置
transport := &http.Transport{
MaxIdleConns: 100,
MaxIdleConnsPerHost: 10,
}
proxyURL, _ := url.Parse("socks5://socks.example.com:1080")
err := ConfigureTransportProxy(transport, proxyURL)
require.NoError(t, err)
assert.Equal(t, 100, transport.MaxIdleConns, "MaxIdleConns should be preserved")
assert.Equal(t, 10, transport.MaxIdleConnsPerHost, "MaxIdleConnsPerHost should be preserved")
assert.NotNil(t, transport.DialContext, "DialContext should be set")
}
func TestConfigureTransportProxy_IPv6(t *testing.T) {
testCases := []struct {
name string
proxyURL string
}{
{"SOCKS5H with IPv6 loopback", "socks5h://[::1]:1080"},
{"SOCKS5 with full IPv6", "socks5://[2001:db8::1]:1080"},
{"HTTP with IPv6", "http://[::1]:8080"},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
transport := &http.Transport{}
proxyURL, err := url.Parse(tc.proxyURL)
require.NoError(t, err, "URL should be parseable")
err = ConfigureTransportProxy(transport, proxyURL)
require.NoError(t, err)
})
}
}
func TestConfigureTransportProxy_SpecialCharsInPassword(t *testing.T) {
testCases := []struct {
name string
proxyURL string
}{
// 密码包含 @ 符号URL 编码为 %40
{"password with @", "socks5://user:p%40ssword@proxy.example.com:1080"},
// 密码包含 : 符号URL 编码为 %3A
{"password with :", "socks5://user:pass%3Aword@proxy.example.com:1080"},
// 密码包含 / 符号URL 编码为 %2F
{"password with /", "socks5://user:pass%2Fword@proxy.example.com:1080"},
// 复杂密码
{"complex password", "socks5h://admin:P%40ss%3Aw0rd%2F123@proxy.example.com:1080"},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
transport := &http.Transport{}
proxyURL, err := url.Parse(tc.proxyURL)
require.NoError(t, err, "URL should be parseable")
err = ConfigureTransportProxy(transport, proxyURL)
require.NoError(t, err)
assert.NotNil(t, transport.DialContext, "SOCKS5 should set DialContext")
})
}
}