Files
2026-05-09 17:30:16 +08:00

217 lines
6.8 KiB
Go

package proxy
import (
"ant-chrome/backend/internal/config"
"fmt"
"net/url"
"strings"
"testing"
)
func TestValidateProxyConfigInvalidRawString(t *testing.T) {
ok, msg := ValidateProxyConfig("not-a-proxy-config", nil, "")
if ok {
t.Fatalf("expected invalid raw string to fail validation")
}
if !strings.Contains(msg, "解析失败") {
t.Fatalf("unexpected message: %s", msg)
}
}
func TestValidateProxyConfigMissingProxyId(t *testing.T) {
ok, msg := ValidateProxyConfig("", []config.BrowserProxy{
{ProxyId: "p1", ProxyConfig: "http://proxy.invalid:8080"},
}, "missing-proxy")
if ok {
t.Fatalf("expected missing proxyId to fail validation")
}
if !strings.Contains(msg, "不存在") {
t.Fatalf("unexpected message: %s", msg)
}
}
func TestValidateProxyConfigMissingProxyIdFallbackToRawConfig(t *testing.T) {
ok, msg := ValidateProxyConfig("socks5://127.0.0.1:1080", []config.BrowserProxy{
{ProxyId: "p1", ProxyConfig: "http://proxy.invalid:8080"},
}, "missing-proxy")
if !ok {
t.Fatalf("expected fallback proxyConfig to pass, msg=%s", msg)
}
}
func TestValidateProxyConfigStandardProxy(t *testing.T) {
ok, msg := ValidateProxyConfig("socks5://127.0.0.1:1080", nil, "")
if !ok {
t.Fatalf("expected standard proxy to pass: %s", msg)
}
}
func TestValidateProxyConfigChainSocks5Proxy(t *testing.T) {
chainConfig := buildTestChainSocks5Config(t, 0)
ok, msg := ValidateProxyConfig(chainConfig, nil, "")
if !ok {
t.Fatalf("expected chain proxy to pass: %s", msg)
}
if !RequiresBridge(chainConfig, nil, "") {
t.Fatalf("expected chain proxy to require bridge")
}
}
func TestValidateProxyConfigChainHTTPProxy(t *testing.T) {
chainConfig := buildTestChainHTTPConfig(t)
ok, msg := ValidateProxyConfig(chainConfig, nil, "")
if !ok {
t.Fatalf("expected chain http proxy to pass: %s", msg)
}
if !RequiresBridge(chainConfig, nil, "") {
t.Fatalf("expected chain http proxy to require bridge")
}
}
func TestRequiresLocalProxyBridgeForBrowserAuthenticatedSocks5(t *testing.T) {
if !RequiresLocalProxyBridgeForBrowser("socks5://user:pass@127.0.0.1:1080") {
t.Fatal("expected authenticated socks5 proxy to require browser bridge")
}
if !RequiresLocalProxyBridgeForBrowser("http://user:pass@127.0.0.1:8080") {
t.Fatal("expected authenticated http proxy to require browser bridge")
}
if RequiresLocalProxyBridgeForBrowser("socks5://127.0.0.1:1080") {
t.Fatal("expected unauthenticated socks5 proxy not to require browser bridge")
}
if RequiresLocalProxyBridgeForBrowser("http://127.0.0.1:8080") {
t.Fatal("expected unauthenticated http proxy not to require browser bridge")
}
}
func TestBuildDirectProxyBridgeOutboundAuthenticatedSocks5(t *testing.T) {
outbound, ok, err := buildDirectProxyBridgeOutbound("socks5://user:pass@127.0.0.1:1080")
if err != nil {
t.Fatalf("buildDirectProxyBridgeOutbound returned error: %v", err)
}
if !ok {
t.Fatal("expected authenticated socks5 proxy to build bridge outbound")
}
if outbound["protocol"] != "socks" {
t.Fatalf("protocol = %v, want socks", outbound["protocol"])
}
settings, ok := outbound["settings"].(map[string]interface{})
if !ok {
t.Fatalf("settings missing: %+v", outbound)
}
servers, ok := settings["servers"].([]interface{})
if !ok || len(servers) != 1 {
t.Fatalf("servers invalid: %+v", settings["servers"])
}
server, ok := servers[0].(map[string]interface{})
if !ok {
t.Fatalf("server invalid: %+v", servers[0])
}
if server["address"] != "127.0.0.1" {
t.Fatalf("address = %v, want 127.0.0.1", server["address"])
}
if got := int(server["port"].(int)); got != 1080 {
t.Fatalf("port = %d, want 1080", got)
}
users, ok := server["users"].([]interface{})
if !ok || len(users) != 1 {
t.Fatalf("users invalid: %+v", server["users"])
}
user, ok := users[0].(map[string]interface{})
if !ok {
t.Fatalf("user invalid: %+v", users[0])
}
if user["user"] != "user" || user["pass"] != "pass" {
t.Fatalf("unexpected user payload: %+v", user)
}
}
func TestBuildDirectProxyBridgeOutboundAuthenticatedHTTP(t *testing.T) {
outbound, ok, err := buildDirectProxyBridgeOutbound("http://user:pass@127.0.0.1:8080")
if err != nil {
t.Fatalf("buildDirectProxyBridgeOutbound returned error: %v", err)
}
if !ok {
t.Fatal("expected bridge outbound for authenticated http proxy")
}
if outbound["protocol"] != "http" {
t.Fatalf("protocol = %v, want http", outbound["protocol"])
}
settings, ok := outbound["settings"].(map[string]interface{})
if !ok {
t.Fatalf("settings missing: %+v", outbound)
}
servers, ok := settings["servers"].([]interface{})
if !ok || len(servers) != 1 {
t.Fatalf("servers invalid: %+v", settings["servers"])
}
server, ok := servers[0].(map[string]interface{})
if !ok {
t.Fatalf("server invalid: %+v", servers[0])
}
users, ok := server["users"].([]interface{})
if !ok || len(users) != 1 {
t.Fatalf("users invalid: %+v", server["users"])
}
user, ok := users[0].(map[string]interface{})
if !ok {
t.Fatalf("user invalid: %+v", users[0])
}
if user["user"] != "user" || user["pass"] != "pass" {
t.Fatalf("unexpected user payload: %+v", user)
}
}
func buildTestChainSocks5Config(t *testing.T, localPort int) string {
t.Helper()
localPortField := ""
if localPort > 0 {
localPortField = fmt.Sprintf(`,"localPort":%d`, localPort)
}
raw := fmt.Sprintf(`{"first":{"protocol":"socks5","server":"127.0.0.1","port":1081,"username":"u1","password":"p1"},"second":{"protocol":"socks5","server":"127.0.0.2","port":1082}%s}`, localPortField)
return "chain+socks5://" + url.QueryEscape(raw)
}
func buildTestChainHTTPConfig(t *testing.T) string {
t.Helper()
raw := `{"first":{"protocol":"http","server":"first-hop.invalid","port":8080,"username":"u1","password":"p1"},"second":{"protocol":"http","server":"second-hop.invalid","port":8081,"username":"u2","password":"p2"}}`
return "chain+socks5://" + url.QueryEscape(raw)
}
func TestDirectProxyBridgeIgnoresClashYAML(t *testing.T) {
t.Parallel()
src := "name: JP-Vmess(Dmit) 1.5x\n" +
"type: vmess\n" +
"server: example.com\n" +
"port: 10091\n" +
"uuid: 5ef4299f-a5eb-4aaa-9bf5-60b541b19294\n" +
"alterId: 0\n" +
"cipher: auto\n" +
"tls: true\n" +
"network: ws\n" +
"ws-opts:\n" +
" path: /\n" +
" headers:\n" +
" Host: example.com\n"
outbound, ok, err := buildDirectProxyBridgeOutbound(src)
if err != nil {
t.Fatalf("buildDirectProxyBridgeOutbound returned error: %v", err)
}
if ok || outbound != nil {
t.Fatalf("clash yaml must not be treated as direct proxy bridge: ok=%v outbound=%v", ok, outbound)
}
standard, parsedOutbound, err := ParseProxyNode(src)
if err != nil {
t.Fatalf("ParseProxyNode returned error: %v", err)
}
if standard != "" {
t.Fatalf("expected xray outbound, got standard proxy %q", standard)
}
if parsedOutbound == nil || parsedOutbound["protocol"] != "vmess" {
t.Fatalf("expected vmess outbound, got %#v", parsedOutbound)
}
}