diff --git a/internal/api/config_handlers_sanitize_test.go b/internal/api/config_handlers_sanitize_test.go index f851e94..e23efc2 100644 --- a/internal/api/config_handlers_sanitize_test.go +++ b/internal/api/config_handlers_sanitize_test.go @@ -1,8 +1,11 @@ package api import ( + "errors" "strings" "testing" + + "github.com/rcourtman/pulse-go-rewrite/internal/config" ) func TestSanitizeInstallerURL(t *testing.T) { @@ -330,3 +333,175 @@ func TestSanitizeSetupAuthToken(t *testing.T) { }) } } + +func TestSanitizeErrorMessage(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + err error + operation string + expected string + }{ + { + name: "create_client operation", + err: errors.New("connection refused"), + operation: "create_client", + expected: "Failed to initialize connection", + }, + { + name: "connection operation", + err: errors.New("network unreachable"), + operation: "connection", + expected: "Connection failed. Please check your credentials and network settings", + }, + { + name: "validation operation", + err: errors.New("invalid field"), + operation: "validation", + expected: "Invalid configuration", + }, + { + name: "unknown operation", + err: errors.New("something went wrong"), + operation: "unknown_operation", + expected: "Operation failed", + }, + { + name: "empty operation string", + err: errors.New("error"), + operation: "", + expected: "Operation failed", + }, + { + name: "nil error still returns message", + err: nil, + operation: "create_client", + expected: "Failed to initialize connection", + }, + { + name: "detailed error hidden from response", + err: errors.New("x509: certificate signed by unknown authority"), + operation: "connection", + expected: "Connection failed. Please check your credentials and network settings", + }, + { + name: "sensitive error hidden from response", + err: errors.New("password: secret123"), + operation: "validation", + expected: "Invalid configuration", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + result := sanitizeErrorMessage(tt.err, tt.operation) + if result != tt.expected { + t.Errorf("sanitizeErrorMessage() = %q, want %q", result, tt.expected) + } + }) + } +} + +func TestFindExistingGuestURL(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + nodeName string + endpoints []config.ClusterEndpoint + expected string + }{ + { + name: "empty endpoints", + nodeName: "node1", + endpoints: []config.ClusterEndpoint{}, + expected: "", + }, + { + name: "nil endpoints", + nodeName: "node1", + endpoints: nil, + expected: "", + }, + { + name: "exact match", + nodeName: "pve1", + endpoints: []config.ClusterEndpoint{ + {NodeName: "pve1", GuestURL: "https://pve1.local:8006"}, + {NodeName: "pve2", GuestURL: "https://pve2.local:8006"}, + }, + expected: "https://pve1.local:8006", + }, + { + name: "match second node", + nodeName: "pve2", + endpoints: []config.ClusterEndpoint{ + {NodeName: "pve1", GuestURL: "https://pve1.local:8006"}, + {NodeName: "pve2", GuestURL: "https://pve2.local:8006"}, + }, + expected: "https://pve2.local:8006", + }, + { + name: "no match", + nodeName: "pve3", + endpoints: []config.ClusterEndpoint{ + {NodeName: "pve1", GuestURL: "https://pve1.local:8006"}, + {NodeName: "pve2", GuestURL: "https://pve2.local:8006"}, + }, + expected: "", + }, + { + name: "empty node name", + nodeName: "", + endpoints: []config.ClusterEndpoint{ + {NodeName: "pve1", GuestURL: "https://pve1.local:8006"}, + }, + expected: "", + }, + { + name: "case sensitive match", + nodeName: "PVE1", + endpoints: []config.ClusterEndpoint{ + {NodeName: "pve1", GuestURL: "https://pve1.local:8006"}, + }, + expected: "", + }, + { + name: "returns first match when duplicates exist", + nodeName: "pve1", + endpoints: []config.ClusterEndpoint{ + {NodeName: "pve1", GuestURL: "https://first.local:8006"}, + {NodeName: "pve1", GuestURL: "https://second.local:8006"}, + }, + expected: "https://first.local:8006", + }, + { + name: "empty GuestURL returned when matched", + nodeName: "pve1", + endpoints: []config.ClusterEndpoint{ + {NodeName: "pve1", GuestURL: ""}, + }, + expected: "", + }, + { + name: "single endpoint match", + nodeName: "pve1", + endpoints: []config.ClusterEndpoint{ + {NodeName: "pve1", GuestURL: "https://pve1.local:8006"}, + }, + expected: "https://pve1.local:8006", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + result := findExistingGuestURL(tt.nodeName, tt.endpoints) + if result != tt.expected { + t.Errorf("findExistingGuestURL(%q, endpoints) = %q, want %q", tt.nodeName, result, tt.expected) + } + }) + } +} diff --git a/internal/api/diagnostics_test.go b/internal/api/diagnostics_test.go index bfa4cfa..9cd5392 100644 --- a/internal/api/diagnostics_test.go +++ b/internal/api/diagnostics_test.go @@ -5,6 +5,7 @@ import ( "time" "github.com/rcourtman/pulse-go-rewrite/internal/alerts" + "github.com/rcourtman/pulse-go-rewrite/internal/config" "github.com/rcourtman/pulse-go-rewrite/internal/models" ) @@ -411,6 +412,166 @@ func TestFormatTimeMaybe(t *testing.T) { } } +func TestMatchInstanceNameByHost(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + cfg *config.Config + host string + expected string + }{ + { + name: "nil config", + cfg: nil, + host: "example.com", + expected: "", + }, + { + name: "empty host", + cfg: &config.Config{ + PVEInstances: []config.PVEInstance{ + {Name: "pve1", Host: "pve1.local"}, + }, + }, + host: "", + expected: "", + }, + { + name: "whitespace-only host", + cfg: &config.Config{ + PVEInstances: []config.PVEInstance{ + {Name: "pve1", Host: "pve1.local"}, + }, + }, + host: " ", + expected: "", + }, + { + name: "exact match", + cfg: &config.Config{ + PVEInstances: []config.PVEInstance{ + {Name: "pve1", Host: "pve1.local"}, + {Name: "pve2", Host: "pve2.local"}, + }, + }, + host: "pve1.local", + expected: "pve1", + }, + { + name: "case insensitive match", + cfg: &config.Config{ + PVEInstances: []config.PVEInstance{ + {Name: "Production PVE", Host: "PVE.EXAMPLE.COM"}, + }, + }, + host: "pve.example.com", + expected: "Production PVE", + }, + { + name: "match with port in config", + cfg: &config.Config{ + PVEInstances: []config.PVEInstance{ + {Name: "pve1", Host: "pve1.local:8006"}, + }, + }, + host: "pve1.local", + expected: "pve1", + }, + { + name: "match with protocol in config", + cfg: &config.Config{ + PVEInstances: []config.PVEInstance{ + {Name: "pve1", Host: "https://pve1.local:8006"}, + }, + }, + host: "pve1.local", + expected: "pve1", + }, + { + name: "match with protocol in search host", + cfg: &config.Config{ + PVEInstances: []config.PVEInstance{ + {Name: "pve1", Host: "pve1.local"}, + }, + }, + host: "https://pve1.local:8006", + expected: "pve1", + }, + { + name: "no match", + cfg: &config.Config{ + PVEInstances: []config.PVEInstance{ + {Name: "pve1", Host: "pve1.local"}, + {Name: "pve2", Host: "pve2.local"}, + }, + }, + host: "pve3.local", + expected: "", + }, + { + name: "empty instances", + cfg: &config.Config{ + PVEInstances: []config.PVEInstance{}, + }, + host: "pve1.local", + expected: "", + }, + { + name: "instance with empty host skipped", + cfg: &config.Config{ + PVEInstances: []config.PVEInstance{ + {Name: "empty", Host: ""}, + {Name: "pve1", Host: "pve1.local"}, + }, + }, + host: "pve1.local", + expected: "pve1", + }, + { + name: "IP address match", + cfg: &config.Config{ + PVEInstances: []config.PVEInstance{ + {Name: "pve1", Host: "192.168.1.100"}, + }, + }, + host: "192.168.1.100", + expected: "pve1", + }, + { + name: "name has leading and trailing whitespace", + cfg: &config.Config{ + PVEInstances: []config.PVEInstance{ + {Name: " pve1 ", Host: "pve1.local"}, + }, + }, + host: "pve1.local", + expected: "pve1", + }, + { + name: "returns first match when duplicates exist", + cfg: &config.Config{ + PVEInstances: []config.PVEInstance{ + {Name: "first", Host: "pve.local"}, + {Name: "second", Host: "pve.local"}, + }, + }, + host: "pve.local", + expected: "first", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + result := matchInstanceNameByHost(tt.cfg, tt.host) + if result != tt.expected { + t.Errorf("matchInstanceNameByHost() = %q, want %q", result, tt.expected) + } + }) + } +} + func TestHasLegacyThresholds(t *testing.T) { ptrFloat := func(v float64) *float64 { return &v }