Pulse/internal/models/profile_validation_test.go
2026-03-18 16:06:30 +00:00

532 lines
13 KiB
Go

package models
import (
"testing"
)
func TestProfileValidator_ValidateStringType(t *testing.T) {
validator := NewProfileValidator()
tests := []struct {
name string
config AgentConfigMap
wantErr bool
errKey string
}{
{
name: "valid string",
config: AgentConfigMap{"report_ip": "192.168.1.100"},
wantErr: false,
},
{
name: "valid empty string",
config: AgentConfigMap{"report_ip": ""},
wantErr: false,
},
{
name: "invalid string type",
config: AgentConfigMap{"report_ip": 123},
wantErr: true,
errKey: "report_ip",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := validator.Validate(tt.config)
if tt.wantErr && result.Valid {
t.Errorf("expected validation to fail, but it passed")
}
if !tt.wantErr && !result.Valid {
t.Errorf("expected validation to pass, but it failed: %v", result.Errors)
}
if tt.wantErr && len(result.Errors) > 0 {
found := false
for _, err := range result.Errors {
if err.Key == tt.errKey {
found = true
break
}
}
if !found {
t.Errorf("expected error for key %s, but got: %v", tt.errKey, result.Errors)
}
}
})
}
}
func TestProfileValidator_ValidateBoolType(t *testing.T) {
validator := NewProfileValidator()
tests := []struct {
name string
config AgentConfigMap
wantErr bool
errKey string
}{
{
name: "valid bool true",
config: AgentConfigMap{"enable_docker": true},
wantErr: false,
},
{
name: "valid bool false",
config: AgentConfigMap{"enable_docker": false},
wantErr: false,
},
{
name: "invalid bool type - string",
config: AgentConfigMap{"enable_docker": "true"},
wantErr: true,
errKey: "enable_docker",
},
{
name: "invalid bool type - int",
config: AgentConfigMap{"enable_docker": 1},
wantErr: true,
errKey: "enable_docker",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := validator.Validate(tt.config)
if tt.wantErr && result.Valid {
t.Errorf("expected validation to fail, but it passed")
}
if !tt.wantErr && !result.Valid {
t.Errorf("expected validation to pass, but it failed: %v", result.Errors)
}
})
}
}
// Note: Int type validation tests removed - no schema keys currently use ConfigTypeInt.
// The validation logic exists in validateValue() and can be tested if int keys are added.
// Note: Float type validation tests removed - no schema keys currently use ConfigTypeFloat.
// The validation logic exists in validateValue() and can be tested if float keys are added.
func TestProfileValidator_ValidateDurationType(t *testing.T) {
validator := NewProfileValidator()
tests := []struct {
name string
config AgentConfigMap
wantErr bool
errKey string
}{
{
name: "valid duration seconds",
config: AgentConfigMap{"interval": "30s"},
wantErr: false,
},
{
name: "valid duration minutes",
config: AgentConfigMap{"interval": "5m"},
wantErr: false,
},
{
name: "valid duration hours",
config: AgentConfigMap{"interval": "1h"},
wantErr: false,
},
{
name: "valid duration complex",
config: AgentConfigMap{"interval": "1h30m45s"},
wantErr: false,
},
{
name: "valid duration with surrounding whitespace",
config: AgentConfigMap{"interval": " 30s "},
wantErr: false,
},
{
name: "invalid duration format",
config: AgentConfigMap{"interval": "30"},
wantErr: true,
errKey: "interval",
},
{
name: "invalid duration - not a string",
config: AgentConfigMap{"interval": 30},
wantErr: true,
errKey: "interval",
},
{
name: "invalid duration - empty after trim",
config: AgentConfigMap{"interval": " "},
wantErr: true,
errKey: "interval",
},
{
name: "invalid duration - zero value",
config: AgentConfigMap{"interval": "0s"},
wantErr: true,
errKey: "interval",
},
{
name: "invalid duration - negative value",
config: AgentConfigMap{"interval": "-5s"},
wantErr: true,
errKey: "interval",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := validator.Validate(tt.config)
if tt.wantErr && result.Valid {
t.Errorf("expected validation to fail, but it passed")
}
if !tt.wantErr && !result.Valid {
t.Errorf("expected validation to pass, but it failed: %v", result.Errors)
}
})
}
}
func TestProfileValidator_ValidateEnumType(t *testing.T) {
validator := NewProfileValidator()
tests := []struct {
name string
config AgentConfigMap
wantErr bool
errKey string
}{
{
name: "valid enum debug",
config: AgentConfigMap{"log_level": "debug"},
wantErr: false,
},
{
name: "valid enum info",
config: AgentConfigMap{"log_level": "info"},
wantErr: false,
},
{
name: "valid enum warn",
config: AgentConfigMap{"log_level": "warn"},
wantErr: false,
},
{
name: "valid enum error",
config: AgentConfigMap{"log_level": "error"},
wantErr: false,
},
{
name: "invalid enum value",
config: AgentConfigMap{"log_level": "trace"},
wantErr: true,
errKey: "log_level",
},
{
name: "invalid enum type",
config: AgentConfigMap{"log_level": 1},
wantErr: true,
errKey: "log_level",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := validator.Validate(tt.config)
if tt.wantErr && result.Valid {
t.Errorf("expected validation to fail, but it passed")
}
if !tt.wantErr && !result.Valid {
t.Errorf("expected validation to pass, but it failed: %v", result.Errors)
}
})
}
}
func TestProfileValidator_UnknownKeysWarning(t *testing.T) {
validator := NewProfileValidator()
config := AgentConfigMap{
"interval": "30s",
"unknown_key": "some_value",
}
result := validator.Validate(config)
// Should still be valid (warnings don't fail validation)
if !result.Valid {
t.Errorf("expected validation to pass with warnings, but it failed: %v", result.Errors)
}
// Should have a warning for unknown key
if len(result.Warnings) == 0 {
t.Errorf("expected a warning for unknown key, but got none")
}
found := false
for _, w := range result.Warnings {
if w.Key == "unknown_key" {
found = true
break
}
}
if !found {
t.Errorf("expected warning for 'unknown_key', got: %v", result.Warnings)
}
}
func TestProfileValidator_ComplexConfig(t *testing.T) {
validator := NewProfileValidator()
config := AgentConfigMap{
"interval": "30s",
"enable_host": true,
"enable_docker": true,
"enable_kubernetes": false,
"enable_proxmox": true,
"proxmox_type": "pve",
"docker_runtime": "auto",
"log_level": "info",
"disable_auto_update": false,
"disable_docker_update_checks": false,
"report_ip": "192.168.1.100",
}
result := validator.Validate(config)
if !result.Valid {
t.Errorf("expected complex config to be valid, but got errors: %v", result.Errors)
}
}
func TestProfileValidator_NullValue(t *testing.T) {
validator := NewProfileValidator()
config := AgentConfigMap{
"interval": nil,
}
result := validator.Validate(config)
// Null values should be valid for non-required keys
if !result.Valid {
t.Errorf("expected null value to be valid for non-required key, but got errors: %v", result.Errors)
}
}
func TestGetConfigKeyDefinitions(t *testing.T) {
defs := GetConfigKeyDefinitions()
if len(defs) == 0 {
t.Error("expected config key definitions to be non-empty")
}
// Check some known keys exist (keys actually applied by the agent)
expectedKeys := []string{"interval", "enable_docker", "log_level", "enable_host", "docker_runtime"}
for _, key := range expectedKeys {
found := false
for _, def := range defs {
if def.Key == key {
found = true
break
}
}
if !found {
t.Errorf("expected key %s to be in definitions", key)
}
}
}
func TestGetConfigKeyDefinition(t *testing.T) {
def, found := GetConfigKeyDefinition("interval")
if !found {
t.Error("expected to find 'interval' definition")
}
if def.Type != ConfigTypeDuration {
t.Errorf("expected 'interval' type to be Duration, got %s", def.Type)
}
_, found = GetConfigKeyDefinition("nonexistent_key")
if found {
t.Error("expected not to find 'nonexistent_key' definition")
}
}
func TestAgentProfile_MergedConfig(t *testing.T) {
parentProfile := AgentProfile{
ID: "parent-1",
Name: "Parent Profile",
Config: AgentConfigMap{
"interval": "30s",
"enable_docker": true,
"log_level": "info",
},
}
childProfile := AgentProfile{
ID: "child-1",
Name: "Child Profile",
ParentID: "parent-1",
Config: AgentConfigMap{
"interval": "10s", // Override parent
"log_level": "debug", // Override parent
},
}
profiles := []AgentProfile{parentProfile, childProfile}
merged := childProfile.MergedConfig(profiles)
// Child's interval should override parent's
if merged["interval"] != "10s" {
t.Errorf("expected interval to be '10s', got %v", merged["interval"])
}
// Child's log_level should override parent's
if merged["log_level"] != "debug" {
t.Errorf("expected log_level to be 'debug', got %v", merged["log_level"])
}
// Parent's enable_docker should be inherited
if merged["enable_docker"] != true {
t.Errorf("expected enable_docker to be true, got %v", merged["enable_docker"])
}
}
func TestAgentProfile_MergedConfigNoParent(t *testing.T) {
profile := AgentProfile{
ID: "profile-1",
Name: "Standalone Profile",
Config: AgentConfigMap{
"interval": "30s",
},
}
profiles := []AgentProfile{profile}
merged := profile.MergedConfig(profiles)
if merged["interval"] != "30s" {
t.Errorf("expected interval to be '30s', got %v", merged["interval"])
}
}
func TestAgentProfile_MergedConfigParentNotFound(t *testing.T) {
profile := AgentProfile{
ID: "child-1",
Name: "Child Profile",
ParentID: "nonexistent-parent",
Config: AgentConfigMap{
"interval": "10s",
},
}
profiles := []AgentProfile{profile}
// Should return just the child's config if parent not found
merged := profile.MergedConfig(profiles)
if merged["interval"] != "10s" {
t.Errorf("expected interval to be '10s', got %v", merged["interval"])
}
}
func TestAgentProfile_MergedConfigSelfCycle(t *testing.T) {
profile := AgentProfile{
ID: "profile-1",
Name: "Self Cycle",
ParentID: "profile-1",
Config: AgentConfigMap{
"interval": "15s",
"enable_host": true,
},
}
merged := profile.MergedConfig([]AgentProfile{profile})
if merged["interval"] != "15s" {
t.Errorf("expected interval to be '15s', got %v", merged["interval"])
}
if merged["enable_host"] != true {
t.Errorf("expected enable_host to be true, got %v", merged["enable_host"])
}
}
func TestAgentProfile_MergedConfigMutualCycle(t *testing.T) {
profileA := AgentProfile{
ID: "a",
Name: "Profile A",
ParentID: "b",
Config: AgentConfigMap{
"interval": "10s",
"enable_docker": true,
},
}
profileB := AgentProfile{
ID: "b",
Name: "Profile B",
ParentID: "a",
Config: AgentConfigMap{
"interval": "30s",
"log_level": "info",
},
}
merged := profileA.MergedConfig([]AgentProfile{profileA, profileB})
// Child profile should still override parent values while avoiding infinite recursion.
if merged["interval"] != "10s" {
t.Errorf("expected interval to be '10s', got %v", merged["interval"])
}
if merged["log_level"] != "info" {
t.Errorf("expected log_level to be 'info', got %v", merged["log_level"])
}
if merged["enable_docker"] != true {
t.Errorf("expected enable_docker to be true, got %v", merged["enable_docker"])
}
}
func TestAgentProfile_MergedConfigSelfParent(t *testing.T) {
profile := AgentProfile{
ID: "self",
Name: "Self Parent",
ParentID: "self",
Config: AgentConfigMap{
"interval": "45s",
},
}
merged := profile.MergedConfig([]AgentProfile{profile})
if merged["interval"] != "45s" {
t.Errorf("expected interval to be '45s', got %v", merged["interval"])
}
}
func TestAgentProfile_MergedConfigReturnsDefensiveCopy(t *testing.T) {
parent := AgentProfile{
ID: "parent",
Config: AgentConfigMap{
"enable_docker": true,
},
}
child := AgentProfile{
ID: "child",
ParentID: "parent",
Config: AgentConfigMap{
"interval": "30s",
},
}
merged := child.MergedConfig([]AgentProfile{parent, child})
merged["interval"] = "10s"
merged["enable_docker"] = false
if child.Config["interval"] != "30s" {
t.Fatalf("child config mutated through merged map: got %v", child.Config["interval"])
}
if parent.Config["enable_docker"] != true {
t.Fatalf("parent config mutated through merged map: got %v", parent.Config["enable_docker"])
}
}