WitNote / internal /config /validate_test.go
AUXteam's picture
Upload folder using huggingface_hub
6a7089a verified
package config
import (
"strings"
"testing"
)
func TestValidateFileConfig_Valid(t *testing.T) {
maxTabs := 20
fc := &FileConfig{
Server: ServerConfig{
Port: "9867",
Bind: "127.0.0.1",
},
InstanceDefaults: InstanceDefaultsConfig{
Mode: "headless",
MaxTabs: &maxTabs,
StealthLevel: "light",
TabEvictionPolicy: "reject",
},
MultiInstance: MultiInstanceConfig{
Strategy: "simple",
AllocationPolicy: "fcfs",
Restart: MultiInstanceRestartConfig{
MaxRestarts: intPtr(20),
InitBackoffSec: intPtr(2),
MaxBackoffSec: intPtr(60),
StableAfterSec: intPtr(300),
},
},
Timeouts: TimeoutsConfig{
ActionSec: 30,
NavigateSec: 60,
},
}
errs := ValidateFileConfig(fc)
if len(errs) > 0 {
t.Errorf("expected no errors for valid config, got: %v", errs)
}
}
func TestValidateFileConfig_RestartPolicy(t *testing.T) {
tests := []struct {
name string
restart MultiInstanceRestartConfig
wantErr bool
}{
{
name: "bounded",
restart: MultiInstanceRestartConfig{
MaxRestarts: intPtr(10),
InitBackoffSec: intPtr(2),
MaxBackoffSec: intPtr(60),
StableAfterSec: intPtr(300),
},
wantErr: false,
},
{
name: "unlimited",
restart: MultiInstanceRestartConfig{
MaxRestarts: intPtr(-1),
InitBackoffSec: intPtr(2),
MaxBackoffSec: intPtr(60),
StableAfterSec: intPtr(300),
},
wantErr: false,
},
{
name: "zero max restarts valid (no restarts)",
restart: MultiInstanceRestartConfig{
MaxRestarts: intPtr(0),
},
wantErr: false,
},
{
name: "negative max restarts invalid (except -1)",
restart: MultiInstanceRestartConfig{
MaxRestarts: intPtr(-2),
},
wantErr: true,
},
{
name: "max backoff lower than init invalid",
restart: MultiInstanceRestartConfig{
InitBackoffSec: intPtr(10),
MaxBackoffSec: intPtr(5),
},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
fc := &FileConfig{
MultiInstance: MultiInstanceConfig{Restart: tt.restart},
}
errs := ValidateFileConfig(fc)
hasErr := len(errs) > 0
if hasErr != tt.wantErr {
t.Fatalf("got error=%v, want %v (errs: %v)", hasErr, tt.wantErr, errs)
}
})
}
}
func intPtr(v int) *int { return &v }
func TestValidateFileConfig_InvalidPort(t *testing.T) {
tests := []struct {
port string
wantErr bool
}{
{"9867", false},
{"1", false},
{"65535", false},
{"0", true},
{"65536", true},
{"-1", true},
{"abc", true},
{"", false}, // empty is ok (uses default)
}
for _, tt := range tests {
fc := &FileConfig{
Server: ServerConfig{Port: tt.port},
}
errs := ValidateFileConfig(fc)
hasErr := len(errs) > 0
if hasErr != tt.wantErr {
t.Errorf("port=%q: got error=%v, want error=%v (errs: %v)", tt.port, hasErr, tt.wantErr, errs)
}
}
}
func TestValidateFileConfig_InvalidStealthLevel(t *testing.T) {
tests := []struct {
level string
wantErr bool
}{
{"light", false},
{"full", false},
{"", false}, // empty is ok
{"medium", true}, // removed, no longer valid
{"none", true},
{"max", true},
{"LIGHT", true}, // case sensitive
}
for _, tt := range tests {
fc := &FileConfig{
InstanceDefaults: InstanceDefaultsConfig{StealthLevel: tt.level},
}
errs := ValidateFileConfig(fc)
hasErr := len(errs) > 0
if hasErr != tt.wantErr {
t.Errorf("stealthLevel=%q: got error=%v, want error=%v", tt.level, hasErr, tt.wantErr)
}
}
}
func TestValidateFileConfig_InvalidEvictionPolicy(t *testing.T) {
tests := []struct {
policy string
wantErr bool
}{
{"reject", false},
{"close_oldest", false},
{"close_lru", false},
{"", false},
{"drop", true},
{"lru", true},
}
for _, tt := range tests {
fc := &FileConfig{
InstanceDefaults: InstanceDefaultsConfig{TabEvictionPolicy: tt.policy},
}
errs := ValidateFileConfig(fc)
hasErr := len(errs) > 0
if hasErr != tt.wantErr {
t.Errorf("tabEvictionPolicy=%q: got error=%v, want error=%v", tt.policy, hasErr, tt.wantErr)
}
}
}
func TestValidateFileConfig_InvalidStrategy(t *testing.T) {
tests := []struct {
strategy string
wantErr bool
}{
{"simple", false},
{"explicit", false},
{"simple-autorestart", false},
{"always-on", false},
{"", false},
{"auto", true},
{"default", true},
}
for _, tt := range tests {
fc := &FileConfig{
MultiInstance: MultiInstanceConfig{Strategy: tt.strategy},
}
errs := ValidateFileConfig(fc)
hasErr := len(errs) > 0
if hasErr != tt.wantErr {
t.Errorf("strategy=%q: got error=%v, want error=%v", tt.strategy, hasErr, tt.wantErr)
}
}
}
func TestValidateFileConfig_InvalidAllocationPolicy(t *testing.T) {
tests := []struct {
policy string
wantErr bool
}{
{"fcfs", false},
{"round_robin", false},
{"random", false},
{"", false},
{"fifo", true},
{"roundrobin", true}, // underscore required
}
for _, tt := range tests {
fc := &FileConfig{
MultiInstance: MultiInstanceConfig{AllocationPolicy: tt.policy},
}
errs := ValidateFileConfig(fc)
hasErr := len(errs) > 0
if hasErr != tt.wantErr {
t.Errorf("allocationPolicy=%q: got error=%v, want error=%v", tt.policy, hasErr, tt.wantErr)
}
}
}
func TestValidateFileConfig_InvalidAttachScheme(t *testing.T) {
tests := []struct {
schemes []string
wantErr bool
}{
{[]string{"ws"}, false},
{[]string{"wss"}, false},
{[]string{"ws", "wss"}, false},
{[]string{"http"}, false},
{[]string{"https"}, false},
{[]string{"ws", "https"}, false},
{[]string{"ftp"}, true},
{[]string{"ws", "tcp"}, true},
}
for _, tt := range tests {
fc := &FileConfig{
Security: SecurityConfig{
Attach: AttachConfig{AllowSchemes: tt.schemes},
},
}
errs := ValidateFileConfig(fc)
hasErr := len(errs) > 0
if hasErr != tt.wantErr {
t.Errorf("allowSchemes=%v: got error=%v, want error=%v", tt.schemes, hasErr, tt.wantErr)
}
}
}
func TestValidateFileConfig_InvalidMaxTabs(t *testing.T) {
zero := 0
negative := -1
positive := 10
tests := []struct {
name string
maxTabs *int
wantErr bool
}{
{"nil", nil, false},
{"positive", &positive, false},
{"zero", &zero, true},
{"negative", &negative, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
fc := &FileConfig{
InstanceDefaults: InstanceDefaultsConfig{MaxTabs: tt.maxTabs},
}
errs := ValidateFileConfig(fc)
hasErr := len(errs) > 0
if hasErr != tt.wantErr {
t.Errorf("maxTabs=%v: got error=%v, want error=%v", tt.maxTabs, hasErr, tt.wantErr)
}
})
}
}
func TestValidateFileConfig_InvalidTimeouts(t *testing.T) {
fc := &FileConfig{
Timeouts: TimeoutsConfig{
ActionSec: -1,
NavigateSec: -1,
ShutdownSec: -1,
WaitNavMs: -1,
},
}
errs := ValidateFileConfig(fc)
if len(errs) != 4 {
t.Errorf("expected 4 timeout errors, got %d: %v", len(errs), errs)
}
}
func TestValidateFileConfig_InstancePortRange(t *testing.T) {
start := 9900
end := 9800 // invalid: start > end
fc := &FileConfig{
Server: ServerConfig{},
MultiInstance: MultiInstanceConfig{
InstancePortStart: &start,
InstancePortEnd: &end,
},
}
errs := ValidateFileConfig(fc)
if len(errs) != 1 {
t.Errorf("expected 1 error for invalid port range, got %d: %v", len(errs), errs)
}
if len(errs) > 0 && !strings.Contains(errs[0].Error(), "start port") {
t.Errorf("expected port range error, got: %v", errs[0])
}
}
func TestValidateFileConfig_MultipleErrors(t *testing.T) {
zero := 0
fc := &FileConfig{
Server: ServerConfig{
Port: "99999", // invalid
},
InstanceDefaults: InstanceDefaultsConfig{
MaxTabs: &zero, // invalid
StealthLevel: "superstealth", // invalid
TabEvictionPolicy: "delete_oldest", // invalid
},
MultiInstance: MultiInstanceConfig{
Strategy: "magical", // invalid
AllocationPolicy: "balanced", // invalid
},
}
errs := ValidateFileConfig(fc)
if len(errs) < 5 {
t.Errorf("expected at least 5 errors, got %d: %v", len(errs), errs)
}
}
func TestValidationError_Error(t *testing.T) {
err := ValidationError{
Field: "server.port",
Message: "port out of range",
}
expected := "server.port: port out of range"
if err.Error() != expected {
t.Errorf("got %q, want %q", err.Error(), expected)
}
}
func TestValidEnumValues(t *testing.T) {
// Test that the valid values match the validation functions
for _, level := range ValidStealthLevels() {
if !isValidStealthLevel(level) {
t.Errorf("ValidStealthLevels contains %q but isValidStealthLevel returns false", level)
}
}
for _, policy := range ValidEvictionPolicies() {
if !isValidEvictionPolicy(policy) {
t.Errorf("ValidEvictionPolicies contains %q but isValidEvictionPolicy returns false", policy)
}
}
for _, strategy := range ValidStrategies() {
if !isValidStrategy(strategy) {
t.Errorf("ValidStrategies contains %q but isValidStrategy returns false", strategy)
}
}
for _, policy := range ValidAllocationPolicies() {
if !isValidAllocationPolicy(policy) {
t.Errorf("ValidAllocationPolicies contains %q but isValidAllocationPolicy returns false", policy)
}
}
for _, scheme := range ValidAttachSchemes() {
if !isValidAttachScheme(scheme) {
t.Errorf("ValidAttachSchemes contains %q but isValidAttachScheme returns false", scheme)
}
}
}
// --- IDPI validation tests ---
// TestValidateIDPIConfig_Disabled verifies that a disabled IDPI config produces
// no errors regardless of what fields are set.
func TestValidateIDPIConfig_Disabled(t *testing.T) {
errs := validateIDPIConfig(IDPIConfig{
Enabled: false,
AllowedDomains: []string{"", " ", "file:///etc/passwd"},
CustomPatterns: []string{"", " "},
})
if len(errs) != 0 {
t.Errorf("expected no errors when IDPI disabled, got: %v", errs)
}
}
// TestValidateIDPIConfig_ValidConfig verifies that a well-formed enabled config
// produces no errors.
func TestValidateIDPIConfig_ValidConfig(t *testing.T) {
errs := validateIDPIConfig(IDPIConfig{
Enabled: true,
AllowedDomains: []string{"example.com", "*.example.com", "*"},
CustomPatterns: []string{"exfiltrate this", "data leak"},
})
if len(errs) != 0 {
t.Errorf("expected no errors for valid IDPI config, got: %v", errs)
}
}
// TestValidateIDPIConfig_EmptyDomain verifies that an empty or whitespace-only
// domain pattern is rejected.
func TestValidateIDPIConfig_EmptyDomain(t *testing.T) {
cases := []struct {
name string
domain string
}{
{"empty string", ""},
{"spaces only", " "},
{"tab only", "\t"},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
errs := validateIDPIConfig(IDPIConfig{
Enabled: true,
AllowedDomains: []string{tc.domain},
})
if len(errs) == 0 {
t.Errorf("expected error for empty domain %q, got none", tc.domain)
}
if len(errs) > 0 && !strings.Contains(errs[0].Error(), "security.idpi.allowedDomains") {
t.Errorf("expected field name in error, got: %v", errs[0])
}
})
}
}
// TestValidateIDPIConfig_DomainWithInternalWhitespace verifies that a domain
// pattern containing internal spaces is rejected.
func TestValidateIDPIConfig_DomainWithInternalWhitespace(t *testing.T) {
errs := validateIDPIConfig(IDPIConfig{
Enabled: true,
AllowedDomains: []string{"example .com"},
})
if len(errs) == 0 {
t.Error("expected error for domain with internal whitespace, got none")
}
}
// TestValidateIDPIConfig_FileSchemeBlocked verifies that file:// domain patterns
// are rejected because they cannot represent a valid network host.
func TestValidateIDPIConfig_FileSchemeBlocked(t *testing.T) {
for _, pattern := range []string{
"file:///etc/passwd",
"file://localhost/etc/passwd",
} {
errs := validateIDPIConfig(IDPIConfig{
Enabled: true,
AllowedDomains: []string{pattern},
})
if len(errs) == 0 {
t.Errorf("expected error for file:// pattern %q, got none", pattern)
}
if len(errs) > 0 && !strings.Contains(errs[0].Error(), "file://") {
t.Errorf("expected file:// mention in error, got: %v", errs[0])
}
}
}
// TestValidateIDPIConfig_EmptyCustomPattern verifies that an empty or
// whitespace-only custom pattern is rejected.
func TestValidateIDPIConfig_EmptyCustomPattern(t *testing.T) {
cases := []string{"", " ", "\t"}
for _, p := range cases {
errs := validateIDPIConfig(IDPIConfig{
Enabled: true,
CustomPatterns: []string{p},
})
if len(errs) == 0 {
t.Errorf("expected error for empty custom pattern %q, got none", p)
}
if len(errs) > 0 && !strings.Contains(errs[0].Error(), "customPatterns") {
t.Errorf("expected customPatterns field in error, got: %v", errs[0])
}
}
}
// TestValidateIDPIConfig_MultipleErrors ensures all IDPI violations are
// accumulated rather than short-circuited.
func TestValidateIDPIConfig_MultipleErrors(t *testing.T) {
errs := validateIDPIConfig(IDPIConfig{
Enabled: true,
AllowedDomains: []string{"", "file:///bad"},
CustomPatterns: []string{"", " "},
})
if len(errs) < 4 {
t.Errorf("expected at least 4 IDPI errors, got %d: %v", len(errs), errs)
}
}
// TestValidateFileConfig_IDPIPassthrough verifies that ValidateFileConfig
// surfaces IDPI errors alongside other config errors.
func TestValidateFileConfig_IDPIPassthrough(t *testing.T) {
fc := &FileConfig{
Security: SecurityConfig{
IDPI: IDPIConfig{
Enabled: true,
AllowedDomains: []string{""}, // invalid
CustomPatterns: []string{" "}, // invalid
},
},
}
errs := ValidateFileConfig(fc)
if len(errs) < 2 {
t.Errorf("expected at least 2 IDPI errors via ValidateFileConfig, got %d: %v", len(errs), errs)
}
}
// TestValidateIDPIConfig_ScanTimeoutSec verifies that negative values are rejected
// and zero/positive values are accepted (zero means use the default).
func TestValidateIDPIConfig_ScanTimeoutSec(t *testing.T) {
t.Run("NegativeIsInvalid", func(t *testing.T) {
errs := validateIDPIConfig(IDPIConfig{
Enabled: true,
ScanTimeoutSec: -1,
})
if len(errs) == 0 {
t.Error("expected error for negative scanTimeoutSec, got none")
}
if len(errs) > 0 && !strings.Contains(errs[0].Error(), "scanTimeoutSec") {
t.Errorf("expected scanTimeoutSec field in error, got: %v", errs[0])
}
})
t.Run("ZeroIsValid", func(t *testing.T) {
errs := validateIDPIConfig(IDPIConfig{
Enabled: true,
ScanTimeoutSec: 0, // zero → use built-in default of 5s
})
if len(errs) != 0 {
t.Errorf("expected no error for scanTimeoutSec=0, got: %v", errs)
}
})
t.Run("PositiveIsValid", func(t *testing.T) {
errs := validateIDPIConfig(IDPIConfig{
Enabled: true,
ScanTimeoutSec: 10,
})
if len(errs) != 0 {
t.Errorf("expected no error for scanTimeoutSec=10, got: %v", errs)
}
})
}