Files
x/config/parsing/node/parse_test.go
T
ginuerzh 2b1e4ca3a7 test(parsing): add 229 unit tests across 18 config/parsing packages
Covering nil-input safety, plugin backends (HTTP/gRPC/default), loaders
(File/HTTP/Redis), selector strategies, edge cases (empty/invalid inputs),
and hop-to-node metadata inheritance. All tests passing.
2026-05-24 13:34:33 +08:00

462 lines
11 KiB
Go

package node
import (
"io"
"testing"
"github.com/go-gost/core/logger"
"github.com/go-gost/x/config"
"github.com/go-gost/x/config/parsing"
xlogger "github.com/go-gost/x/logger"
// Register connector and dialer implementations needed for node parsing.
_ "github.com/go-gost/x/connector/http"
_ "github.com/go-gost/x/dialer/tcp"
)
func TestMain(m *testing.M) {
logger.SetDefault(xlogger.NewLogger(xlogger.OutputOption(io.Discard)))
m.Run()
}
func testLogger() logger.Logger {
return xlogger.NewLogger(xlogger.OutputOption(io.Discard))
}
func TestParseNode_Nil(t *testing.T) {
n, err := ParseNode("test-hop", nil, testLogger())
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if n != nil {
t.Fatal("expected nil for nil config")
}
}
func TestParseNode_Defaults(t *testing.T) {
n, err := ParseNode("test-hop", &config.NodeConfig{
Name: "node1",
Addr: "example.com:8080",
}, testLogger())
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if n == nil {
t.Fatal("expected non-nil node")
}
}
func TestParseNode_DefaultsEmptyName(t *testing.T) {
n, err := ParseNode("test-hop", &config.NodeConfig{
Name: "",
Addr: "example.com:8080",
}, testLogger())
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if n == nil {
t.Fatal("expected non-nil node")
}
}
func TestParseNode_DefaultsEmptyAddr(t *testing.T) {
n, err := ParseNode("test-hop", &config.NodeConfig{
Name: "no-addr",
Addr: "",
}, testLogger())
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if n == nil {
t.Fatal("expected non-nil node")
}
}
func TestParseNode_ExplicitConnector(t *testing.T) {
n, err := ParseNode("test-hop", &config.NodeConfig{
Name: "explicit-conn",
Addr: "example.com:8080",
Connector: &config.ConnectorConfig{
Type: "http",
},
}, testLogger())
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if n == nil {
t.Fatal("expected non-nil node")
}
}
func TestParseNode_UnregisteredConnector(t *testing.T) {
_, err := ParseNode("test-hop", &config.NodeConfig{
Name: "bad-conn",
Addr: "example.com:8080",
Connector: &config.ConnectorConfig{
Type: "nonexistent-connector-type",
},
}, testLogger())
if err == nil {
t.Fatal("expected error for unregistered connector type")
}
}
func TestParseNode_UnregisteredDialer(t *testing.T) {
_, err := ParseNode("test-hop", &config.NodeConfig{
Name: "bad-dial",
Addr: "example.com:8080",
Dialer: &config.DialerConfig{
Type: "nonexistent-dialer-type",
},
}, testLogger())
if err == nil {
t.Fatal("expected error for unregistered dialer type")
}
}
func TestParseNode_ExplicitDialer(t *testing.T) {
n, err := ParseNode("test-hop", &config.NodeConfig{
Name: "explicit-dial",
Addr: "example.com:8080",
Dialer: &config.DialerConfig{
Type: "tcp",
},
}, testLogger())
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if n == nil {
t.Fatal("expected non-nil node")
}
}
func TestParseNode_WithBypass(t *testing.T) {
n, err := ParseNode("test-hop", &config.NodeConfig{
Name: "bypass-node",
Addr: "example.com:8080",
Bypass: "bp1",
Bypasses: []string{"bp2", "bp3"},
}, testLogger())
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if n == nil {
t.Fatal("expected non-nil node")
}
}
func TestParseNode_WithResolver(t *testing.T) {
n, err := ParseNode("test-hop", &config.NodeConfig{
Name: "resolver-node",
Addr: "example.com:8080",
Resolver: "my-resolver",
}, testLogger())
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if n == nil {
t.Fatal("expected non-nil node")
}
}
func TestParseNode_WithHosts(t *testing.T) {
n, err := ParseNode("test-hop", &config.NodeConfig{
Name: "hosts-node",
Addr: "example.com:8080",
Hosts: "my-hosts",
}, testLogger())
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if n == nil {
t.Fatal("expected non-nil node")
}
}
func TestParseNode_WithNetwork(t *testing.T) {
n, err := ParseNode("test-hop", &config.NodeConfig{
Name: "network-node",
Addr: "example.com:8080",
Network: "tcp",
}, testLogger())
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if n == nil {
t.Fatal("expected non-nil node")
}
}
func TestParseNode_WithMetadata(t *testing.T) {
n, err := ParseNode("test-hop", &config.NodeConfig{
Name: "meta-node",
Addr: "example.com:8080",
Metadata: map[string]any{
parsing.MDKeySoMark: 1234,
parsing.MDKeyInterface: "eth0",
parsing.MDKeyNetns: "myns",
},
}, testLogger())
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if n == nil {
t.Fatal("expected non-nil node")
}
}
func TestParseNode_WithSockOpts(t *testing.T) {
n, err := ParseNode("test-hop", &config.NodeConfig{
Name: "sockopts-node",
Addr: "example.com:8080",
SockOpts: &config.SockOptsConfig{
Mark: 9999,
},
}, testLogger())
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if n == nil {
t.Fatal("expected non-nil node")
}
}
func TestParseNode_WithFilter(t *testing.T) {
n, err := ParseNode("test-hop", &config.NodeConfig{
Name: "filter-node",
Addr: "example.com:8080",
Filter: &config.NodeFilterConfig{
Protocol: "http",
Host: "*.example.com",
Path: "/api",
},
}, testLogger())
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if n == nil {
t.Fatal("expected non-nil node")
}
}
func TestParseNode_WithFilterStarBare(t *testing.T) {
// "*example.com" should become ".example.com"
n, err := ParseNode("test-hop", &config.NodeConfig{
Name: "star-bare-node",
Addr: "example.com:8080",
Filter: &config.NodeFilterConfig{
Host: "*example.com",
},
}, testLogger())
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if n == nil {
t.Fatal("expected non-nil node")
}
}
func TestParseNode_WithMatcher(t *testing.T) {
n, err := ParseNode("test-hop", &config.NodeConfig{
Name: "matcher-node",
Addr: "example.com:8080",
Matcher: &config.NodeMatcherConfig{
Rule: "dstport==80",
Priority: 10,
},
}, testLogger())
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if n == nil {
t.Fatal("expected non-nil node")
}
}
func TestParseNode_WithMatcherAutoPriority(t *testing.T) {
// Priority auto-set to len(rule) when zero
n, err := ParseNode("test-hop", &config.NodeConfig{
Name: "auto-prio-node",
Addr: "example.com:8080",
Matcher: &config.NodeMatcherConfig{
Rule: "dstport==443",
Priority: 0,
},
}, testLogger())
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if n == nil {
t.Fatal("expected non-nil node")
}
}
func TestParseNode_WithHTTPNode(t *testing.T) {
n, err := ParseNode("test-hop", &config.NodeConfig{
Name: "http-node",
Addr: "example.com:8080",
HTTP: &config.HTTPNodeConfig{
Host: "custom.example.com",
RequestHeader: map[string]string{"X-Custom": "value"},
ResponseHeader: map[string]string{"X-Response": "value"},
},
}, testLogger())
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if n == nil {
t.Fatal("expected non-nil node")
}
}
func TestParseNode_WithHTTPAuth(t *testing.T) {
n, err := ParseNode("test-hop", &config.NodeConfig{
Name: "http-auth-node",
Addr: "example.com:8080",
HTTP: &config.HTTPNodeConfig{
Auth: &config.AuthConfig{
Username: "user",
Password: "pass",
},
},
}, testLogger())
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if n == nil {
t.Fatal("expected non-nil node")
}
}
func TestParseNode_WithHTTPDeprecatedHeader(t *testing.T) {
// Deprecated Header field should populate RequestHeader
n, err := ParseNode("test-hop", &config.NodeConfig{
Name: "http-deprecated-node",
Addr: "example.com:8080",
HTTP: &config.HTTPNodeConfig{
Header: map[string]string{"X-Old": "value"},
},
}, testLogger())
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if n == nil {
t.Fatal("expected non-nil node")
}
}
func TestParseNode_WithTLSNode(t *testing.T) {
n, err := ParseNode("test-hop", &config.NodeConfig{
Name: "tls-node",
Addr: "example.com:8080",
TLS: &config.TLSNodeConfig{
ServerName: "secure.example.com",
Secure: true,
Options: &config.TLSOptions{
MinVersion: "1.2",
MaxVersion: "1.3",
CipherSuites: []string{"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384"},
ALPN: []string{"h2", "http/1.1"},
},
},
}, testLogger())
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if n == nil {
t.Fatal("expected non-nil node")
}
}
func TestParseNode_TLSServerNameFromAddr(t *testing.T) {
// When connector/dialer TLS ServerName is empty, it defaults from addr hostname.
// The server name is set on the connector's TLS config (internal), so the best
// verification is that ParseNode does not error and returns a node.
n, err := ParseNode("test-hop", &config.NodeConfig{
Name: "tls-servername-node",
Addr: "tls.example.com:443",
}, testLogger())
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if n == nil {
t.Fatal("expected non-nil node")
}
}
func TestParseNode_ConnectorAuth(t *testing.T) {
n, err := ParseNode("test-hop", &config.NodeConfig{
Name: "conn-auth-node",
Addr: "example.com:8080",
Connector: &config.ConnectorConfig{
Type: "http",
Auth: &config.AuthConfig{
Username: "connuser",
Password: "connpass",
},
},
}, testLogger())
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if n == nil {
t.Fatal("expected non-nil node")
}
}
func TestParseNode_DialerAuth(t *testing.T) {
n, err := ParseNode("test-hop", &config.NodeConfig{
Name: "dial-auth-node",
Addr: "example.com:8080",
Dialer: &config.DialerConfig{
Type: "tcp",
Auth: &config.AuthConfig{
Username: "dialuser",
Password: "dialpass",
},
},
}, testLogger())
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if n == nil {
t.Fatal("expected non-nil node")
}
}
func TestParseNode_InvalidMatcherRule(t *testing.T) {
// An invalid matcher rule sets priority to -1
n, err := ParseNode("test-hop", &config.NodeConfig{
Name: "invalid-matcher-node",
Addr: "example.com:8080",
Matcher: &config.NodeMatcherConfig{
Rule: "",
},
}, testLogger())
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if n == nil {
t.Fatal("expected non-nil node even with empty matcher rule")
}
}
func TestParseNode_DeprecatedFilterFields(t *testing.T) {
// Deprecated filter fields: Protocol/Host/Path via Filter
n, err := ParseNode("test-hop", &config.NodeConfig{
Name: "deprecated-node",
Addr: "example.com:8080",
Filter: &config.NodeFilterConfig{
Protocol: "socks5",
Host: "*.example.com",
Path: "/proxy",
},
}, testLogger())
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if n == nil {
t.Fatal("expected non-nil node")
}
}