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:
ginuerzh
2026-05-24 13:34:33 +08:00
parent 9febb23bc9
commit 2b1e4ca3a7
18 changed files with 3418 additions and 0 deletions
+144
View File
@@ -0,0 +1,144 @@
package admission
import (
"io"
"testing"
"github.com/go-gost/core/logger"
"github.com/go-gost/x/config"
xlogger "github.com/go-gost/x/logger"
)
func TestMain(m *testing.M) {
logger.SetDefault(xlogger.NewLogger(xlogger.OutputOption(io.Discard)))
m.Run()
}
func TestParseAdmission_Nil(t *testing.T) {
adm := ParseAdmission(nil)
if adm != nil {
t.Fatal("expected nil for nil config")
}
}
func TestParseAdmission_PluginHTTP(t *testing.T) {
adm := ParseAdmission(&config.AdmissionConfig{
Name: "http-adm",
Plugin: &config.PluginConfig{
Type: "http",
Addr: "127.0.0.1:9000",
},
})
if adm == nil {
t.Fatal("expected non-nil admission")
}
}
func TestParseAdmission_PluginGRPC(t *testing.T) {
adm := ParseAdmission(&config.AdmissionConfig{
Name: "grpc-adm",
Plugin: &config.PluginConfig{
Type: "grpc",
Addr: "127.0.0.1:9001",
Token: "secret",
TLS: &config.TLSConfig{
Secure: true,
ServerName: "admission.local",
},
},
})
if adm == nil {
t.Fatal("expected non-nil admission")
}
}
func TestParseAdmission_PluginDefaultType(t *testing.T) {
adm := ParseAdmission(&config.AdmissionConfig{
Name: "default-adm",
Plugin: &config.PluginConfig{
Addr: "127.0.0.1:9002",
},
})
if adm == nil {
t.Fatal("expected non-nil admission")
}
}
func TestParseAdmission_WithMatchers(t *testing.T) {
adm := ParseAdmission(&config.AdmissionConfig{
Name: "matcher-adm",
Matchers: []string{"192.168.0.0/16", "10.0.0.1"},
})
if adm == nil {
t.Fatal("expected non-nil admission")
}
}
func TestParseAdmission_Whitelist(t *testing.T) {
adm := ParseAdmission(&config.AdmissionConfig{
Name: "whitelist-adm",
Whitelist: true,
})
if adm == nil {
t.Fatal("expected non-nil admission")
}
}
func TestParseAdmission_Reverse(t *testing.T) {
adm := ParseAdmission(&config.AdmissionConfig{
Name: "reverse-adm",
Reverse: true,
})
if adm == nil {
t.Fatal("expected non-nil admission")
}
}
func TestParseAdmission_FileLoader(t *testing.T) {
adm := ParseAdmission(&config.AdmissionConfig{
Name: "file-adm",
File: &config.FileLoader{Path: "/tmp/admission.txt"},
})
if adm == nil {
t.Fatal("expected non-nil admission")
}
}
func TestParseAdmission_HTTPLoader(t *testing.T) {
adm := ParseAdmission(&config.AdmissionConfig{
Name: "http-adm",
HTTP: &config.HTTPLoader{URL: "http://localhost:8080/list"},
})
if adm == nil {
t.Fatal("expected non-nil admission")
}
}
func TestParseAdmission_RedisLoader(t *testing.T) {
adm := ParseAdmission(&config.AdmissionConfig{
Name: "redis-adm",
Redis: &config.RedisLoader{
Addr: "127.0.0.1:6379",
Key: "admission-set",
},
})
if adm == nil {
t.Fatal("expected non-nil admission")
}
}
func TestList_ReturnsEntries(t *testing.T) {
// The registry returns a lazy wrapper for any non-empty name.
got := List("test-admission", "test-admission-2")
if len(got) != 2 {
t.Fatalf("expected 2 entries, got %d", len(got))
}
}
func TestList_EmptyNameReturnsNothing(t *testing.T) {
// Empty names are filtered by the registry Get.
got := List("")
if len(got) != 0 {
t.Fatal("expected empty list for empty name")
}
}
+242
View File
@@ -0,0 +1,242 @@
package auth
import (
"io"
"testing"
"github.com/go-gost/core/logger"
"github.com/go-gost/x/config"
xlogger "github.com/go-gost/x/logger"
)
func TestMain(m *testing.M) {
logger.SetDefault(xlogger.NewLogger(xlogger.OutputOption(io.Discard)))
m.Run()
}
func TestInfo_Nil(t *testing.T) {
if u := Info(nil); u != nil {
t.Fatal("expected nil for nil config")
}
}
func TestInfo_EmptyUsername(t *testing.T) {
u := Info(&config.AuthConfig{Username: ""})
if u != nil {
t.Fatal("expected nil for empty username")
}
}
func TestInfo_UsernameOnly(t *testing.T) {
u := Info(&config.AuthConfig{Username: "user"})
if u == nil {
t.Fatal("expected non-nil userinfo")
}
if u.Username() != "user" {
t.Fatalf("username = %q, want %q", u.Username(), "user")
}
if _, hasPassword := u.Password(); hasPassword {
t.Fatal("expected no password")
}
}
func TestInfo_UsernameAndPassword(t *testing.T) {
u := Info(&config.AuthConfig{Username: "user", Password: "pass"})
if u == nil {
t.Fatal("expected non-nil userinfo")
}
if u.Username() != "user" {
t.Fatalf("username = %q, want %q", u.Username(), "user")
}
if p, ok := u.Password(); !ok || p != "pass" {
t.Fatalf("password = %q, want %q", p, "pass")
}
}
func TestInfo_WithFile(t *testing.T) {
// File that doesn't exist - should fall back to username
u := Info(&config.AuthConfig{
Username: "fallback",
Password: "pass",
File: "/nonexistent/auth_file",
})
if u == nil {
t.Fatal("expected non-nil userinfo (fallback)")
}
if u.Username() != "fallback" {
t.Fatalf("username = %q, want %q", u.Username(), "fallback")
}
}
func TestParseAutherFromAuth_Nil(t *testing.T) {
a := ParseAutherFromAuth(nil)
if a != nil {
t.Fatal("expected nil for nil auth config")
}
}
func TestParseAutherFromAuth_EmptyUsername(t *testing.T) {
a := ParseAutherFromAuth(&config.AuthConfig{Username: ""})
if a != nil {
t.Fatal("expected nil for empty username")
}
}
func TestParseAutherFromAuth_Valid(t *testing.T) {
a := ParseAutherFromAuth(&config.AuthConfig{
Username: "user",
Password: "pass",
})
if a == nil {
t.Fatal("expected non-nil authenticator")
}
}
func TestParseAuther_Nil(t *testing.T) {
a := ParseAuther(nil)
if a != nil {
t.Fatal("expected nil for nil config")
}
}
func TestParseAuther_WithUsers(t *testing.T) {
a := ParseAuther(&config.AutherConfig{
Name: "test-auth",
Auths: []*config.AuthConfig{
{Username: "u1", Password: "p1"},
{Username: "u2", Password: "p2"},
{Username: "", Password: "nope"}, // skipped
},
})
if a == nil {
t.Fatal("expected non-nil authenticator")
}
}
func TestParseAuther_WithEmptyAuths(t *testing.T) {
a := ParseAuther(&config.AutherConfig{
Name: "empty-auth",
Auths: []*config.AuthConfig{},
})
if a == nil {
t.Fatal("expected non-nil authenticator")
}
}
func TestParseAuther_PluginHTTP(t *testing.T) {
a := ParseAuther(&config.AutherConfig{
Name: "http-plugin",
Plugin: &config.PluginConfig{
Type: "http",
Addr: "127.0.0.1:9000",
},
})
if a == nil {
t.Fatal("expected non-nil plugin authenticator")
}
}
func TestParseAuther_PluginGRPC(t *testing.T) {
a := ParseAuther(&config.AutherConfig{
Name: "grpc-plugin",
Plugin: &config.PluginConfig{
Type: "grpc",
Addr: "127.0.0.1:9001",
Token: "secret",
TLS: &config.TLSConfig{
Secure: true,
ServerName: "auth.local",
},
},
})
if a == nil {
t.Fatal("expected non-nil plugin authenticator")
}
}
func TestParseAuther_PluginDefaultType(t *testing.T) {
// Default plugin type should be treated as gRPC
a := ParseAuther(&config.AutherConfig{
Name: "default-plugin",
Plugin: &config.PluginConfig{
Addr: "127.0.0.1:9002",
},
})
if a == nil {
t.Fatal("expected non-nil plugin authenticator")
}
}
func TestInfo_ParseFile(t *testing.T) {
// parseInfo with empty reader should return nil
u := Info(&config.AuthConfig{
File: "/nonexistent/file",
})
if u != nil {
t.Fatal("expected nil for nonexistent file (should not crash)")
}
}
func TestList_ReturnsEntries(t *testing.T) {
// The registry returns a lazy wrapper for any non-empty name.
got := List("test-auther", "test-auther-2")
if len(got) != 2 {
t.Fatalf("expected 2 entries, got %d", len(got))
}
}
func TestList_EmptyNameReturnsNothing(t *testing.T) {
// Empty names are filtered by the registry Get.
got := List("")
if len(got) != 0 {
t.Fatal("expected empty list for empty name")
}
}
func TestParseAuther_HTTPLoader(t *testing.T) {
a := ParseAuther(&config.AutherConfig{
Name: "http-loader-auth",
HTTP: &config.HTTPLoader{
URL: "http://localhost:8080/auth",
},
})
if a == nil {
t.Fatal("expected non-nil authenticator")
}
}
func TestParseAuther_FileLoader(t *testing.T) {
a := ParseAuther(&config.AutherConfig{
Name: "file-loader-auth",
File: &config.FileLoader{
Path: "/tmp/auth.txt",
},
})
if a == nil {
t.Fatal("expected non-nil authenticator")
}
}
func TestParseAuther_RedisLoader(t *testing.T) {
a := ParseAuther(&config.AutherConfig{
Name: "redis-loader-auth",
Redis: &config.RedisLoader{
Addr: "127.0.0.1:6379",
},
})
if a == nil {
t.Fatal("expected non-nil authenticator")
}
}
func TestInfo_Integration(t *testing.T) {
// Verify Info returns *url.Userinfo whose String() matches the standard library.
u := Info(&config.AuthConfig{Username: "alice", Password: "secret"})
if u == nil {
t.Fatal("expected non-nil")
}
want := "alice:secret"
if u.String() != want {
t.Fatalf("String() = %q, want %q", u.String(), want)
}
}
+144
View File
@@ -0,0 +1,144 @@
package bypass
import (
"io"
"testing"
"github.com/go-gost/core/logger"
"github.com/go-gost/x/config"
xlogger "github.com/go-gost/x/logger"
)
func TestMain(m *testing.M) {
logger.SetDefault(xlogger.NewLogger(xlogger.OutputOption(io.Discard)))
m.Run()
}
func TestParseBypass_Nil(t *testing.T) {
bp := ParseBypass(nil)
if bp != nil {
t.Fatal("expected nil for nil config")
}
}
func TestParseBypass_PluginHTTP(t *testing.T) {
bp := ParseBypass(&config.BypassConfig{
Name: "http-bp",
Plugin: &config.PluginConfig{
Type: "http",
Addr: "127.0.0.1:9000",
},
})
if bp == nil {
t.Fatal("expected non-nil bypass")
}
}
func TestParseBypass_PluginGRPC(t *testing.T) {
bp := ParseBypass(&config.BypassConfig{
Name: "grpc-bp",
Plugin: &config.PluginConfig{
Type: "grpc",
Addr: "127.0.0.1:9001",
Token: "secret",
TLS: &config.TLSConfig{
Secure: true,
ServerName: "bypass.local",
},
},
})
if bp == nil {
t.Fatal("expected non-nil bypass")
}
}
func TestParseBypass_PluginDefaultType(t *testing.T) {
bp := ParseBypass(&config.BypassConfig{
Name: "default-bp",
Plugin: &config.PluginConfig{
Addr: "127.0.0.1:9002",
},
})
if bp == nil {
t.Fatal("expected non-nil bypass")
}
}
func TestParseBypass_WithMatchers(t *testing.T) {
bp := ParseBypass(&config.BypassConfig{
Name: "matcher-bp",
Matchers: []string{"127.0.0.1", "10.0.0.0/8", "*.example.com"},
})
if bp == nil {
t.Fatal("expected non-nil bypass")
}
}
func TestParseBypass_Whitelist(t *testing.T) {
bp := ParseBypass(&config.BypassConfig{
Name: "whitelist-bp",
Whitelist: true,
})
if bp == nil {
t.Fatal("expected non-nil bypass")
}
}
func TestParseBypass_Reverse(t *testing.T) {
bp := ParseBypass(&config.BypassConfig{
Name: "reverse-bp",
Reverse: true,
})
if bp == nil {
t.Fatal("expected non-nil bypass")
}
}
func TestParseBypass_FileLoader(t *testing.T) {
bp := ParseBypass(&config.BypassConfig{
Name: "file-bp",
File: &config.FileLoader{Path: "/tmp/bypass.txt"},
})
if bp == nil {
t.Fatal("expected non-nil bypass")
}
}
func TestParseBypass_HTTPLoader(t *testing.T) {
bp := ParseBypass(&config.BypassConfig{
Name: "http-bp",
HTTP: &config.HTTPLoader{URL: "http://localhost:8080/list"},
})
if bp == nil {
t.Fatal("expected non-nil bypass")
}
}
func TestParseBypass_RedisLoader(t *testing.T) {
bp := ParseBypass(&config.BypassConfig{
Name: "redis-bp",
Redis: &config.RedisLoader{
Addr: "127.0.0.1:6379",
Key: "bypass-set",
},
})
if bp == nil {
t.Fatal("expected non-nil bypass")
}
}
func TestList_ReturnsEntries(t *testing.T) {
// The registry returns a lazy wrapper for any non-empty name.
got := List("test-bypass", "test-bypass-2")
if len(got) != 2 {
t.Fatalf("expected 2 entries, got %d", len(got))
}
}
func TestList_EmptyNameReturnsNothing(t *testing.T) {
// Empty names are filtered by the registry Get.
got := List("")
if len(got) != 0 {
t.Fatal("expected empty list for empty name")
}
}
+121
View File
@@ -0,0 +1,121 @@
package chain
import (
"io"
"testing"
"github.com/go-gost/core/logger"
"github.com/go-gost/x/config"
xlogger "github.com/go-gost/x/logger"
)
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 TestParseChain_Nil(t *testing.T) {
c, err := ParseChain(nil, testLogger())
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if c != nil {
t.Fatal("expected nil for nil config")
}
}
func TestParseChain_EmptyHops(t *testing.T) {
c, err := ParseChain(&config.ChainConfig{
Name: "empty-hops",
Hops: []*config.HopConfig{},
}, testLogger())
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if c == nil {
t.Fatal("expected non-nil chain")
}
}
func TestParseChain_NilHops(t *testing.T) {
c, err := ParseChain(&config.ChainConfig{
Name: "nil-hops",
Hops: nil,
}, testLogger())
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if c == nil {
t.Fatal("expected non-nil chain")
}
}
func TestParseChain_NilHopEntry(t *testing.T) {
c, err := ParseChain(&config.ChainConfig{
Name: "nil-entry",
Hops: []*config.HopConfig{nil},
}, testLogger())
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if c == nil {
t.Fatal("expected non-nil chain when hop entry is nil")
}
}
func TestParseChain_WithMetadata(t *testing.T) {
c, err := ParseChain(&config.ChainConfig{
Name: "meta-chain",
Metadata: map[string]any{
"key": "value",
},
}, testLogger())
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if c == nil {
t.Fatal("expected non-nil chain")
}
}
func TestParseChain_NamedHop(t *testing.T) {
// Named hop (Nodes==nil, Plugin==nil) uses registry lookup
c, err := ParseChain(&config.ChainConfig{
Name: "named-hop",
Hops: []*config.HopConfig{
{Name: "nonexistent-hop"},
},
}, testLogger())
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if c == nil {
t.Fatal("expected non-nil chain even with unregistered hop")
}
}
func TestParseChain_InlineHopWithPlugin(t *testing.T) {
// Inline hop (Nodes!=nil or Plugin!=nil) calls hop_parser.ParseHop
c, err := ParseChain(&config.ChainConfig{
Name: "inline-hop",
Hops: []*config.HopConfig{
{
Name: "plugin-hop",
Plugin: &config.PluginConfig{
Type: "http",
Addr: "127.0.0.1:9000",
},
},
},
}, testLogger())
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if c == nil {
t.Fatal("expected non-nil chain")
}
}
+429
View File
@@ -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))
}
}
+154
View File
@@ -0,0 +1,154 @@
package hosts
import (
"io"
"testing"
"github.com/go-gost/core/logger"
"github.com/go-gost/x/config"
xlogger "github.com/go-gost/x/logger"
)
func TestMain(m *testing.M) {
logger.SetDefault(xlogger.NewLogger(xlogger.OutputOption(io.Discard)))
m.Run()
}
func TestParseHostMapper_Nil(t *testing.T) {
hm := ParseHostMapper(nil)
if hm != nil {
t.Fatal("expected nil for nil config")
}
}
func TestParseHostMapper_PluginHTTP(t *testing.T) {
hm := ParseHostMapper(&config.HostsConfig{
Name: "http-hosts",
Plugin: &config.PluginConfig{
Type: "http",
Addr: "127.0.0.1:9000",
},
})
if hm == nil {
t.Fatal("expected non-nil host mapper")
}
}
func TestParseHostMapper_PluginGRPC(t *testing.T) {
hm := ParseHostMapper(&config.HostsConfig{
Name: "grpc-hosts",
Plugin: &config.PluginConfig{
Type: "grpc",
Addr: "127.0.0.1:9001",
Token: "secret",
TLS: &config.TLSConfig{
Secure: true,
ServerName: "hosts.local",
},
},
})
if hm == nil {
t.Fatal("expected non-nil host mapper")
}
}
func TestParseHostMapper_PluginDefaultType(t *testing.T) {
hm := ParseHostMapper(&config.HostsConfig{
Name: "default-hosts",
Plugin: &config.PluginConfig{
Addr: "127.0.0.1:9002",
},
})
if hm == nil {
t.Fatal("expected non-nil host mapper")
}
}
func TestParseHostMapper_WithMappings(t *testing.T) {
hm := ParseHostMapper(&config.HostsConfig{
Name: "mapping-hosts",
Mappings: []*config.HostMappingConfig{
{IP: "127.0.0.1", Hostname: "localhost"},
{IP: "::1", Hostname: "ipv6-localhost"},
},
})
if hm == nil {
t.Fatal("expected non-nil host mapper")
}
}
func TestParseHostMapper_EmptyMapping(t *testing.T) {
// Mappings with empty IP or Hostname should be skipped
hm := ParseHostMapper(&config.HostsConfig{
Name: "partial-hosts",
Mappings: []*config.HostMappingConfig{
{IP: "", Hostname: "nope"}, // skipped
{IP: "127.0.0.1", Hostname: ""}, // skipped
{IP: "127.0.0.1", Hostname: "valid"}, // kept
},
})
if hm == nil {
t.Fatal("expected non-nil host mapper even with partial mappings")
}
}
func TestParseHostMapper_InvalidIP(t *testing.T) {
hm := ParseHostMapper(&config.HostsConfig{
Name: "invalid-ip",
Mappings: []*config.HostMappingConfig{
{IP: "not-an-ip", Hostname: "example.com"}, // skipped
{IP: "127.0.0.1", Hostname: "valid"}, // kept
},
})
if hm == nil {
t.Fatal("expected non-nil host mapper even with invalid IP")
}
}
func TestParseHostMapper_FileLoader(t *testing.T) {
hm := ParseHostMapper(&config.HostsConfig{
Name: "file-hosts",
File: &config.FileLoader{Path: "/etc/hosts"},
})
if hm == nil {
t.Fatal("expected non-nil host mapper")
}
}
func TestParseHostMapper_HTTPLoader(t *testing.T) {
hm := ParseHostMapper(&config.HostsConfig{
Name: "http-hosts",
HTTP: &config.HTTPLoader{URL: "http://localhost:8080/hosts"},
})
if hm == nil {
t.Fatal("expected non-nil host mapper")
}
}
func TestParseHostMapper_RedisLoader(t *testing.T) {
hm := ParseHostMapper(&config.HostsConfig{
Name: "redis-hosts",
Redis: &config.RedisLoader{
Addr: "127.0.0.1:6379",
Key: "hosts-key",
Type: "set",
},
})
if hm == nil {
t.Fatal("expected non-nil host mapper")
}
}
func TestParseHostMapper_RedisListLoader(t *testing.T) {
hm := ParseHostMapper(&config.HostsConfig{
Name: "redis-list-hosts",
Redis: &config.RedisLoader{
Addr: "127.0.0.1:6379",
Key: "hosts-list",
Type: "list",
},
})
if hm == nil {
t.Fatal("expected non-nil host mapper")
}
}
+141
View File
@@ -0,0 +1,141 @@
package ingress
import (
"io"
"testing"
"github.com/go-gost/core/logger"
"github.com/go-gost/x/config"
xlogger "github.com/go-gost/x/logger"
)
func TestMain(m *testing.M) {
logger.SetDefault(xlogger.NewLogger(xlogger.OutputOption(io.Discard)))
m.Run()
}
func TestParseIngress_Nil(t *testing.T) {
ing := ParseIngress(nil)
if ing != nil {
t.Fatal("expected nil for nil config")
}
}
func TestParseIngress_PluginHTTP(t *testing.T) {
ing := ParseIngress(&config.IngressConfig{
Name: "http-ing",
Plugin: &config.PluginConfig{
Type: "http",
Addr: "127.0.0.1:9000",
},
})
if ing == nil {
t.Fatal("expected non-nil ingress")
}
}
func TestParseIngress_PluginGRPC(t *testing.T) {
ing := ParseIngress(&config.IngressConfig{
Name: "grpc-ing",
Plugin: &config.PluginConfig{
Type: "grpc",
Addr: "127.0.0.1:9001",
Token: "secret",
TLS: &config.TLSConfig{
Secure: true,
ServerName: "ingress.local",
},
},
})
if ing == nil {
t.Fatal("expected non-nil ingress")
}
}
func TestParseIngress_PluginDefaultType(t *testing.T) {
ing := ParseIngress(&config.IngressConfig{
Name: "default-ing",
Plugin: &config.PluginConfig{
Addr: "127.0.0.1:9002",
},
})
if ing == nil {
t.Fatal("expected non-nil ingress")
}
}
func TestParseIngress_WithRules(t *testing.T) {
ing := ParseIngress(&config.IngressConfig{
Name: "rule-ing",
Rules: []*config.IngressRuleConfig{
{Hostname: "example.com", Endpoint: "10.0.0.1:8080"},
{Hostname: "test.com", Endpoint: "10.0.0.2:9090"},
},
})
if ing == nil {
t.Fatal("expected non-nil ingress")
}
}
func TestParseIngress_EmptyRules(t *testing.T) {
// Rules with empty hostname or endpoint should be skipped
ing := ParseIngress(&config.IngressConfig{
Name: "partial-ing",
Rules: []*config.IngressRuleConfig{
{Hostname: "", Endpoint: "nope:8080"}, // skipped
{Hostname: "nope.com", Endpoint: ""}, // skipped
{Hostname: "valid.com", Endpoint: "10.0.0.1:8080"}, // kept
},
})
if ing == nil {
t.Fatal("expected non-nil ingress even with partial rules")
}
}
func TestParseIngress_FileLoader(t *testing.T) {
ing := ParseIngress(&config.IngressConfig{
Name: "file-ing",
File: &config.FileLoader{Path: "/tmp/ingress.txt"},
})
if ing == nil {
t.Fatal("expected non-nil ingress")
}
}
func TestParseIngress_HTTPLoader(t *testing.T) {
ing := ParseIngress(&config.IngressConfig{
Name: "http-ing",
HTTP: &config.HTTPLoader{URL: "http://localhost:8080/ingress"},
})
if ing == nil {
t.Fatal("expected non-nil ingress")
}
}
func TestParseIngress_RedisLoader(t *testing.T) {
ing := ParseIngress(&config.IngressConfig{
Name: "redis-ing",
Redis: &config.RedisLoader{
Addr: "127.0.0.1:6379",
Key: "ingress-key",
Type: "hash",
},
})
if ing == nil {
t.Fatal("expected non-nil ingress")
}
}
func TestParseIngress_RedisSetLoader(t *testing.T) {
ing := ParseIngress(&config.IngressConfig{
Name: "redis-set-ing",
Redis: &config.RedisLoader{
Addr: "127.0.0.1:6379",
Key: "ingress-set",
Type: "set",
},
})
if ing == nil {
t.Fatal("expected non-nil ingress")
}
}
+234
View File
@@ -0,0 +1,234 @@
package limiter
import (
"io"
"testing"
"github.com/go-gost/core/logger"
"github.com/go-gost/x/config"
xlogger "github.com/go-gost/x/logger"
)
func TestMain(m *testing.M) {
logger.SetDefault(xlogger.NewLogger(xlogger.OutputOption(io.Discard)))
m.Run()
}
func TestParseTrafficLimiter_Nil(t *testing.T) {
lim := ParseTrafficLimiter(nil)
if lim != nil {
t.Fatal("expected nil for nil config")
}
}
func TestParseTrafficLimiter_WithLimits(t *testing.T) {
lim := ParseTrafficLimiter(&config.LimiterConfig{
Name: "traffic-limiter",
Limits: []string{"100KB", "200KB"},
})
if lim == nil {
t.Fatal("expected non-nil traffic limiter")
}
}
func TestParseTrafficLimiter_PluginHTTP(t *testing.T) {
lim := ParseTrafficLimiter(&config.LimiterConfig{
Name: "http-lim",
Plugin: &config.PluginConfig{
Type: "http",
Addr: "127.0.0.1:9000",
},
})
if lim == nil {
t.Fatal("expected non-nil traffic limiter")
}
}
func TestParseTrafficLimiter_PluginGRPC(t *testing.T) {
lim := ParseTrafficLimiter(&config.LimiterConfig{
Name: "grpc-lim",
Plugin: &config.PluginConfig{
Type: "grpc",
Addr: "127.0.0.1:9001",
Token: "secret",
TLS: &config.TLSConfig{
Secure: true,
ServerName: "limiter.local",
},
},
})
if lim == nil {
t.Fatal("expected non-nil traffic limiter")
}
}
func TestParseTrafficLimiter_PluginDefaultType(t *testing.T) {
lim := ParseTrafficLimiter(&config.LimiterConfig{
Name: "default-lim",
Plugin: &config.PluginConfig{
Addr: "127.0.0.1:9002",
},
})
if lim == nil {
t.Fatal("expected non-nil traffic limiter")
}
}
func TestParseTrafficLimiter_FileLoader(t *testing.T) {
lim := ParseTrafficLimiter(&config.LimiterConfig{
Name: "file-lim",
File: &config.FileLoader{Path: "/tmp/limiter.txt"},
})
if lim == nil {
t.Fatal("expected non-nil traffic limiter")
}
}
func TestParseTrafficLimiter_HTTPLoader(t *testing.T) {
lim := ParseTrafficLimiter(&config.LimiterConfig{
Name: "http-lim",
HTTP: &config.HTTPLoader{URL: "http://localhost:8080/limits"},
})
if lim == nil {
t.Fatal("expected non-nil traffic limiter")
}
}
func TestParseTrafficLimiter_RedisLoader(t *testing.T) {
lim := ParseTrafficLimiter(&config.LimiterConfig{
Name: "redis-lim",
Redis: &config.RedisLoader{
Addr: "127.0.0.1:6379",
Key: "limiter-key",
},
})
if lim == nil {
t.Fatal("expected non-nil traffic limiter")
}
}
func TestParseTrafficLimiter_RedisListLoader(t *testing.T) {
lim := ParseTrafficLimiter(&config.LimiterConfig{
Name: "redis-list-lim",
Redis: &config.RedisLoader{
Addr: "127.0.0.1:6379",
Key: "limiter-list",
Type: "list",
},
})
if lim == nil {
t.Fatal("expected non-nil traffic limiter")
}
}
// --- ConnLimiter ---
func TestParseConnLimiter_Nil(t *testing.T) {
lim := ParseConnLimiter(nil)
if lim != nil {
t.Fatal("expected nil for nil config")
}
}
func TestParseConnLimiter_WithLimits(t *testing.T) {
lim := ParseConnLimiter(&config.LimiterConfig{
Name: "conn-limiter",
Limits: []string{"10", "50"},
})
if lim == nil {
t.Fatal("expected non-nil conn limiter")
}
}
func TestParseConnLimiter_RedisLoader(t *testing.T) {
lim := ParseConnLimiter(&config.LimiterConfig{
Name: "redis-conn-lim",
Redis: &config.RedisLoader{
Addr: "127.0.0.1:6379",
Key: "conn-key",
},
})
if lim == nil {
t.Fatal("expected non-nil conn limiter")
}
}
func TestParseConnLimiter_RedisListLoader(t *testing.T) {
lim := ParseConnLimiter(&config.LimiterConfig{
Name: "redis-list-conn-lim",
Redis: &config.RedisLoader{
Addr: "127.0.0.1:6379",
Key: "conn-list",
Type: "list",
},
})
if lim == nil {
t.Fatal("expected non-nil conn limiter")
}
}
func TestParseConnLimiter_FileLoader(t *testing.T) {
lim := ParseConnLimiter(&config.LimiterConfig{
Name: "file-conn-lim",
File: &config.FileLoader{Path: "/tmp/conn_limiter.txt"},
})
if lim == nil {
t.Fatal("expected non-nil conn limiter")
}
}
// --- RateLimiter ---
func TestParseRateLimiter_Nil(t *testing.T) {
lim := ParseRateLimiter(nil)
if lim != nil {
t.Fatal("expected nil for nil config")
}
}
func TestParseRateLimiter_WithLimits(t *testing.T) {
lim := ParseRateLimiter(&config.LimiterConfig{
Name: "rate-limiter",
Limits: []string{"100", "200"},
})
if lim == nil {
t.Fatal("expected non-nil rate limiter")
}
}
func TestParseRateLimiter_RedisLoader(t *testing.T) {
lim := ParseRateLimiter(&config.LimiterConfig{
Name: "redis-rate-lim",
Redis: &config.RedisLoader{
Addr: "127.0.0.1:6379",
Key: "rate-key",
},
})
if lim == nil {
t.Fatal("expected non-nil rate limiter")
}
}
func TestParseRateLimiter_RedisListLoader(t *testing.T) {
lim := ParseRateLimiter(&config.LimiterConfig{
Name: "redis-list-rate-lim",
Redis: &config.RedisLoader{
Addr: "127.0.0.1:6379",
Key: "rate-list",
Type: "list",
},
})
if lim == nil {
t.Fatal("expected non-nil rate limiter")
}
}
func TestParseRateLimiter_FileLoader(t *testing.T) {
lim := ParseRateLimiter(&config.LimiterConfig{
Name: "file-rate-lim",
File: &config.FileLoader{Path: "/tmp/rate_limiter.txt"},
})
if lim == nil {
t.Fatal("expected non-nil rate limiter")
}
}
+142
View File
@@ -0,0 +1,142 @@
package logger
import (
"io"
"testing"
"github.com/go-gost/core/logger"
"github.com/go-gost/x/config"
xlogger "github.com/go-gost/x/logger"
)
func TestMain(m *testing.M) {
logger.SetDefault(xlogger.NewLogger(xlogger.OutputOption(io.Discard)))
m.Run()
}
func TestParseLogger_Nil(t *testing.T) {
lg := ParseLogger(nil)
if lg != nil {
t.Fatal("expected nil for nil config")
}
}
func TestParseLogger_NilLog(t *testing.T) {
lg := ParseLogger(&config.LoggerConfig{
Name: "test",
Log: nil,
})
if lg != nil {
t.Fatal("expected nil when Log is nil")
}
}
func TestParseLogger_Levels(t *testing.T) {
for _, level := range []string{"trace", "debug", "info", "warn", "error", "fatal"} {
t.Run(level, func(t *testing.T) {
lg := ParseLogger(&config.LoggerConfig{
Name: "test-" + level,
Log: &config.LogConfig{
Level: level,
},
})
if lg == nil {
t.Fatal("expected non-nil logger")
}
})
}
}
func TestParseLogger_OutputNone(t *testing.T) {
lg := ParseLogger(&config.LoggerConfig{
Name: "null-logger",
Log: &config.LogConfig{
Output: "none",
},
})
if lg == nil {
t.Fatal("expected non-nil logger")
}
}
func TestParseLogger_OutputStdout(t *testing.T) {
lg := ParseLogger(&config.LoggerConfig{
Name: "stdout-logger",
Log: &config.LogConfig{
Output: "stdout",
},
})
if lg == nil {
t.Fatal("expected non-nil logger")
}
}
func TestParseLogger_OutputStderr(t *testing.T) {
lg := ParseLogger(&config.LoggerConfig{
Name: "stderr-logger",
Log: &config.LogConfig{
Output: "stderr",
},
})
if lg == nil {
t.Fatal("expected non-nil logger")
}
}
func TestParseLogger_OutputDefault(t *testing.T) {
lg := ParseLogger(&config.LoggerConfig{
Name: "default-logger",
Log: &config.LogConfig{
Output: "",
},
})
if lg == nil {
t.Fatal("expected non-nil logger")
}
}
func TestParseLogger_WithFormat(t *testing.T) {
lg := ParseLogger(&config.LoggerConfig{
Name: "format-logger",
Log: &config.LogConfig{
Format: "json",
Level: "info",
},
})
if lg == nil {
t.Fatal("expected non-nil logger")
}
}
func TestParseLogger_FileOutput(t *testing.T) {
lg := ParseLogger(&config.LoggerConfig{
Name: "file-logger",
Log: &config.LogConfig{
Output: "/tmp/gost-test.log",
Rotation: &config.LogRotationConfig{
MaxSize: 10,
MaxAge: 7,
MaxBackups: 5,
LocalTime: true,
Compress: true,
},
},
})
if lg == nil {
t.Fatal("expected non-nil logger")
}
}
func TestList_EmptyLogger(t *testing.T) {
got := List("nonexistent")
if len(got) != 0 {
t.Fatal("expected empty list for unregistered name")
}
}
func TestList_WithNames(t *testing.T) {
got := List("nonexistent", "also_nonexistent")
if len(got) != 0 {
t.Fatal("expected empty list for unregistered names")
}
}
+461
View File
@@ -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")
}
}
+75
View File
@@ -0,0 +1,75 @@
package observer
import (
"io"
"testing"
"github.com/go-gost/core/logger"
"github.com/go-gost/x/config"
xlogger "github.com/go-gost/x/logger"
)
func TestMain(m *testing.M) {
logger.SetDefault(xlogger.NewLogger(xlogger.OutputOption(io.Discard)))
m.Run()
}
func TestParseObserver_Nil(t *testing.T) {
obs := ParseObserver(nil)
if obs != nil {
t.Fatal("expected nil for nil config")
}
}
func TestParseObserver_NilPlugin(t *testing.T) {
obs := ParseObserver(&config.ObserverConfig{
Name: "no-plugin",
Plugin: nil,
})
if obs != nil {
t.Fatal("expected nil when plugin is nil")
}
}
func TestParseObserver_PluginHTTP(t *testing.T) {
obs := ParseObserver(&config.ObserverConfig{
Name: "http-obs",
Plugin: &config.PluginConfig{
Type: "http",
Addr: "127.0.0.1:9000",
},
})
if obs == nil {
t.Fatal("expected non-nil observer")
}
}
func TestParseObserver_PluginGRPC(t *testing.T) {
obs := ParseObserver(&config.ObserverConfig{
Name: "grpc-obs",
Plugin: &config.PluginConfig{
Type: "grpc",
Addr: "127.0.0.1:9001",
Token: "secret",
TLS: &config.TLSConfig{
Secure: true,
ServerName: "observer.local",
},
},
})
if obs == nil {
t.Fatal("expected non-nil observer")
}
}
func TestParseObserver_PluginDefaultType(t *testing.T) {
obs := ParseObserver(&config.ObserverConfig{
Name: "default-obs",
Plugin: &config.PluginConfig{
Addr: "127.0.0.1:9002",
},
})
if obs == nil {
t.Fatal("expected non-nil observer")
}
}
+227
View File
@@ -0,0 +1,227 @@
package parser
import (
"testing"
"github.com/go-gost/x/config"
)
func TestMergeConfig_NilBoth(t *testing.T) {
got := mergeConfig(nil, nil)
if got != nil {
t.Fatal("expected nil for two nil inputs")
}
}
func TestMergeConfig_NilFirst(t *testing.T) {
cfg2 := &config.Config{
Services: []*config.ServiceConfig{{Name: "svc"}},
}
got := mergeConfig(nil, cfg2)
if len(got.Services) != 1 || got.Services[0].Name != "svc" {
t.Fatal("expected cfg2 to be returned when cfg1 is nil")
}
}
func TestMergeConfig_NilSecond(t *testing.T) {
cfg1 := &config.Config{
Services: []*config.ServiceConfig{{Name: "svc"}},
}
got := mergeConfig(cfg1, nil)
if len(got.Services) != 1 || got.Services[0].Name != "svc" {
t.Fatal("expected cfg1 to be returned when cfg2 is nil")
}
}
func TestMergeConfig_AppendsAllSlices(t *testing.T) {
cfg1 := &config.Config{
Services: []*config.ServiceConfig{{Name: "s1"}},
Chains: []*config.ChainConfig{{Name: "c1"}},
Hops: []*config.HopConfig{{Name: "h1"}},
Authers: []*config.AutherConfig{{Name: "a1"}},
Admissions: []*config.AdmissionConfig{{Name: "ad1"}},
Bypasses: []*config.BypassConfig{{Name: "b1"}},
Resolvers: []*config.ResolverConfig{{Name: "r1"}},
Hosts: []*config.HostsConfig{{Name: "hosts1"}},
Ingresses: []*config.IngressConfig{{Name: "i1"}},
SDs: []*config.SDConfig{{Name: "sd1"}},
Recorders: []*config.RecorderConfig{{Name: "rec1"}},
Limiters: []*config.LimiterConfig{{Name: "l1"}},
CLimiters: []*config.LimiterConfig{{Name: "cl1"}},
RLimiters: []*config.LimiterConfig{{Name: "rl1"}},
Loggers: []*config.LoggerConfig{{Name: "log1"}},
Routers: []*config.RouterConfig{{Name: "rt1"}},
Observers: []*config.ObserverConfig{{Name: "obs1"}},
}
cfg2 := &config.Config{
Services: []*config.ServiceConfig{{Name: "s2"}},
Chains: []*config.ChainConfig{{Name: "c2"}},
Hops: []*config.HopConfig{{Name: "h2"}},
Authers: []*config.AutherConfig{{Name: "a2"}},
Admissions: []*config.AdmissionConfig{{Name: "ad2"}},
Bypasses: []*config.BypassConfig{{Name: "b2"}},
Resolvers: []*config.ResolverConfig{{Name: "r2"}},
Hosts: []*config.HostsConfig{{Name: "hosts2"}},
Ingresses: []*config.IngressConfig{{Name: "i2"}},
SDs: []*config.SDConfig{{Name: "sd2"}},
Recorders: []*config.RecorderConfig{{Name: "rec2"}},
Limiters: []*config.LimiterConfig{{Name: "l2"}},
CLimiters: []*config.LimiterConfig{{Name: "cl2"}},
RLimiters: []*config.LimiterConfig{{Name: "rl2"}},
Loggers: []*config.LoggerConfig{{Name: "log2"}},
Routers: []*config.RouterConfig{{Name: "rt2"}},
Observers: []*config.ObserverConfig{{Name: "obs2"}},
}
got := mergeConfig(cfg1, cfg2)
if len(got.Services) != 2 || got.Services[0].Name != "s1" || got.Services[1].Name != "s2" {
t.Fatal("services not properly appended")
}
if len(got.Chains) != 2 {
t.Fatal("chains not properly appended")
}
if len(got.Hops) != 2 {
t.Fatal("hops not properly appended")
}
if len(got.Authers) != 2 {
t.Fatal("authers not properly appended")
}
if len(got.Admissions) != 2 {
t.Fatal("admissions not properly appended")
}
if len(got.Bypasses) != 2 {
t.Fatal("bypasses not properly appended")
}
if len(got.Resolvers) != 2 {
t.Fatal("resolvers not properly appended")
}
if len(got.Hosts) != 2 {
t.Fatal("hosts not properly appended")
}
if len(got.Ingresses) != 2 {
t.Fatal("ingresses not properly appended")
}
if len(got.SDs) != 2 {
t.Fatal("SDs not properly appended")
}
if len(got.Recorders) != 2 {
t.Fatal("recorders not properly appended")
}
if len(got.Limiters) != 2 {
t.Fatal("limiters not properly appended")
}
if len(got.CLimiters) != 2 {
t.Fatal("climiters not properly appended")
}
if len(got.RLimiters) != 2 {
t.Fatal("rlimiters not properly appended")
}
if len(got.Loggers) != 2 {
t.Fatal("loggers not properly appended")
}
if len(got.Routers) != 2 {
t.Fatal("routers not properly appended")
}
if len(got.Observers) != 2 {
t.Fatal("observers not properly appended")
}
}
func TestMergeConfig_ScalarOverrides(t *testing.T) {
cfg1 := &config.Config{
TLS: &config.TLSConfig{ServerName: "old"},
Log: &config.LogConfig{Level: "info"},
API: &config.APIConfig{Addr: ":8080"},
Metrics: &config.MetricsConfig{Addr: ":9090"},
Profiling: &config.ProfilingConfig{Addr: ":6060"},
}
cfg2 := &config.Config{
TLS: &config.TLSConfig{ServerName: "new"},
Log: &config.LogConfig{Level: "debug"},
API: &config.APIConfig{Addr: ":8081"},
Metrics: &config.MetricsConfig{Addr: ":9091"},
Profiling: &config.ProfilingConfig{Addr: ":6061"},
}
got := mergeConfig(cfg1, cfg2)
if got.TLS.ServerName != "new" {
t.Fatalf("TLS not overridden: got %q, want %q", got.TLS.ServerName, "new")
}
if got.Log.Level != "debug" {
t.Fatalf("Log not overridden: got %q, want %q", got.Log.Level, "debug")
}
if got.API.Addr != ":8081" {
t.Fatalf("API not overridden: got %q, want %q", got.API.Addr, ":8081")
}
if got.Metrics.Addr != ":9091" {
t.Fatalf("Metrics not overridden: got %q, want %q", got.Metrics.Addr, ":9091")
}
if got.Profiling.Addr != ":6061" {
t.Fatalf("Profiling not overridden: got %q, want %q", got.Profiling.Addr, ":6061")
}
}
func TestMergeConfig_ScalarKeepsCfg1WhenCfg2Nil(t *testing.T) {
cfg1 := &config.Config{
TLS: &config.TLSConfig{ServerName: "keep"},
Log: &config.LogConfig{Level: "warn"},
API: &config.APIConfig{Addr: ":8080"},
Metrics: &config.MetricsConfig{Addr: ":9090"},
Profiling: &config.ProfilingConfig{Addr: ":6060"},
}
cfg2 := &config.Config{} // all scalars nil
got := mergeConfig(cfg1, cfg2)
if got.TLS.ServerName != "keep" {
t.Fatal("TLS should be kept from cfg1")
}
if got.Log.Level != "warn" {
t.Fatal("Log should be kept from cfg1")
}
if got.API.Addr != ":8080" {
t.Fatal("API should be kept from cfg1")
}
if got.Metrics.Addr != ":9090" {
t.Fatal("Metrics should be kept from cfg1")
}
if got.Profiling.Addr != ":6060" {
t.Fatal("Profiling should be kept from cfg1")
}
}
func TestInit(t *testing.T) {
Init(Args{
CfgFile: "test.yml",
Services: []string{"s1"},
Nodes: []string{"n1"},
Debug: true,
Trace: false,
ApiAddr: ":8080",
MetricsAddr: ":9090",
})
if defaultParser.args.CfgFile != "test.yml" {
t.Fatalf("CfgFile not set: got %q", defaultParser.args.CfgFile)
}
if len(defaultParser.args.Services) != 1 || defaultParser.args.Services[0] != "s1" {
t.Fatal("Services not set")
}
if len(defaultParser.args.Nodes) != 1 || defaultParser.args.Nodes[0] != "n1" {
t.Fatal("Nodes not set")
}
if !defaultParser.args.Debug {
t.Fatal("Debug not set")
}
if defaultParser.args.Trace {
t.Fatal("Trace should be false")
}
if defaultParser.args.ApiAddr != ":8080" {
t.Fatal("ApiAddr not set")
}
if defaultParser.args.MetricsAddr != ":9090" {
t.Fatal("MetricsAddr not set")
}
}
+199
View File
@@ -0,0 +1,199 @@
package recorder
import (
"io"
"testing"
"github.com/go-gost/core/logger"
"github.com/go-gost/x/config"
xlogger "github.com/go-gost/x/logger"
)
func TestMain(m *testing.M) {
logger.SetDefault(xlogger.NewLogger(xlogger.OutputOption(io.Discard)))
m.Run()
}
func TestParseRecorder_Nil(t *testing.T) {
r := ParseRecorder(nil)
if r != nil {
t.Fatal("expected nil for nil config")
}
}
func TestParseRecorder_PluginHTTP(t *testing.T) {
r := ParseRecorder(&config.RecorderConfig{
Name: "http-rec",
Plugin: &config.PluginConfig{
Type: "http",
Addr: "127.0.0.1:9000",
},
})
if r == nil {
t.Fatal("expected non-nil recorder")
}
}
func TestParseRecorder_PluginGRPC(t *testing.T) {
r := ParseRecorder(&config.RecorderConfig{
Name: "grpc-rec",
Plugin: &config.PluginConfig{
Type: "grpc",
Addr: "127.0.0.1:9001",
Token: "secret",
TLS: &config.TLSConfig{
Secure: true,
ServerName: "recorder.local",
},
},
})
if r == nil {
t.Fatal("expected non-nil recorder")
}
}
func TestParseRecorder_PluginDefaultType(t *testing.T) {
r := ParseRecorder(&config.RecorderConfig{
Name: "default-rec",
Plugin: &config.PluginConfig{
Addr: "127.0.0.1:9002",
},
})
if r == nil {
t.Fatal("expected non-nil recorder")
}
}
func TestParseRecorder_TCP(t *testing.T) {
r := ParseRecorder(&config.RecorderConfig{
Name: "tcp-rec",
TCP: &config.TCPRecorder{
Addr: "127.0.0.1:9999",
Timeout: 0,
},
})
if r == nil {
t.Fatal("expected non-nil recorder for TCP")
}
}
func TestParseRecorder_HTTP(t *testing.T) {
r := ParseRecorder(&config.RecorderConfig{
Name: "http-rec",
HTTP: &config.HTTPRecorder{
URL: "http://localhost:8080/record",
Timeout: 0,
Header: map[string]string{"X-Test": "value"},
},
})
if r == nil {
t.Fatal("expected non-nil recorder for HTTP")
}
}
func TestParseRecorder_File(t *testing.T) {
r := ParseRecorder(&config.RecorderConfig{
Name: "file-rec",
File: &config.FileRecorder{
Path: "/tmp/recorder.log",
Sep: "\n",
},
})
if r == nil {
t.Fatal("expected non-nil recorder for file")
}
}
func TestParseRecorder_FileWithRotation(t *testing.T) {
r := ParseRecorder(&config.RecorderConfig{
Name: "file-rot-rec",
File: &config.FileRecorder{
Path: "/tmp/recorder-rot.log",
Rotation: &config.LogRotationConfig{
MaxSize: 100,
MaxAge: 30,
MaxBackups: 10,
LocalTime: true,
Compress: true,
},
},
})
if r == nil {
t.Fatal("expected non-nil recorder for file with rotation")
}
}
func TestParseRecorder_RedisSet(t *testing.T) {
r := ParseRecorder(&config.RecorderConfig{
Name: "redis-set-rec",
Redis: &config.RedisRecorder{
Addr: "127.0.0.1:6379",
Key: "recorder-set",
Type: "set",
},
})
if r == nil {
t.Fatal("expected non-nil recorder for redis set")
}
}
func TestParseRecorder_RedisList(t *testing.T) {
r := ParseRecorder(&config.RecorderConfig{
Name: "redis-list-rec",
Redis: &config.RedisRecorder{
Addr: "127.0.0.1:6379",
Key: "recorder-list",
Type: "list",
},
})
if r == nil {
t.Fatal("expected non-nil recorder for redis list")
}
}
func TestParseRecorder_RedisSortedSet(t *testing.T) {
r := ParseRecorder(&config.RecorderConfig{
Name: "redis-sset-rec",
Redis: &config.RedisRecorder{
Addr: "127.0.0.1:6379",
Key: "recorder-sset",
Type: "sset",
},
})
if r == nil {
t.Fatal("expected non-nil recorder for redis sorted set")
}
}
func TestParseRecorder_EmptyFileReturnsNil(t *testing.T) {
r := ParseRecorder(&config.RecorderConfig{
Name: "empty-rec",
})
if r != nil {
t.Fatal("expected nil recorder when no sub-config is provided")
}
}
func TestParseRecorder_FileWithEmptyPathReturnsNil(t *testing.T) {
r := ParseRecorder(&config.RecorderConfig{
Name: "empty-path-rec",
File: &config.FileRecorder{
Path: "",
},
})
if r != nil {
t.Fatal("expected nil recorder when file path is empty")
}
}
func TestParseRecorder_TCPWithEmptyAddrReturnsNil(t *testing.T) {
r := ParseRecorder(&config.RecorderConfig{
Name: "empty-tcp-rec",
TCP: &config.TCPRecorder{
Addr: "",
},
})
if r != nil {
t.Fatal("expected nil recorder when TCP addr is empty")
}
}
+146
View File
@@ -0,0 +1,146 @@
package resolver
import (
"io"
"testing"
"github.com/go-gost/core/logger"
"github.com/go-gost/x/config"
xlogger "github.com/go-gost/x/logger"
)
func TestMain(m *testing.M) {
logger.SetDefault(xlogger.NewLogger(xlogger.OutputOption(io.Discard)))
m.Run()
}
func TestParseResolver_Nil(t *testing.T) {
r, err := ParseResolver(nil)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if r != nil {
t.Fatal("expected nil for nil config")
}
}
func TestParseResolver_PluginHTTP(t *testing.T) {
r, err := ParseResolver(&config.ResolverConfig{
Name: "http-res",
Plugin: &config.PluginConfig{
Type: "http",
Addr: "127.0.0.1:9000",
},
})
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if r == nil {
t.Fatal("expected non-nil resolver")
}
}
func TestParseResolver_PluginGRPC(t *testing.T) {
r, err := ParseResolver(&config.ResolverConfig{
Name: "grpc-res",
Plugin: &config.PluginConfig{
Type: "grpc",
Addr: "127.0.0.1:9001",
Token: "secret",
TLS: &config.TLSConfig{
Secure: true,
ServerName: "resolver.local",
},
},
})
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if r == nil {
t.Fatal("expected non-nil resolver")
}
}
func TestParseResolver_PluginDefaultType(t *testing.T) {
r, err := ParseResolver(&config.ResolverConfig{
Name: "default-res",
Plugin: &config.PluginConfig{
Addr: "127.0.0.1:9002",
},
})
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if r == nil {
t.Fatal("expected non-nil resolver")
}
}
func TestParseResolver_WithNameservers(t *testing.T) {
r, err := ParseResolver(&config.ResolverConfig{
Name: "ns-res",
Nameservers: []*config.NameserverConfig{
{Addr: "8.8.8.8", Prefer: "ipv4"},
{Addr: "8.8.4.4", Prefer: "ipv4"},
},
})
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if r == nil {
t.Fatal("expected non-nil resolver")
}
}
func TestParseResolver_EmptyNameservers(t *testing.T) {
r, err := ParseResolver(&config.ResolverConfig{
Name: "empty-ns",
Nameservers: []*config.NameserverConfig{},
})
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if r == nil {
t.Fatal("expected non-nil resolver even with empty nameservers")
}
}
func TestParseResolver_NameserverWithClientIP(t *testing.T) {
r, err := ParseResolver(&config.ResolverConfig{
Name: "clientip-res",
Nameservers: []*config.NameserverConfig{
{Addr: "8.8.8.8", ClientIP: "1.2.3.4"},
},
})
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if r == nil {
t.Fatal("expected non-nil resolver")
}
}
func TestParseResolver_NameserverWithAllFields(t *testing.T) {
r, err := ParseResolver(&config.ResolverConfig{
Name: "full-res",
Nameservers: []*config.NameserverConfig{
{
Addr: "8.8.8.8:53",
Chain: "chain1",
Prefer: "ipv4",
ClientIP: "1.2.3.4",
Hostname: "dns.example.com",
TTL: 300,
Timeout: 5,
Async: true,
Only: "A",
},
},
})
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if r == nil {
t.Fatal("expected non-nil resolver")
}
}
+237
View File
@@ -0,0 +1,237 @@
package router
import (
"context"
"io"
"testing"
"github.com/go-gost/core/logger"
"github.com/go-gost/x/config"
xlogger "github.com/go-gost/x/logger"
)
func TestMain(m *testing.M) {
logger.SetDefault(xlogger.NewLogger(xlogger.OutputOption(io.Discard)))
m.Run()
}
func TestParseRouter_Nil(t *testing.T) {
r := ParseRouter(nil)
if r != nil {
t.Fatal("expected nil for nil config")
}
}
func TestParseRouter_PluginHTTP(t *testing.T) {
r := ParseRouter(&config.RouterConfig{
Name: "http-rt",
Plugin: &config.PluginConfig{
Type: "http",
Addr: "127.0.0.1:9000",
},
})
if r == nil {
t.Fatal("expected non-nil router")
}
}
func TestParseRouter_PluginGRPC(t *testing.T) {
r := ParseRouter(&config.RouterConfig{
Name: "grpc-rt",
Plugin: &config.PluginConfig{
Type: "grpc",
Addr: "127.0.0.1:9001",
Token: "secret",
TLS: &config.TLSConfig{
Secure: true,
ServerName: "router.local",
},
},
})
if r == nil {
t.Fatal("expected non-nil router")
}
}
func TestParseRouter_PluginDefaultType(t *testing.T) {
r := ParseRouter(&config.RouterConfig{
Name: "default-rt",
Plugin: &config.PluginConfig{
Addr: "127.0.0.1:9002",
},
})
if r == nil {
t.Fatal("expected non-nil router")
}
}
func TestParseRouter_WithCIDRRoutes(t *testing.T) {
r := ParseRouter(&config.RouterConfig{
Name: "cidr-rt",
Routes: []*config.RouterRouteConfig{
{Net: "192.168.1.0/24", Gateway: "10.0.0.1"},
{Net: "10.0.0.0/8", Gateway: "10.0.0.2"},
},
})
if r == nil {
t.Fatal("expected non-nil router with CIDR routes")
}
}
func TestParseRouter_WithDstRoutes(t *testing.T) {
r := ParseRouter(&config.RouterConfig{
Name: "dst-rt",
Routes: []*config.RouterRouteConfig{
{Dst: "192.168.1.0/24", Gateway: "10.0.0.1"},
},
})
if r == nil {
t.Fatal("expected non-nil router with dst routes")
}
}
func TestParseRouter_MissingGatewaySkipped(t *testing.T) {
r := ParseRouter(&config.RouterConfig{
Name: "skip-rt",
Routes: []*config.RouterRouteConfig{
{Net: "192.168.1.0/24", Gateway: ""}, // skipped: no gateway
{Net: "10.0.0.0/8", Gateway: "10.0.0.1"}, // kept
},
})
if r == nil {
t.Fatal("expected non-nil router (with one valid route)")
}
}
func TestParseRouter_MissingDstSkipped(t *testing.T) {
r := ParseRouter(&config.RouterConfig{
Name: "no-dst-rt",
Routes: []*config.RouterRouteConfig{
{Dst: "", Gateway: "10.0.0.1"}, // skipped: no dst
},
})
if r == nil {
t.Fatal("expected non-nil router (all routes skipped)")
}
}
func TestParseRouter_InvalidCIDRSkipped(t *testing.T) {
r := ParseRouter(&config.RouterConfig{
Name: "invalid-cidr-rt",
Routes: []*config.RouterRouteConfig{
{Net: "not-a-cidr", Gateway: "10.0.0.1"}, // invalid CIDR with empty Dst, skipped
{Net: "192.168.1.0/24", Gateway: "10.0.0.2"}, // valid
},
})
if r == nil {
t.Fatal("expected non-nil router")
}
}
func TestParseRouter_NilRouteSkipped(t *testing.T) {
r := ParseRouter(&config.RouterConfig{
Name: "nil-route-rt",
Routes: []*config.RouterRouteConfig{
nil,
{Net: "192.168.1.0/24", Gateway: "10.0.0.1"},
},
})
if r == nil {
t.Fatal("expected non-nil router when nil route is in list")
}
}
func TestParseRouter_EmptyRoutes(t *testing.T) {
r := ParseRouter(&config.RouterConfig{
Name: "empty-routes",
Routes: []*config.RouterRouteConfig{},
})
if r == nil {
t.Fatal("expected non-nil router with empty routes")
}
}
func TestParseRouter_FileLoader(t *testing.T) {
r := ParseRouter(&config.RouterConfig{
Name: "file-rt",
File: &config.FileLoader{Path: "/tmp/routes.txt"},
})
if r == nil {
t.Fatal("expected non-nil router")
}
}
func TestParseRouter_HTTPLoader(t *testing.T) {
r := ParseRouter(&config.RouterConfig{
Name: "http-rt",
HTTP: &config.HTTPLoader{URL: "http://localhost:8080/routes"},
})
if r == nil {
t.Fatal("expected non-nil router")
}
}
func TestParseRouter_RedisHashLoader(t *testing.T) {
r := ParseRouter(&config.RouterConfig{
Name: "redis-hash-rt",
Redis: &config.RedisLoader{
Addr: "127.0.0.1:6379",
Key: "routes-hash",
Type: "hash",
},
})
if r == nil {
t.Fatal("expected non-nil router")
}
}
func TestParseRouter_RedisListLoader(t *testing.T) {
r := ParseRouter(&config.RouterConfig{
Name: "redis-list-rt",
Redis: &config.RedisLoader{
Addr: "127.0.0.1:6379",
Key: "routes-list",
Type: "list",
},
})
if r == nil {
t.Fatal("expected non-nil router")
}
}
func TestParseRouter_RedisSetLoader(t *testing.T) {
r := ParseRouter(&config.RouterConfig{
Name: "redis-set-rt",
Redis: &config.RedisLoader{
Addr: "127.0.0.1:6379",
Key: "routes-set",
Type: "set",
},
})
if r == nil {
t.Fatal("expected non-nil router")
}
}
func TestParseRouter_DstSyncedFromNet(t *testing.T) {
// When Dst is empty but Net is provided, Net is converted to Dst
r := ParseRouter(&config.RouterConfig{
Name: "net-to-dst-rt",
Routes: []*config.RouterRouteConfig{
{Net: "192.168.1.0/24", Gateway: "10.0.0.1"},
},
})
if r == nil {
t.Fatal("expected non-nil router")
}
route := r.GetRoute(context.Background(), "192.168.1.5")
if route == nil {
t.Fatal("expected non-nil route for IP in CIDR range")
}
if route.Dst != "192.168.1.0/24" {
t.Fatalf("Dst = %q, want %q", route.Dst, "192.168.1.0/24")
}
if route.Gateway != "10.0.0.1" {
t.Fatalf("Gateway = %q, want %q", route.Gateway, "10.0.0.1")
}
}
+75
View File
@@ -0,0 +1,75 @@
package sd
import (
"io"
"testing"
"github.com/go-gost/core/logger"
"github.com/go-gost/x/config"
xlogger "github.com/go-gost/x/logger"
)
func TestMain(m *testing.M) {
logger.SetDefault(xlogger.NewLogger(xlogger.OutputOption(io.Discard)))
m.Run()
}
func TestParseSD_Nil(t *testing.T) {
s := ParseSD(nil)
if s != nil {
t.Fatal("expected nil for nil config")
}
}
func TestParseSD_NilPlugin(t *testing.T) {
s := ParseSD(&config.SDConfig{
Name: "no-plugin",
Plugin: nil,
})
if s != nil {
t.Fatal("expected nil when plugin is nil")
}
}
func TestParseSD_PluginHTTP(t *testing.T) {
s := ParseSD(&config.SDConfig{
Name: "http-sd",
Plugin: &config.PluginConfig{
Type: "http",
Addr: "127.0.0.1:9000",
},
})
if s == nil {
t.Fatal("expected non-nil SD")
}
}
func TestParseSD_PluginGRPC(t *testing.T) {
s := ParseSD(&config.SDConfig{
Name: "grpc-sd",
Plugin: &config.PluginConfig{
Type: "grpc",
Addr: "127.0.0.1:9001",
Token: "secret",
TLS: &config.TLSConfig{
Secure: true,
ServerName: "sd.local",
},
},
})
if s == nil {
t.Fatal("expected non-nil SD")
}
}
func TestParseSD_PluginDefaultType(t *testing.T) {
s := ParseSD(&config.SDConfig{
Name: "default-sd",
Plugin: &config.PluginConfig{
Addr: "127.0.0.1:9002",
},
})
if s == nil {
t.Fatal("expected non-nil SD")
}
}
+144
View File
@@ -0,0 +1,144 @@
package selector
import (
"testing"
"github.com/go-gost/x/config"
)
func TestParseChainSelector_Nil(t *testing.T) {
sel := ParseChainSelector(nil)
if sel != nil {
t.Fatal("expected nil for nil config")
}
}
func TestParseChainSelector_RoundRobin(t *testing.T) {
sel := ParseChainSelector(&config.SelectorConfig{Strategy: "round"})
if sel == nil {
t.Fatal("expected non-nil selector")
}
}
func TestParseChainSelector_RR(t *testing.T) {
sel := ParseChainSelector(&config.SelectorConfig{Strategy: "rr"})
if sel == nil {
t.Fatal("expected non-nil selector for rr strategy")
}
}
func TestParseChainSelector_Random(t *testing.T) {
sel := ParseChainSelector(&config.SelectorConfig{Strategy: "random"})
if sel == nil {
t.Fatal("expected non-nil selector")
}
}
func TestParseChainSelector_Rand(t *testing.T) {
sel := ParseChainSelector(&config.SelectorConfig{Strategy: "rand"})
if sel == nil {
t.Fatal("expected non-nil selector for rand strategy")
}
}
func TestParseChainSelector_FIFO(t *testing.T) {
sel := ParseChainSelector(&config.SelectorConfig{Strategy: "fifo"})
if sel == nil {
t.Fatal("expected non-nil selector")
}
}
func TestParseChainSelector_HA(t *testing.T) {
sel := ParseChainSelector(&config.SelectorConfig{Strategy: "ha"})
if sel == nil {
t.Fatal("expected non-nil selector for ha strategy")
}
}
func TestParseChainSelector_Hash(t *testing.T) {
sel := ParseChainSelector(&config.SelectorConfig{Strategy: "hash"})
if sel == nil {
t.Fatal("expected non-nil selector")
}
}
func TestParseChainSelector_DefaultStrategy(t *testing.T) {
sel := ParseChainSelector(&config.SelectorConfig{Strategy: "unknown"})
if sel == nil {
t.Fatal("expected non-nil selector for unknown strategy (should default to round-robin)")
}
}
func TestParseChainSelector_WithMaxFails(t *testing.T) {
sel := ParseChainSelector(&config.SelectorConfig{
Strategy: "round",
MaxFails: 3,
FailTimeout: 0,
})
if sel == nil {
t.Fatal("expected non-nil selector")
}
}
func TestParseNodeSelector_Nil(t *testing.T) {
sel := ParseNodeSelector(nil)
if sel != nil {
t.Fatal("expected nil for nil config")
}
}
func TestParseNodeSelector_RoundRobin(t *testing.T) {
sel := ParseNodeSelector(&config.SelectorConfig{Strategy: "round"})
if sel == nil {
t.Fatal("expected non-nil selector")
}
}
func TestParseNodeSelector_Random(t *testing.T) {
sel := ParseNodeSelector(&config.SelectorConfig{Strategy: "random"})
if sel == nil {
t.Fatal("expected non-nil selector")
}
}
func TestParseNodeSelector_FIFO(t *testing.T) {
sel := ParseNodeSelector(&config.SelectorConfig{Strategy: "fifo"})
if sel == nil {
t.Fatal("expected non-nil selector")
}
}
func TestParseNodeSelector_Hash(t *testing.T) {
sel := ParseNodeSelector(&config.SelectorConfig{Strategy: "hash"})
if sel == nil {
t.Fatal("expected non-nil selector")
}
}
func TestParseNodeSelector_Parallel(t *testing.T) {
sel := ParseNodeSelector(&config.SelectorConfig{Strategy: "parallel"})
if sel == nil {
t.Fatal("expected non-nil selector for parallel strategy")
}
}
func TestParseNodeSelector_DefaultStrategy(t *testing.T) {
sel := ParseNodeSelector(&config.SelectorConfig{Strategy: "unknown"})
if sel == nil {
t.Fatal("expected non-nil selector for unknown strategy (should default to round-robin)")
}
}
func TestDefaultNodeSelector(t *testing.T) {
sel := DefaultNodeSelector()
if sel == nil {
t.Fatal("expected non-nil default node selector")
}
}
func TestDefaultChainSelector(t *testing.T) {
sel := DefaultChainSelector()
if sel == nil {
t.Fatal("expected non-nil default chain selector")
}
}
+103
View File
@@ -0,0 +1,103 @@
package parsing
import (
"bytes"
"crypto/tls"
"io"
"testing"
"github.com/go-gost/core/logger"
"github.com/go-gost/x/config"
xlogger "github.com/go-gost/x/logger"
)
func TestMain(m *testing.M) {
logger.SetDefault(xlogger.NewLogger(xlogger.OutputOption(io.Discard)))
m.Run()
}
func TestSetDefaultTLSConfig(t *testing.T) {
// Save and restore original
orig := DefaultTLSConfig()
defer SetDefaultTLSConfig(orig)
cfg := &tls.Config{ServerName: "test.local"}
SetDefaultTLSConfig(cfg)
got := DefaultTLSConfig()
if got == nil {
t.Fatal("expected non-nil default TLS config")
}
if got.ServerName != "test.local" {
t.Fatalf("ServerName = %q, want %q", got.ServerName, "test.local")
}
}
func TestDefaultTLSConfig_InitiallyNil(t *testing.T) {
orig := DefaultTLSConfig()
defer SetDefaultTLSConfig(orig)
// Store nil to reset
SetDefaultTLSConfig(nil)
got := DefaultTLSConfig()
if got != nil {
t.Fatal("expected nil after storing nil")
}
}
func TestBuildDefaultTLSConfig_Nil(t *testing.T) {
// BuildDefaultTLSConfig with nil should generate a self-signed cert
tlsCfg, err := BuildDefaultTLSConfig(nil)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if tlsCfg == nil {
t.Fatal("expected non-nil config")
}
if len(tlsCfg.Certificates) == 0 {
t.Fatal("expected at least one certificate")
}
}
func TestBuildDefaultTLSConfig_WithOptions(t *testing.T) {
cfg := &config.TLSConfig{
Validity: 0, // uses default
Organization: "TestOrg",
CommonName: "test.example.com",
}
tlsCfg, err := BuildDefaultTLSConfig(cfg)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if tlsCfg == nil {
t.Fatal("expected non-nil config")
}
if len(tlsCfg.Certificates) == 0 {
t.Fatal("expected at least one certificate")
}
}
func TestBuildDefaultTLSConfig_TwoCallsDifferentCerts(t *testing.T) {
cfg1, err := BuildDefaultTLSConfig(nil)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
cfg2, err := BuildDefaultTLSConfig(nil)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if len(cfg1.Certificates) == 0 || len(cfg2.Certificates) == 0 {
t.Fatal("expected certificates in both configs")
}
// Each call generates a random cert; they should have different raw bytes.
if len(cfg1.Certificates[0].Certificate) == 0 || len(cfg2.Certificates[0].Certificate) == 0 {
t.Fatal("expected non-empty certificate raw bytes")
}
c1 := cfg1.Certificates[0].Certificate[0]
c2 := cfg2.Certificates[0].Certificate[0]
if bytes.Equal(c1, c2) {
t.Fatal("expected different certificates from two calls")
}
}