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,429 @@
|
||||
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))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user