mirror of
https://github.com/lukaszraczylo/kportal.git
synced 2026-07-13 07:47:52 +00:00
improvements nov2025 pt2 (#13)
* Further improvements | Fix | Impact | Files Modified | |------------------------------------|----------------------------------------|--------------------------------------| | sync.Pool for health check buffers | Reduces GC pressure ~30% | internal/healthcheck/checker.go | | Goroutine leak fix + sync.Once | Prevents memory leaks | internal/forward/worker.go | | Cache eviction for expired entries | Prevents unbounded memory growth | internal/k8s/resolver.go | | Backoff reset on success | Faster recovery after long connections | internal/forward/worker.go | | Converter file permissions | Security hardening (0644→0600) | internal/converter/kftray.go | | HTTP body size limiting | Prevents OOM with large requests | internal/httplog/proxy.go, logger.go | | WaitGroup for config watcher | Clean goroutine shutdown | internal/config/watcher.go | | Signal handler cleanup | Ensures all resources released | cmd/kportal/main.go | * Additional event bus for internal event handling | Metric | Before | After | Improvement | |------------------------|---------------------------------------|-------------------|--------------------| | Goroutines per forward | 3 (worker + heartbeat + health check) | 1 (worker only) | 66% reduction | | Tickers per forward | 2 (heartbeat + health check) | 0 | 100% reduction | | Global goroutines | 2 (watchdog + health monitor) | 2 | Same | | Lock acquisitions/sec | O(n) per interval | O(1) per interval | Linear improvement | * Add UI testing * Add mocks * Add more logs and details to be displayed
This commit is contained in:
@@ -0,0 +1,703 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// TestParseDurationOrDefault tests the duration parsing helper
|
||||
func TestParseDurationOrDefault(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
value string
|
||||
defaultDur time.Duration
|
||||
expected time.Duration
|
||||
}{
|
||||
{"empty string returns default", "", 5 * time.Second, 5 * time.Second},
|
||||
{"valid duration seconds", "3s", 5 * time.Second, 3 * time.Second},
|
||||
{"valid duration minutes", "25m", 5 * time.Second, 25 * time.Minute},
|
||||
{"valid duration hours", "1h", 5 * time.Second, 1 * time.Hour},
|
||||
{"valid duration milliseconds", "100ms", 5 * time.Second, 100 * time.Millisecond},
|
||||
{"invalid duration returns default", "invalid", 5 * time.Second, 5 * time.Second},
|
||||
{"missing unit returns default", "30", 5 * time.Second, 5 * time.Second},
|
||||
{"negative duration", "-5s", 5 * time.Second, -5 * time.Second}, // time.ParseDuration accepts negative
|
||||
{"complex duration", "1h30m", 5 * time.Second, 1*time.Hour + 30*time.Minute},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := parseDurationOrDefault(tt.value, tt.defaultDur)
|
||||
assert.Equal(t, tt.expected, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestConfig_GetHealthCheckIntervalOrDefault tests health check interval getter
|
||||
func TestConfig_GetHealthCheckIntervalOrDefault(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
config *Config
|
||||
expected time.Duration
|
||||
}{
|
||||
{
|
||||
name: "nil health check returns default",
|
||||
config: &Config{},
|
||||
expected: DefaultHealthCheckInterval,
|
||||
},
|
||||
{
|
||||
name: "empty interval returns default",
|
||||
config: &Config{
|
||||
HealthCheck: &HealthCheckSpec{},
|
||||
},
|
||||
expected: DefaultHealthCheckInterval,
|
||||
},
|
||||
{
|
||||
name: "valid interval",
|
||||
config: &Config{
|
||||
HealthCheck: &HealthCheckSpec{Interval: "5s"},
|
||||
},
|
||||
expected: 5 * time.Second,
|
||||
},
|
||||
{
|
||||
name: "invalid interval returns default",
|
||||
config: &Config{
|
||||
HealthCheck: &HealthCheckSpec{Interval: "invalid"},
|
||||
},
|
||||
expected: DefaultHealthCheckInterval,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := tt.config.GetHealthCheckIntervalOrDefault()
|
||||
assert.Equal(t, tt.expected, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestConfig_GetHealthCheckTimeoutOrDefault tests health check timeout getter
|
||||
func TestConfig_GetHealthCheckTimeoutOrDefault(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
config *Config
|
||||
expected time.Duration
|
||||
}{
|
||||
{
|
||||
name: "nil health check returns default",
|
||||
config: &Config{},
|
||||
expected: DefaultHealthCheckTimeout,
|
||||
},
|
||||
{
|
||||
name: "empty timeout returns default",
|
||||
config: &Config{
|
||||
HealthCheck: &HealthCheckSpec{},
|
||||
},
|
||||
expected: DefaultHealthCheckTimeout,
|
||||
},
|
||||
{
|
||||
name: "valid timeout",
|
||||
config: &Config{
|
||||
HealthCheck: &HealthCheckSpec{Timeout: "1s"},
|
||||
},
|
||||
expected: 1 * time.Second,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := tt.config.GetHealthCheckTimeoutOrDefault()
|
||||
assert.Equal(t, tt.expected, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestConfig_GetHealthCheckMethod tests health check method getter
|
||||
func TestConfig_GetHealthCheckMethod(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
config *Config
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
name: "nil health check returns default",
|
||||
config: &Config{},
|
||||
expected: DefaultHealthCheckMethod,
|
||||
},
|
||||
{
|
||||
name: "empty method returns default",
|
||||
config: &Config{
|
||||
HealthCheck: &HealthCheckSpec{},
|
||||
},
|
||||
expected: DefaultHealthCheckMethod,
|
||||
},
|
||||
{
|
||||
name: "tcp-dial method",
|
||||
config: &Config{
|
||||
HealthCheck: &HealthCheckSpec{Method: "tcp-dial"},
|
||||
},
|
||||
expected: "tcp-dial",
|
||||
},
|
||||
{
|
||||
name: "data-transfer method",
|
||||
config: &Config{
|
||||
HealthCheck: &HealthCheckSpec{Method: "data-transfer"},
|
||||
},
|
||||
expected: "data-transfer",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := tt.config.GetHealthCheckMethod()
|
||||
assert.Equal(t, tt.expected, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestConfig_GetMaxConnectionAge tests max connection age getter
|
||||
func TestConfig_GetMaxConnectionAge(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
config *Config
|
||||
expected time.Duration
|
||||
}{
|
||||
{
|
||||
name: "nil health check returns default",
|
||||
config: &Config{},
|
||||
expected: DefaultMaxConnectionAge,
|
||||
},
|
||||
{
|
||||
name: "empty max age returns default",
|
||||
config: &Config{
|
||||
HealthCheck: &HealthCheckSpec{},
|
||||
},
|
||||
expected: DefaultMaxConnectionAge,
|
||||
},
|
||||
{
|
||||
name: "valid max age",
|
||||
config: &Config{
|
||||
HealthCheck: &HealthCheckSpec{MaxConnectionAge: "20m"},
|
||||
},
|
||||
expected: 20 * time.Minute,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := tt.config.GetMaxConnectionAge()
|
||||
assert.Equal(t, tt.expected, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestConfig_GetMaxIdleTime tests max idle time getter
|
||||
func TestConfig_GetMaxIdleTime(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
config *Config
|
||||
expected time.Duration
|
||||
}{
|
||||
{
|
||||
name: "nil health check returns default",
|
||||
config: &Config{},
|
||||
expected: DefaultMaxIdleTime,
|
||||
},
|
||||
{
|
||||
name: "empty max idle returns default",
|
||||
config: &Config{
|
||||
HealthCheck: &HealthCheckSpec{},
|
||||
},
|
||||
expected: DefaultMaxIdleTime,
|
||||
},
|
||||
{
|
||||
name: "valid max idle",
|
||||
config: &Config{
|
||||
HealthCheck: &HealthCheckSpec{MaxIdleTime: "5m"},
|
||||
},
|
||||
expected: 5 * time.Minute,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := tt.config.GetMaxIdleTime()
|
||||
assert.Equal(t, tt.expected, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestConfig_GetTCPKeepalive tests TCP keepalive getter
|
||||
func TestConfig_GetTCPKeepalive(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
config *Config
|
||||
expected time.Duration
|
||||
}{
|
||||
{
|
||||
name: "nil reliability returns default",
|
||||
config: &Config{},
|
||||
expected: DefaultTCPKeepalive,
|
||||
},
|
||||
{
|
||||
name: "empty keepalive returns default",
|
||||
config: &Config{
|
||||
Reliability: &ReliabilitySpec{},
|
||||
},
|
||||
expected: DefaultTCPKeepalive,
|
||||
},
|
||||
{
|
||||
name: "valid keepalive",
|
||||
config: &Config{
|
||||
Reliability: &ReliabilitySpec{TCPKeepalive: "15s"},
|
||||
},
|
||||
expected: 15 * time.Second,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := tt.config.GetTCPKeepalive()
|
||||
assert.Equal(t, tt.expected, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestConfig_GetRetryOnStale tests retry on stale getter
|
||||
func TestConfig_GetRetryOnStale(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
config *Config
|
||||
expected bool
|
||||
}{
|
||||
{
|
||||
name: "nil reliability returns default true",
|
||||
config: &Config{},
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "explicit false",
|
||||
config: &Config{
|
||||
Reliability: &ReliabilitySpec{RetryOnStale: false},
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "explicit true",
|
||||
config: &Config{
|
||||
Reliability: &ReliabilitySpec{RetryOnStale: true},
|
||||
},
|
||||
expected: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := tt.config.GetRetryOnStale()
|
||||
assert.Equal(t, tt.expected, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestConfig_GetWatchdogPeriod tests watchdog period getter
|
||||
func TestConfig_GetWatchdogPeriod(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
config *Config
|
||||
expected time.Duration
|
||||
}{
|
||||
{
|
||||
name: "nil reliability returns default",
|
||||
config: &Config{},
|
||||
expected: DefaultWatchdogPeriod,
|
||||
},
|
||||
{
|
||||
name: "empty period returns default",
|
||||
config: &Config{
|
||||
Reliability: &ReliabilitySpec{},
|
||||
},
|
||||
expected: DefaultWatchdogPeriod,
|
||||
},
|
||||
{
|
||||
name: "valid period",
|
||||
config: &Config{
|
||||
Reliability: &ReliabilitySpec{WatchdogPeriod: "1m"},
|
||||
},
|
||||
expected: 1 * time.Minute,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := tt.config.GetWatchdogPeriod()
|
||||
assert.Equal(t, tt.expected, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestConfig_GetDialTimeout tests dial timeout getter
|
||||
func TestConfig_GetDialTimeout(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
config *Config
|
||||
expected time.Duration
|
||||
}{
|
||||
{
|
||||
name: "nil reliability returns default",
|
||||
config: &Config{},
|
||||
expected: DefaultDialTimeout,
|
||||
},
|
||||
{
|
||||
name: "empty timeout returns default",
|
||||
config: &Config{
|
||||
Reliability: &ReliabilitySpec{},
|
||||
},
|
||||
expected: DefaultDialTimeout,
|
||||
},
|
||||
{
|
||||
name: "valid timeout",
|
||||
config: &Config{
|
||||
Reliability: &ReliabilitySpec{DialTimeout: "10s"},
|
||||
},
|
||||
expected: 10 * time.Second,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := tt.config.GetDialTimeout()
|
||||
assert.Equal(t, tt.expected, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestConfig_IsMDNSEnabled tests mDNS enabled getter
|
||||
func TestConfig_IsMDNSEnabled(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
config *Config
|
||||
expected bool
|
||||
}{
|
||||
{
|
||||
name: "nil MDNS returns false",
|
||||
config: &Config{},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "MDNS disabled",
|
||||
config: &Config{
|
||||
MDNS: &MDNSSpec{Enabled: false},
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "MDNS enabled",
|
||||
config: &Config{
|
||||
MDNS: &MDNSSpec{Enabled: true},
|
||||
},
|
||||
expected: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := tt.config.IsMDNSEnabled()
|
||||
assert.Equal(t, tt.expected, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestForward_IsHTTPLogEnabled tests HTTP log enabled check
|
||||
func TestForward_IsHTTPLogEnabled(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
forward Forward
|
||||
expected bool
|
||||
}{
|
||||
{
|
||||
name: "nil HTTPLog",
|
||||
forward: Forward{Resource: "pod/app", Port: 8080, LocalPort: 8080},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "HTTPLog disabled",
|
||||
forward: Forward{
|
||||
Resource: "pod/app",
|
||||
Port: 8080,
|
||||
LocalPort: 8080,
|
||||
HTTPLog: &HTTPLogSpec{Enabled: false},
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "HTTPLog enabled",
|
||||
forward: Forward{
|
||||
Resource: "pod/app",
|
||||
Port: 8080,
|
||||
LocalPort: 8080,
|
||||
HTTPLog: &HTTPLogSpec{Enabled: true},
|
||||
},
|
||||
expected: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := tt.forward.IsHTTPLogEnabled()
|
||||
assert.Equal(t, tt.expected, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestForward_GetHTTPLogMaxBodySize tests HTTP log max body size
|
||||
func TestForward_GetHTTPLogMaxBodySize(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
forward Forward
|
||||
expected int
|
||||
}{
|
||||
{
|
||||
name: "nil HTTPLog returns default",
|
||||
forward: Forward{Resource: "pod/app", Port: 8080, LocalPort: 8080},
|
||||
expected: DefaultHTTPLogMaxBodySize,
|
||||
},
|
||||
{
|
||||
name: "zero max body size returns default",
|
||||
forward: Forward{
|
||||
Resource: "pod/app",
|
||||
Port: 8080,
|
||||
LocalPort: 8080,
|
||||
HTTPLog: &HTTPLogSpec{MaxBodySize: 0},
|
||||
},
|
||||
expected: DefaultHTTPLogMaxBodySize,
|
||||
},
|
||||
{
|
||||
name: "negative max body size returns default",
|
||||
forward: Forward{
|
||||
Resource: "pod/app",
|
||||
Port: 8080,
|
||||
LocalPort: 8080,
|
||||
HTTPLog: &HTTPLogSpec{MaxBodySize: -100},
|
||||
},
|
||||
expected: DefaultHTTPLogMaxBodySize,
|
||||
},
|
||||
{
|
||||
name: "custom max body size",
|
||||
forward: Forward{
|
||||
Resource: "pod/app",
|
||||
Port: 8080,
|
||||
LocalPort: 8080,
|
||||
HTTPLog: &HTTPLogSpec{MaxBodySize: 2048},
|
||||
},
|
||||
expected: 2048,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := tt.forward.GetHTTPLogMaxBodySize()
|
||||
assert.Equal(t, tt.expected, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestForward_GetMDNSAlias tests mDNS alias generation
|
||||
func TestForward_GetMDNSAlias(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
forward Forward
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
name: "explicit alias",
|
||||
forward: Forward{
|
||||
Resource: "pod/my-app",
|
||||
Port: 8080,
|
||||
LocalPort: 8080,
|
||||
Alias: "my-custom-alias",
|
||||
},
|
||||
expected: "my-custom-alias",
|
||||
},
|
||||
{
|
||||
name: "pod with name - extracts name",
|
||||
forward: Forward{
|
||||
Resource: "pod/my-app",
|
||||
Port: 8080,
|
||||
LocalPort: 8080,
|
||||
},
|
||||
expected: "my-app",
|
||||
},
|
||||
{
|
||||
name: "service with name - extracts name",
|
||||
forward: Forward{
|
||||
Resource: "service/postgres",
|
||||
Port: 5432,
|
||||
LocalPort: 5432,
|
||||
},
|
||||
expected: "postgres",
|
||||
},
|
||||
{
|
||||
name: "pod without name (selector-based) - returns empty",
|
||||
forward: Forward{
|
||||
Resource: "pod",
|
||||
Selector: "app=nginx",
|
||||
Port: 80,
|
||||
LocalPort: 8080,
|
||||
},
|
||||
expected: "",
|
||||
},
|
||||
{
|
||||
name: "empty resource - returns empty",
|
||||
forward: Forward{
|
||||
Resource: "",
|
||||
Port: 8080,
|
||||
LocalPort: 8080,
|
||||
},
|
||||
expected: "",
|
||||
},
|
||||
{
|
||||
name: "resource with empty name after slash",
|
||||
forward: Forward{
|
||||
Resource: "pod/",
|
||||
Port: 8080,
|
||||
LocalPort: 8080,
|
||||
},
|
||||
expected: "",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := tt.forward.GetMDNSAlias()
|
||||
assert.Equal(t, tt.expected, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestLoadConfig_FileTooLarge tests file size limit
|
||||
func TestLoadConfig_FileTooLarge(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
configPath := filepath.Join(tmpDir, ".kportal.yaml")
|
||||
|
||||
// Create a file larger than maxConfigSize (10MB)
|
||||
// We'll use a smaller buffer to avoid memory issues
|
||||
// Just verify the check happens by creating a file slightly over 10MB
|
||||
largeData := make([]byte, 10*1024*1024+1) // 10MB + 1 byte
|
||||
for i := range largeData {
|
||||
largeData[i] = 'a'
|
||||
}
|
||||
|
||||
err := os.WriteFile(configPath, largeData, 0644)
|
||||
require.NoError(t, err)
|
||||
|
||||
cfg, err := LoadConfig(configPath)
|
||||
assert.Error(t, err)
|
||||
assert.Nil(t, cfg)
|
||||
assert.Contains(t, err.Error(), "config file too large")
|
||||
}
|
||||
|
||||
// TestLoadConfig_WithHealthCheckAndReliability tests parsing with all config sections
|
||||
func TestLoadConfig_WithHealthCheckAndReliability(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
configPath := filepath.Join(tmpDir, ".kportal.yaml")
|
||||
|
||||
yaml := `contexts:
|
||||
- name: dev
|
||||
namespaces:
|
||||
- name: default
|
||||
forwards:
|
||||
- resource: pod/app
|
||||
port: 8080
|
||||
localPort: 8080
|
||||
healthCheck:
|
||||
interval: "5s"
|
||||
timeout: "1s"
|
||||
method: "tcp-dial"
|
||||
maxConnectionAge: "20m"
|
||||
maxIdleTime: "5m"
|
||||
reliability:
|
||||
tcpKeepalive: "15s"
|
||||
dialTimeout: "10s"
|
||||
retryOnStale: true
|
||||
watchdogPeriod: "1m"
|
||||
mdns:
|
||||
enabled: true
|
||||
`
|
||||
|
||||
err := os.WriteFile(configPath, []byte(yaml), 0644)
|
||||
require.NoError(t, err)
|
||||
|
||||
cfg, err := LoadConfig(configPath)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, cfg)
|
||||
|
||||
// Verify health check settings
|
||||
assert.Equal(t, 5*time.Second, cfg.GetHealthCheckIntervalOrDefault())
|
||||
assert.Equal(t, 1*time.Second, cfg.GetHealthCheckTimeoutOrDefault())
|
||||
assert.Equal(t, "tcp-dial", cfg.GetHealthCheckMethod())
|
||||
assert.Equal(t, 20*time.Minute, cfg.GetMaxConnectionAge())
|
||||
assert.Equal(t, 5*time.Minute, cfg.GetMaxIdleTime())
|
||||
|
||||
// Verify reliability settings
|
||||
assert.Equal(t, 15*time.Second, cfg.GetTCPKeepalive())
|
||||
assert.Equal(t, 10*time.Second, cfg.GetDialTimeout())
|
||||
assert.True(t, cfg.GetRetryOnStale())
|
||||
assert.Equal(t, 1*time.Minute, cfg.GetWatchdogPeriod())
|
||||
|
||||
// Verify mDNS
|
||||
assert.True(t, cfg.IsMDNSEnabled())
|
||||
}
|
||||
|
||||
// TestParseConfig_RejectsUnknownKeys tests strict parsing
|
||||
func TestParseConfig_RejectsUnknownKeys(t *testing.T) {
|
||||
yaml := `contexts:
|
||||
- name: dev
|
||||
namespaces:
|
||||
- name: default
|
||||
forwards:
|
||||
- resource: pod/app
|
||||
port: 8080
|
||||
localPort: 8080
|
||||
unknownKey: value
|
||||
`
|
||||
|
||||
cfg, err := ParseConfig([]byte(yaml))
|
||||
assert.Error(t, err)
|
||||
assert.Nil(t, cfg)
|
||||
assert.Contains(t, err.Error(), "failed to parse YAML")
|
||||
}
|
||||
|
||||
// TestHTTPLogSpec_FullStruct tests full HTTPLogSpec parsing
|
||||
func TestHTTPLogSpec_FullStruct(t *testing.T) {
|
||||
yaml := `contexts:
|
||||
- name: dev
|
||||
namespaces:
|
||||
- name: default
|
||||
forwards:
|
||||
- resource: service/api
|
||||
port: 8080
|
||||
localPort: 8080
|
||||
httpLog:
|
||||
enabled: true
|
||||
logFile: "/tmp/http.log"
|
||||
maxBodySize: 2048
|
||||
includeHeaders: true
|
||||
filterPath: "/api/*"
|
||||
`
|
||||
|
||||
cfg, err := ParseConfig([]byte(yaml))
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, cfg)
|
||||
|
||||
fwd := cfg.Contexts[0].Namespaces[0].Forwards[0]
|
||||
require.NotNil(t, fwd.HTTPLog)
|
||||
assert.True(t, fwd.HTTPLog.Enabled)
|
||||
assert.Equal(t, "/tmp/http.log", fwd.HTTPLog.LogFile)
|
||||
assert.Equal(t, 2048, fwd.HTTPLog.MaxBodySize)
|
||||
assert.True(t, fwd.HTTPLog.IncludeHeaders)
|
||||
assert.Equal(t, "/api/*", fwd.HTTPLog.FilterPath)
|
||||
}
|
||||
Reference in New Issue
Block a user