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.
This commit is contained in:
@@ -0,0 +1,461 @@
|
||||
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")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user