Files
x/config/parsing/hop/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

430 lines
9.3 KiB
Go

package hop
import (
"io"
"testing"
"github.com/go-gost/core/hop"
"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"
mdutil "github.com/go-gost/x/metadata/util"
// 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 TestParseHop_Nil(t *testing.T) {
h, err := ParseHop(nil, testLogger())
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if h != nil {
t.Fatal("expected nil for nil config")
}
}
func TestParseHop_PluginHTTP(t *testing.T) {
h, err := ParseHop(&config.HopConfig{
Name: "http-hop",
Plugin: &config.PluginConfig{
Type: "http",
Addr: "127.0.0.1:9000",
},
}, testLogger())
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if h == nil {
t.Fatal("expected non-nil hop")
}
}
func TestParseHop_PluginGRPC(t *testing.T) {
h, err := ParseHop(&config.HopConfig{
Name: "grpc-hop",
Plugin: &config.PluginConfig{
Type: "grpc",
Addr: "127.0.0.1:9001",
Token: "secret",
TLS: &config.TLSConfig{
Secure: true,
ServerName: "hop.local",
},
},
}, testLogger())
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if h == nil {
t.Fatal("expected non-nil hop")
}
}
func TestParseHop_PluginDefaultType(t *testing.T) {
h, err := ParseHop(&config.HopConfig{
Name: "default-hop",
Plugin: &config.PluginConfig{
Addr: "127.0.0.1:9002",
},
}, testLogger())
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if h == nil {
t.Fatal("expected non-nil hop")
}
}
func TestParseHop_PluginHTTPWithTLS(t *testing.T) {
h, err := ParseHop(&config.HopConfig{
Name: "http-tls-hop",
Plugin: &config.PluginConfig{
Type: "http",
Addr: "127.0.0.1:9003",
TLS: &config.TLSConfig{
Secure: false,
ServerName: "hop.example.com",
},
},
}, testLogger())
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if h == nil {
t.Fatal("expected non-nil hop")
}
}
func TestParseHop_PluginGRPCWithTLSInsecure(t *testing.T) {
h, err := ParseHop(&config.HopConfig{
Name: "grpc-insecure-hop",
Plugin: &config.PluginConfig{
Type: "grpc",
Addr: "127.0.0.1:9004",
TLS: &config.TLSConfig{
Secure: false, // InsecureSkipVerify = true
},
},
}, testLogger())
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if h == nil {
t.Fatal("expected non-nil hop")
}
}
func TestParseHop_NoNodesNoPlugin(t *testing.T) {
h, err := ParseHop(&config.HopConfig{
Name: "empty-hop",
Nodes: nil,
}, testLogger())
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if h == nil {
t.Fatal("expected non-nil hop even with no nodes")
}
}
func TestParseHop_WithBypass(t *testing.T) {
h, err := ParseHop(&config.HopConfig{
Name: "bypass-hop",
Bypass: "bypass1",
Bypasses: []string{"bypass2", "bypass3"},
}, testLogger())
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if h == nil {
t.Fatal("expected non-nil hop")
}
}
func TestParseHop_WithSelector(t *testing.T) {
h, err := ParseHop(&config.HopConfig{
Name: "selector-hop",
Selector: &config.SelectorConfig{
Strategy: "round",
},
}, testLogger())
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if h == nil {
t.Fatal("expected non-nil hop")
}
}
func TestParseHop_WithNodeNilEntries(t *testing.T) {
h, err := ParseHop(&config.HopConfig{
Name: "nil-nodes-hop",
Nodes: []*config.NodeConfig{
nil,
nil,
},
}, testLogger())
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if h == nil {
t.Fatal("expected non-nil hop when nodes contain nil entries")
}
}
func TestParseHop_SockOptsMark(t *testing.T) {
h, err := ParseHop(&config.HopConfig{
Name: "sockopts-hop",
SockOpts: &config.SockOptsConfig{
Mark: 1234,
},
}, testLogger())
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if h == nil {
t.Fatal("expected non-nil hop")
}
}
func TestParseHop_MetadataSoMark(t *testing.T) {
h, err := ParseHop(&config.HopConfig{
Name: "md-somark-hop",
Metadata: map[string]any{
parsing.MDKeySoMark: 5678,
},
}, testLogger())
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if h == nil {
t.Fatal("expected non-nil hop")
}
}
func TestParseHop_MetadataInterface(t *testing.T) {
h, err := ParseHop(&config.HopConfig{
Name: "md-iface-hop",
Metadata: map[string]any{
parsing.MDKeyInterface: "eth0",
},
}, testLogger())
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if h == nil {
t.Fatal("expected non-nil hop")
}
}
func TestParseHop_MetadataProxyProtocol(t *testing.T) {
h, err := ParseHop(&config.HopConfig{
Name: "md-ppv-hop",
Metadata: map[string]any{
parsing.MDKeyProxyProtocol: 2,
},
}, testLogger())
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if h == nil {
t.Fatal("expected non-nil hop")
}
}
func TestParseHop_MetadataNetns(t *testing.T) {
h, err := ParseHop(&config.HopConfig{
Name: "md-netns-hop",
Metadata: map[string]any{
parsing.MDKeyNetns: "mynetns",
},
}, testLogger())
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if h == nil {
t.Fatal("expected non-nil hop")
}
}
func TestParseHop_InterfaceDeprecated(t *testing.T) {
h, err := ParseHop(&config.HopConfig{
Name: "iface-deprecated-hop",
Interface: "eth1",
}, testLogger())
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if h == nil {
t.Fatal("expected non-nil hop")
}
}
func TestParseHop_FileLoader(t *testing.T) {
h, err := ParseHop(&config.HopConfig{
Name: "file-hop",
File: &config.FileLoader{Path: "/tmp/hop.txt"},
}, testLogger())
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if h == nil {
t.Fatal("expected non-nil hop")
}
}
func TestParseHop_HTTPLoader(t *testing.T) {
h, err := ParseHop(&config.HopConfig{
Name: "http-hop",
HTTP: &config.HTTPLoader{URL: "http://localhost:8080/hop"},
}, testLogger())
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if h == nil {
t.Fatal("expected non-nil hop")
}
}
func TestParseHop_RedisLoader(t *testing.T) {
h, err := ParseHop(&config.HopConfig{
Name: "redis-hop",
Redis: &config.RedisLoader{
Addr: "127.0.0.1:6379",
Key: "hop-key",
Username: "user",
Password: "pass",
DB: 1,
},
}, testLogger())
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if h == nil {
t.Fatal("expected non-nil hop")
}
}
func TestParseHop_NodeInheritsInterface(t *testing.T) {
// When hop-level interface is set and node-level is not, node inherits.
h, err := ParseHop(&config.HopConfig{
Name: "inherit-iface-hop",
Interface: "eth0",
Nodes: []*config.NodeConfig{
{
Name: "node1",
Addr: "example.com:8080",
},
},
}, testLogger())
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if h == nil {
t.Fatal("expected non-nil hop")
}
nodes := h.(hop.NodeList).Nodes()
if len(nodes) != 1 {
t.Fatalf("expected 1 node, got %d", len(nodes))
}
if got := mdutil.GetString(nodes[0].Metadata(), parsing.MDKeyInterface); got != "eth0" {
t.Fatalf("inherited interface = %q, want %q", got, "eth0")
}
}
func TestParseHop_NodeInheritsSoMark(t *testing.T) {
h, err := ParseHop(&config.HopConfig{
Name: "inherit-somark-hop",
SockOpts: &config.SockOptsConfig{
Mark: 9999,
},
Nodes: []*config.NodeConfig{
{
Name: "node1",
Addr: "example.com:8080",
},
},
}, testLogger())
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if h == nil {
t.Fatal("expected non-nil hop")
}
nodes := h.(hop.NodeList).Nodes()
if len(nodes) != 1 {
t.Fatalf("expected 1 node, got %d", len(nodes))
}
if got := mdutil.GetInt(nodes[0].Metadata(), parsing.MDKeySoMark); got != 9999 {
t.Fatalf("inherited so_mark = %d, want %d", got, 9999)
}
}
func TestParseHop_NodeInheritsNetns(t *testing.T) {
h, err := ParseHop(&config.HopConfig{
Name: "inherit-netns-hop",
Metadata: map[string]any{
parsing.MDKeyNetns: "mynetns",
},
Nodes: []*config.NodeConfig{
{
Name: "node1",
Addr: "example.com:8080",
},
},
}, testLogger())
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if h == nil {
t.Fatal("expected non-nil hop")
}
nodes := h.(hop.NodeList).Nodes()
if len(nodes) != 1 {
t.Fatalf("expected 1 node, got %d", len(nodes))
}
if got := mdutil.GetString(nodes[0].Metadata(), parsing.MDKeyNetns); got != "mynetns" {
t.Fatalf("inherited netns = %q, want %q", got, "mynetns")
}
}
func TestParseHop_NodeResolverHostsInheritance(t *testing.T) {
h, err := ParseHop(&config.HopConfig{
Name: "inherit-resolver-hop",
Resolver: "my-resolver",
Hosts: "my-hosts",
Nodes: []*config.NodeConfig{
{
Name: "node1",
Addr: "example.com:8080",
},
},
}, testLogger())
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if h == nil {
t.Fatal("expected non-nil hop")
}
// Verify the node was created (no panic from nil resolver/hosts).
// The resolver/hosts names are inherited, but registry lookup returns
// nil since these names are not registered in this test.
nodes := h.(hop.NodeList).Nodes()
if len(nodes) != 1 {
t.Fatalf("expected 1 node, got %d", len(nodes))
}
}