orama/pkg/gateway/config_validate_test.go
2026-02-13 16:18:22 +02:00

406 lines
9.9 KiB
Go

package gateway
import (
"strings"
"testing"
)
func TestValidateListenAddr(t *testing.T) {
tests := []struct {
name string
addr string
wantErr bool
errSubstr string
}{
{"valid :8080", ":8080", false, ""},
{"valid 0.0.0.0:443", "0.0.0.0:443", false, ""},
{"valid 127.0.0.1:6001", "127.0.0.1:6001", false, ""},
{"valid :80", ":80", false, ""},
{"valid high port", ":65535", false, ""},
{"invalid no colon", "8080", true, "invalid format"},
{"invalid port zero", ":0", true, "port must be a number"},
{"invalid port too high", ":99999", true, "port must be a number"},
{"invalid non-numeric port", ":abc", true, "port must be a number"},
{"empty string", "", true, "invalid format"},
{"invalid negative port", ":-1", true, "port must be a number"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := validateListenAddr(tt.addr)
if tt.wantErr {
if err == nil {
t.Errorf("validateListenAddr(%q) = nil, want error containing %q", tt.addr, tt.errSubstr)
} else if tt.errSubstr != "" && !strings.Contains(err.Error(), tt.errSubstr) {
t.Errorf("validateListenAddr(%q) error = %q, want error containing %q", tt.addr, err.Error(), tt.errSubstr)
}
} else {
if err != nil {
t.Errorf("validateListenAddr(%q) = %v, want nil", tt.addr, err)
}
}
})
}
}
func TestValidateRQLiteDSN(t *testing.T) {
tests := []struct {
name string
dsn string
wantErr bool
errSubstr string
}{
{"valid http localhost", "http://localhost:4001", false, ""},
{"valid https", "https://db.example.com", false, ""},
{"valid http with path", "http://192.168.1.1:4001/db", false, ""},
{"valid https with port", "https://db.example.com:4001", false, ""},
{"invalid scheme ftp", "ftp://localhost", true, "scheme must be http or https"},
{"invalid scheme tcp", "tcp://localhost:4001", true, "scheme must be http or https"},
{"missing host", "http://", true, "host must not be empty"},
{"no scheme", "localhost:4001", true, "scheme must be http or https"},
{"empty string", "", true, "scheme must be http or https"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := validateRQLiteDSN(tt.dsn)
if tt.wantErr {
if err == nil {
t.Errorf("validateRQLiteDSN(%q) = nil, want error containing %q", tt.dsn, tt.errSubstr)
} else if tt.errSubstr != "" && !strings.Contains(err.Error(), tt.errSubstr) {
t.Errorf("validateRQLiteDSN(%q) error = %q, want error containing %q", tt.dsn, err.Error(), tt.errSubstr)
}
} else {
if err != nil {
t.Errorf("validateRQLiteDSN(%q) = %v, want nil", tt.dsn, err)
}
}
})
}
}
func TestIsValidDomainName(t *testing.T) {
tests := []struct {
name string
domain string
want bool
}{
{"valid example.com", "example.com", true},
{"valid sub.domain.co.uk", "sub.domain.co.uk", true},
{"valid with numbers", "host123.example.com", true},
{"valid with hyphen", "my-host.example.com", true},
{"valid uppercase", "Example.COM", true},
{"invalid starts with hyphen", "-example.com", false},
{"invalid ends with hyphen", "example.com-", false},
{"invalid starts with dot", ".example.com", false},
{"invalid ends with dot", "example.com.", false},
{"invalid special chars", "exam!ple.com", false},
{"invalid underscore", "my_host.example.com", false},
{"invalid space", "example .com", false},
{"empty string", "", false},
{"no dot", "localhost", false},
{"single char domain", "a.b", true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := isValidDomainName(tt.domain)
if got != tt.want {
t.Errorf("isValidDomainName(%q) = %v, want %v", tt.domain, got, tt.want)
}
})
}
}
func TestExtractTCPPort_Gateway(t *testing.T) {
tests := []struct {
name string
multiaddr string
want string
}{
{
"standard multiaddr",
"/ip4/127.0.0.1/tcp/4001/p2p/12D3KooWExample",
"4001",
},
{
"no tcp component",
"/ip4/127.0.0.1/udp/4001",
"",
},
{
"multiple tcp segments uses last",
"/ip4/127.0.0.1/tcp/4001/tcp/5001/p2p/12D3KooWExample",
"5001",
},
{
"tcp port at end",
"/ip4/0.0.0.0/tcp/8080",
"8080",
},
{
"empty string",
"",
"",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := extractTCPPort(tt.multiaddr)
if got != tt.want {
t.Errorf("extractTCPPort(%q) = %q, want %q", tt.multiaddr, got, tt.want)
}
})
}
}
func TestValidateConfig_Empty(t *testing.T) {
cfg := &Config{}
errs := cfg.ValidateConfig()
if len(errs) == 0 {
t.Fatal("empty config should produce validation errors")
}
// Should have errors for listen_addr and client_namespace at minimum
var foundListenAddr, foundClientNamespace bool
for _, err := range errs {
msg := err.Error()
if strings.Contains(msg, "listen_addr") {
foundListenAddr = true
}
if strings.Contains(msg, "client_namespace") {
foundClientNamespace = true
}
}
if !foundListenAddr {
t.Error("expected validation error for listen_addr, got none")
}
if !foundClientNamespace {
t.Error("expected validation error for client_namespace, got none")
}
}
func TestValidateConfig_ValidMinimal(t *testing.T) {
cfg := &Config{
ListenAddr: ":8080",
ClientNamespace: "default",
}
errs := cfg.ValidateConfig()
if len(errs) > 0 {
t.Errorf("valid minimal config should not produce errors, got: %v", errs)
}
}
func TestValidateConfig_DuplicateBootstrapPeers(t *testing.T) {
peer := "/ip4/127.0.0.1/tcp/4001/p2p/12D3KooWHbcFcrGPXKUrHcxvd8MXEeUzRYyvY8fQcpEBxncSUwhj"
cfg := &Config{
ListenAddr: ":8080",
ClientNamespace: "default",
BootstrapPeers: []string{peer, peer},
}
errs := cfg.ValidateConfig()
var foundDuplicate bool
for _, err := range errs {
if strings.Contains(err.Error(), "duplicate") {
foundDuplicate = true
break
}
}
if !foundDuplicate {
t.Error("expected duplicate bootstrap peer error, got none")
}
}
func TestValidateConfig_InvalidMultiaddr(t *testing.T) {
cfg := &Config{
ListenAddr: ":8080",
ClientNamespace: "default",
BootstrapPeers: []string{"not-a-multiaddr"},
}
errs := cfg.ValidateConfig()
if len(errs) == 0 {
t.Fatal("invalid multiaddr should produce validation error")
}
var foundInvalid bool
for _, err := range errs {
if strings.Contains(err.Error(), "invalid multiaddr") {
foundInvalid = true
break
}
}
if !foundInvalid {
t.Errorf("expected 'invalid multiaddr' error, got: %v", errs)
}
}
func TestValidateConfig_MissingP2PComponent(t *testing.T) {
cfg := &Config{
ListenAddr: ":8080",
ClientNamespace: "default",
BootstrapPeers: []string{"/ip4/127.0.0.1/tcp/4001"},
}
errs := cfg.ValidateConfig()
var foundMissingP2P bool
for _, err := range errs {
if strings.Contains(err.Error(), "missing /p2p/") {
foundMissingP2P = true
break
}
}
if !foundMissingP2P {
t.Errorf("expected 'missing /p2p/' error, got: %v", errs)
}
}
func TestValidateConfig_InvalidListenAddr(t *testing.T) {
cfg := &Config{
ListenAddr: "not-valid",
ClientNamespace: "default",
}
errs := cfg.ValidateConfig()
if len(errs) == 0 {
t.Fatal("invalid listen_addr should produce validation error")
}
var foundListenAddr bool
for _, err := range errs {
if strings.Contains(err.Error(), "listen_addr") {
foundListenAddr = true
break
}
}
if !foundListenAddr {
t.Errorf("expected listen_addr error, got: %v", errs)
}
}
func TestValidateConfig_InvalidRQLiteDSN(t *testing.T) {
cfg := &Config{
ListenAddr: ":8080",
ClientNamespace: "default",
RQLiteDSN: "ftp://invalid",
}
errs := cfg.ValidateConfig()
var foundDSN bool
for _, err := range errs {
if strings.Contains(err.Error(), "rqlite_dsn") {
foundDSN = true
break
}
}
if !foundDSN {
t.Errorf("expected rqlite_dsn error, got: %v", errs)
}
}
func TestValidateConfig_HTTPSWithoutDomain(t *testing.T) {
cfg := &Config{
ListenAddr: ":443",
ClientNamespace: "default",
EnableHTTPS: true,
}
errs := cfg.ValidateConfig()
var foundDomain bool
for _, err := range errs {
if strings.Contains(err.Error(), "domain_name") {
foundDomain = true
break
}
}
if !foundDomain {
t.Errorf("expected domain_name error when HTTPS enabled without domain, got: %v", errs)
}
}
func TestValidateConfig_HTTPSWithInvalidDomain(t *testing.T) {
cfg := &Config{
ListenAddr: ":443",
ClientNamespace: "default",
EnableHTTPS: true,
DomainName: "-invalid",
TLSCacheDir: "/tmp/tls",
}
errs := cfg.ValidateConfig()
var foundDomain bool
for _, err := range errs {
if strings.Contains(err.Error(), "domain_name") && strings.Contains(err.Error(), "invalid domain") {
foundDomain = true
break
}
}
if !foundDomain {
t.Errorf("expected invalid domain_name error, got: %v", errs)
}
}
func TestValidateConfig_HTTPSWithoutTLSCacheDir(t *testing.T) {
cfg := &Config{
ListenAddr: ":443",
ClientNamespace: "default",
EnableHTTPS: true,
DomainName: "example.com",
}
errs := cfg.ValidateConfig()
var foundTLS bool
for _, err := range errs {
if strings.Contains(err.Error(), "tls_cache_dir") {
foundTLS = true
break
}
}
if !foundTLS {
t.Errorf("expected tls_cache_dir error when HTTPS enabled without TLS cache dir, got: %v", errs)
}
}
func TestValidateConfig_ValidHTTPS(t *testing.T) {
cfg := &Config{
ListenAddr: ":443",
ClientNamespace: "default",
EnableHTTPS: true,
DomainName: "example.com",
TLSCacheDir: "/tmp/tls",
}
errs := cfg.ValidateConfig()
if len(errs) > 0 {
t.Errorf("valid HTTPS config should not produce errors, got: %v", errs)
}
}
func TestValidateConfig_EmptyRQLiteDSNSkipped(t *testing.T) {
cfg := &Config{
ListenAddr: ":8080",
ClientNamespace: "default",
RQLiteDSN: "",
}
errs := cfg.ValidateConfig()
for _, err := range errs {
if strings.Contains(err.Error(), "rqlite_dsn") {
t.Errorf("empty rqlite_dsn should not produce error, got: %v", err)
}
}
}