2b1e4ca3a7
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.
430 lines
9.3 KiB
Go
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))
|
|
}
|
|
}
|