Pulse/internal/models/profile_validation_test.go
rcourtman 8c7581d32c feat(profiles): add AI-assisted profile suggestions
Add ability for users to describe what kind of agent profile they need
in natural language, and have AI generate a suggestion with name,
description, config values, and rationale.

- Add ProfileSuggestionHandler with schema-aware prompting
- Add SuggestProfileModal component with example prompts
- Update AgentProfilesPanel with suggest button and description field
- Streamline ValidConfigKeys to only agent-supported settings
- Update profile validation tests for simplified schema
2026-01-15 13:24:18 +00:00

409 lines
9.7 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: "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",
},
}
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"])
}
}