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

211 lines
6.2 KiB
Go

package proxy
import (
"encoding/json"
"os"
"testing"
"ant-chrome/backend/internal/config"
)
func TestChainSocks5RuntimeConfigRoutesThroughSecondHop(t *testing.T) {
chainConfig := buildTestChainSocks5Config(t, 19090)
chainCfg, err := ParseChainSocks5Config(chainConfig)
if err != nil {
t.Fatalf("ParseChainSocks5Config returned error: %v", err)
}
cfg := config.DefaultConfig()
cfg.Browser.UserDataRoot = t.TempDir()
manager := &XrayManager{
Config: cfg,
AppRoot: t.TempDir(),
}
cfgPath, err := manager.buildRuntimeConfigWithRoute(
"chain-test",
[]interface{}{
chainSocks5Outbound(chainCfg.First, "first-hop", ""),
chainSocks5Outbound(chainCfg.Second, "second-hop", "first-hop"),
},
[]interface{}{
map[string]interface{}{
"type": "field",
"inboundTag": []string{"socks-in"},
"outboundTag": "second-hop",
},
},
chainCfg.LocalPort,
"",
)
if err != nil {
t.Fatalf("buildRuntimeConfigWithRoute returned error: %v", err)
}
data, err := os.ReadFile(cfgPath)
if err != nil {
t.Fatalf("read runtime config failed: %v", err)
}
var runtimeConfig map[string]interface{}
if err := json.Unmarshal(data, &runtimeConfig); err != nil {
t.Fatalf("unmarshal runtime config failed: %v", err)
}
inbounds := runtimeConfig["inbounds"].([]interface{})
inbound := inbounds[0].(map[string]interface{})
if got := int(inbound["port"].(float64)); got != 19090 {
t.Fatalf("inbound port = %d, want 19090", got)
}
outbounds := runtimeConfig["outbounds"].([]interface{})
byTag := map[string]map[string]interface{}{}
for _, item := range outbounds {
outbound := item.(map[string]interface{})
if tag, ok := outbound["tag"].(string); ok {
byTag[tag] = outbound
}
}
secondHop := byTag["second-hop"]
if secondHop == nil {
t.Fatalf("second-hop outbound is missing: %+v", byTag)
}
proxySettings, ok := secondHop["proxySettings"].(map[string]interface{})
if !ok {
t.Fatalf("second-hop proxySettings is missing: %+v", secondHop)
}
if got := proxySettings["tag"]; got != "first-hop" {
t.Fatalf("second-hop proxy tag = %v, want first-hop", got)
}
routing := runtimeConfig["routing"].(map[string]interface{})
rules := routing["rules"].([]interface{})
rule := rules[0].(map[string]interface{})
if got := rule["outboundTag"]; got != "second-hop" {
t.Fatalf("route outboundTag = %v, want second-hop", got)
}
}
func TestAuthenticatedSocks5RuntimeConfigUsesLocalBridgeOutbound(t *testing.T) {
cfg := config.DefaultConfig()
cfg.Browser.UserDataRoot = t.TempDir()
manager := &XrayManager{
Config: cfg,
AppRoot: t.TempDir(),
}
outbound, ok, err := buildDirectProxyBridgeOutbound("socks5://user:pass@first-hop.invalid:1080")
if err != nil {
t.Fatalf("buildDirectProxyBridgeOutbound returned error: %v", err)
}
if !ok {
t.Fatal("expected bridge outbound for authenticated socks5 proxy")
}
cfgPath, err := manager.buildRuntimeConfigWithRoute(
"direct-auth-socks-test",
[]interface{}{outbound},
[]interface{}{
map[string]interface{}{
"type": "field",
"inboundTag": []string{"socks-in"},
"outboundTag": "proxy-out",
},
},
19091,
"",
)
if err != nil {
t.Fatalf("buildRuntimeConfigWithRoute returned error: %v", err)
}
data, err := os.ReadFile(cfgPath)
if err != nil {
t.Fatalf("read runtime config failed: %v", err)
}
var runtimeConfig map[string]interface{}
if err := json.Unmarshal(data, &runtimeConfig); err != nil {
t.Fatalf("unmarshal runtime config failed: %v", err)
}
outbounds := runtimeConfig["outbounds"].([]interface{})
byTag := map[string]map[string]interface{}{}
for _, item := range outbounds {
current := item.(map[string]interface{})
if tag, ok := current["tag"].(string); ok {
byTag[tag] = current
}
}
proxyOut := byTag["proxy-out"]
if proxyOut == nil {
t.Fatalf("proxy-out outbound is missing: %+v", byTag)
}
if proxyOut["protocol"] != "socks" {
t.Fatalf("proxy-out protocol = %v, want socks", proxyOut["protocol"])
}
}
func TestChainHTTPOutboundUsesAuthenticatedHTTPServer(t *testing.T) {
outbound := chainSocks5Outbound(chainSocks5Hop{
Protocol: "http",
Server: "first-hop.invalid",
Port: 1080,
Username: "user",
Password: "pass",
}, "first-hop", "")
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])
}
if server["address"] != "first-hop.invalid" {
t.Fatalf("address = %v, want first-hop.invalid", server["address"])
}
if server["port"] != 1080 {
t.Fatalf("port = %v, want 1080", server["port"])
}
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 TestChainMixedHTTPAndSocksRuntimeConfig(t *testing.T) {
chainConfig := "chain+socks5://%7B%22first%22%3A%7B%22protocol%22%3A%22http%22%2C%22server%22%3A%22127.0.0.1%22%2C%22port%22%3A8080%2C%22username%22%3A%22u1%22%2C%22password%22%3A%22p1%22%7D%2C%22second%22%3A%7B%22protocol%22%3A%22socks5%22%2C%22server%22%3A%22127.0.0.2%22%2C%22port%22%3A1080%2C%22username%22%3A%22u2%22%2C%22password%22%3A%22p2%22%7D%7D"
chainCfg, err := ParseChainSocks5Config(chainConfig)
if err != nil {
t.Fatalf("ParseChainSocks5Config returned error: %v", err)
}
first := chainSocks5Outbound(chainCfg.First, "first-hop", "")
second := chainSocks5Outbound(chainCfg.Second, "second-hop", "first-hop")
if first["protocol"] != "http" {
t.Fatalf("first protocol = %v, want http", first["protocol"])
}
if second["protocol"] != "socks" {
t.Fatalf("second protocol = %v, want socks", second["protocol"])
}
proxySettings, ok := second["proxySettings"].(map[string]interface{})
if !ok || proxySettings["tag"] != "first-hop" {
t.Fatalf("second proxySettings invalid: %+v", second["proxySettings"])
}
}