mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-02-18 23:41:48 +01:00
Tests for: - normalizePoweredOffSeverity: Verifies severity normalization to warning/critical - cloneMetadata: Deep copy functionality for metadata maps - cloneMetadataValue: Type-specific cloning for various slice and map types - Alert.Clone: Full alert cloning with pointer field independence 46 test cases covering nil handling, deep copy verification, slice/map independence, and all supported metadata value types. Coverage improved from 44.8% to 45.3%.
491 lines
13 KiB
Go
491 lines
13 KiB
Go
package alerts
|
|
|
|
import (
|
|
"reflect"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestNormalizePoweredOffSeverity(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
input AlertLevel
|
|
expected AlertLevel
|
|
}{
|
|
{
|
|
name: "critical lowercase",
|
|
input: AlertLevel("critical"),
|
|
expected: AlertLevelCritical,
|
|
},
|
|
{
|
|
name: "critical uppercase",
|
|
input: AlertLevel("CRITICAL"),
|
|
expected: AlertLevelCritical,
|
|
},
|
|
{
|
|
name: "critical mixed case",
|
|
input: AlertLevel("Critical"),
|
|
expected: AlertLevelCritical,
|
|
},
|
|
{
|
|
name: "warning lowercase",
|
|
input: AlertLevel("warning"),
|
|
expected: AlertLevelWarning,
|
|
},
|
|
{
|
|
name: "warning uppercase",
|
|
input: AlertLevel("WARNING"),
|
|
expected: AlertLevelWarning,
|
|
},
|
|
{
|
|
name: "empty string defaults to warning",
|
|
input: AlertLevel(""),
|
|
expected: AlertLevelWarning,
|
|
},
|
|
{
|
|
name: "unknown value defaults to warning",
|
|
input: AlertLevel("info"),
|
|
expected: AlertLevelWarning,
|
|
},
|
|
{
|
|
name: "garbage value defaults to warning",
|
|
input: AlertLevel("xyz123"),
|
|
expected: AlertLevelWarning,
|
|
},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
result := normalizePoweredOffSeverity(tc.input)
|
|
if result != tc.expected {
|
|
t.Errorf("normalizePoweredOffSeverity(%q) = %q, want %q", tc.input, result, tc.expected)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestCloneMetadata_Nil(t *testing.T) {
|
|
result := cloneMetadata(nil)
|
|
if result != nil {
|
|
t.Errorf("cloneMetadata(nil) = %v, want nil", result)
|
|
}
|
|
}
|
|
|
|
func TestCloneMetadata_Empty(t *testing.T) {
|
|
src := map[string]interface{}{}
|
|
result := cloneMetadata(src)
|
|
if result == nil {
|
|
t.Fatal("cloneMetadata({}) returned nil, want empty map")
|
|
}
|
|
if len(result) != 0 {
|
|
t.Errorf("cloneMetadata({}) length = %d, want 0", len(result))
|
|
}
|
|
}
|
|
|
|
func TestCloneMetadata_SimpleValues(t *testing.T) {
|
|
src := map[string]interface{}{
|
|
"string": "hello",
|
|
"int": 42,
|
|
"float": 3.14,
|
|
"bool": true,
|
|
"nil_val": nil,
|
|
}
|
|
|
|
result := cloneMetadata(src)
|
|
|
|
if len(result) != len(src) {
|
|
t.Errorf("cloneMetadata length = %d, want %d", len(result), len(src))
|
|
}
|
|
|
|
for k, v := range src {
|
|
if result[k] != v {
|
|
t.Errorf("cloneMetadata[%s] = %v, want %v", k, result[k], v)
|
|
}
|
|
}
|
|
|
|
// Verify it's a copy, not the same reference
|
|
src["new_key"] = "new_value"
|
|
if _, exists := result["new_key"]; exists {
|
|
t.Error("cloneMetadata should create independent copy")
|
|
}
|
|
}
|
|
|
|
func TestCloneMetadata_NestedMap(t *testing.T) {
|
|
src := map[string]interface{}{
|
|
"outer": map[string]interface{}{
|
|
"inner": "value",
|
|
"count": 10,
|
|
},
|
|
}
|
|
|
|
result := cloneMetadata(src)
|
|
|
|
// Verify structure
|
|
outer, ok := result["outer"].(map[string]interface{})
|
|
if !ok {
|
|
t.Fatal("cloneMetadata nested map not preserved")
|
|
}
|
|
if outer["inner"] != "value" {
|
|
t.Errorf("cloneMetadata nested value = %v, want 'value'", outer["inner"])
|
|
}
|
|
|
|
// Verify it's a deep copy - modify original nested map
|
|
srcOuter := src["outer"].(map[string]interface{})
|
|
srcOuter["new"] = "modified"
|
|
|
|
// Result should not be affected
|
|
if _, exists := outer["new"]; exists {
|
|
t.Error("cloneMetadata should deep copy nested maps")
|
|
}
|
|
}
|
|
|
|
func TestCloneMetadataValue_StringSlice(t *testing.T) {
|
|
src := []string{"a", "b", "c"}
|
|
result := cloneMetadataValue(src)
|
|
|
|
resultSlice, ok := result.([]string)
|
|
if !ok {
|
|
t.Fatalf("cloneMetadataValue([]string) type = %T, want []string", result)
|
|
}
|
|
|
|
if !reflect.DeepEqual(resultSlice, src) {
|
|
t.Errorf("cloneMetadataValue([]string) = %v, want %v", resultSlice, src)
|
|
}
|
|
|
|
// Verify independence
|
|
src[0] = "modified"
|
|
if resultSlice[0] == "modified" {
|
|
t.Error("cloneMetadataValue should create independent copy of []string")
|
|
}
|
|
}
|
|
|
|
func TestCloneMetadataValue_IntSlice(t *testing.T) {
|
|
src := []int{1, 2, 3}
|
|
result := cloneMetadataValue(src)
|
|
|
|
resultSlice, ok := result.([]int)
|
|
if !ok {
|
|
t.Fatalf("cloneMetadataValue([]int) type = %T, want []int", result)
|
|
}
|
|
|
|
if !reflect.DeepEqual(resultSlice, src) {
|
|
t.Errorf("cloneMetadataValue([]int) = %v, want %v", resultSlice, src)
|
|
}
|
|
|
|
// Verify independence
|
|
src[0] = 999
|
|
if resultSlice[0] == 999 {
|
|
t.Error("cloneMetadataValue should create independent copy of []int")
|
|
}
|
|
}
|
|
|
|
func TestCloneMetadataValue_FloatSlice(t *testing.T) {
|
|
src := []float64{1.1, 2.2, 3.3}
|
|
result := cloneMetadataValue(src)
|
|
|
|
resultSlice, ok := result.([]float64)
|
|
if !ok {
|
|
t.Fatalf("cloneMetadataValue([]float64) type = %T, want []float64", result)
|
|
}
|
|
|
|
if !reflect.DeepEqual(resultSlice, src) {
|
|
t.Errorf("cloneMetadataValue([]float64) = %v, want %v", resultSlice, src)
|
|
}
|
|
|
|
// Verify independence
|
|
src[0] = 999.9
|
|
if resultSlice[0] == 999.9 {
|
|
t.Error("cloneMetadataValue should create independent copy of []float64")
|
|
}
|
|
}
|
|
|
|
func TestCloneMetadataValue_InterfaceSlice(t *testing.T) {
|
|
src := []interface{}{"a", 1, 3.14}
|
|
result := cloneMetadataValue(src)
|
|
|
|
resultSlice, ok := result.([]interface{})
|
|
if !ok {
|
|
t.Fatalf("cloneMetadataValue([]interface{}) type = %T, want []interface{}", result)
|
|
}
|
|
|
|
if len(resultSlice) != len(src) {
|
|
t.Errorf("cloneMetadataValue([]interface{}) length = %d, want %d", len(resultSlice), len(src))
|
|
}
|
|
|
|
for i, v := range src {
|
|
if resultSlice[i] != v {
|
|
t.Errorf("cloneMetadataValue([]interface{})[%d] = %v, want %v", i, resultSlice[i], v)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestCloneMetadataValue_MapStringString(t *testing.T) {
|
|
src := map[string]string{"key": "value", "foo": "bar"}
|
|
result := cloneMetadataValue(src)
|
|
|
|
// The function converts map[string]string to map[string]interface{}
|
|
resultMap, ok := result.(map[string]interface{})
|
|
if !ok {
|
|
t.Fatalf("cloneMetadataValue(map[string]string) type = %T, want map[string]interface{}", result)
|
|
}
|
|
|
|
if len(resultMap) != len(src) {
|
|
t.Errorf("cloneMetadataValue length = %d, want %d", len(resultMap), len(src))
|
|
}
|
|
|
|
for k, v := range src {
|
|
if resultMap[k] != v {
|
|
t.Errorf("cloneMetadataValue[%s] = %v, want %v", k, resultMap[k], v)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestCloneMetadataValue_Primitives(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
input interface{}
|
|
}{
|
|
{"string", "hello"},
|
|
{"int", 42},
|
|
{"float64", 3.14},
|
|
{"bool", true},
|
|
{"nil", nil},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
result := cloneMetadataValue(tc.input)
|
|
if result != tc.input {
|
|
t.Errorf("cloneMetadataValue(%v) = %v, want same value", tc.input, result)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestAlert_Clone_Nil(t *testing.T) {
|
|
var a *Alert
|
|
result := a.Clone()
|
|
if result != nil {
|
|
t.Errorf("nil.Clone() = %v, want nil", result)
|
|
}
|
|
}
|
|
|
|
func TestAlert_Clone_BasicFields(t *testing.T) {
|
|
now := time.Now()
|
|
original := &Alert{
|
|
ID: "test-alert-1",
|
|
Type: "cpu",
|
|
Level: AlertLevelCritical,
|
|
ResourceID: "vm-100",
|
|
ResourceName: "test-vm",
|
|
Node: "node1",
|
|
Instance: "instance1",
|
|
Message: "CPU usage high",
|
|
Value: 95.5,
|
|
Threshold: 90.0,
|
|
StartTime: now,
|
|
LastSeen: now,
|
|
Acknowledged: true,
|
|
AckUser: "admin",
|
|
}
|
|
|
|
clone := original.Clone()
|
|
|
|
if clone == original {
|
|
t.Error("Clone() should return a different pointer")
|
|
}
|
|
|
|
if clone.ID != original.ID {
|
|
t.Errorf("Clone ID = %q, want %q", clone.ID, original.ID)
|
|
}
|
|
if clone.Type != original.Type {
|
|
t.Errorf("Clone Type = %q, want %q", clone.Type, original.Type)
|
|
}
|
|
if clone.Level != original.Level {
|
|
t.Errorf("Clone Level = %q, want %q", clone.Level, original.Level)
|
|
}
|
|
if clone.ResourceID != original.ResourceID {
|
|
t.Errorf("Clone ResourceID = %q, want %q", clone.ResourceID, original.ResourceID)
|
|
}
|
|
if clone.ResourceName != original.ResourceName {
|
|
t.Errorf("Clone ResourceName = %q, want %q", clone.ResourceName, original.ResourceName)
|
|
}
|
|
if clone.Node != original.Node {
|
|
t.Errorf("Clone Node = %q, want %q", clone.Node, original.Node)
|
|
}
|
|
if clone.Instance != original.Instance {
|
|
t.Errorf("Clone Instance = %q, want %q", clone.Instance, original.Instance)
|
|
}
|
|
if clone.Message != original.Message {
|
|
t.Errorf("Clone Message = %q, want %q", clone.Message, original.Message)
|
|
}
|
|
if clone.Value != original.Value {
|
|
t.Errorf("Clone Value = %f, want %f", clone.Value, original.Value)
|
|
}
|
|
if clone.Threshold != original.Threshold {
|
|
t.Errorf("Clone Threshold = %f, want %f", clone.Threshold, original.Threshold)
|
|
}
|
|
if !clone.StartTime.Equal(original.StartTime) {
|
|
t.Errorf("Clone StartTime = %v, want %v", clone.StartTime, original.StartTime)
|
|
}
|
|
if !clone.LastSeen.Equal(original.LastSeen) {
|
|
t.Errorf("Clone LastSeen = %v, want %v", clone.LastSeen, original.LastSeen)
|
|
}
|
|
if clone.Acknowledged != original.Acknowledged {
|
|
t.Errorf("Clone Acknowledged = %v, want %v", clone.Acknowledged, original.Acknowledged)
|
|
}
|
|
if clone.AckUser != original.AckUser {
|
|
t.Errorf("Clone AckUser = %q, want %q", clone.AckUser, original.AckUser)
|
|
}
|
|
}
|
|
|
|
func TestAlert_Clone_AckTime(t *testing.T) {
|
|
now := time.Now()
|
|
ackTime := now.Add(-1 * time.Hour)
|
|
original := &Alert{
|
|
ID: "test-1",
|
|
AckTime: &ackTime,
|
|
}
|
|
|
|
clone := original.Clone()
|
|
|
|
if clone.AckTime == nil {
|
|
t.Fatal("Clone AckTime should not be nil")
|
|
}
|
|
if clone.AckTime == original.AckTime {
|
|
t.Error("Clone AckTime should be a different pointer")
|
|
}
|
|
if !clone.AckTime.Equal(*original.AckTime) {
|
|
t.Errorf("Clone AckTime = %v, want %v", *clone.AckTime, *original.AckTime)
|
|
}
|
|
|
|
// Verify independence
|
|
*original.AckTime = time.Now().Add(24 * time.Hour)
|
|
if clone.AckTime.Equal(*original.AckTime) {
|
|
t.Error("Clone AckTime should be independent of original")
|
|
}
|
|
}
|
|
|
|
func TestAlert_Clone_LastNotified(t *testing.T) {
|
|
now := time.Now()
|
|
notified := now.Add(-30 * time.Minute)
|
|
original := &Alert{
|
|
ID: "test-1",
|
|
LastNotified: ¬ified,
|
|
}
|
|
|
|
clone := original.Clone()
|
|
|
|
if clone.LastNotified == nil {
|
|
t.Fatal("Clone LastNotified should not be nil")
|
|
}
|
|
if clone.LastNotified == original.LastNotified {
|
|
t.Error("Clone LastNotified should be a different pointer")
|
|
}
|
|
if !clone.LastNotified.Equal(*original.LastNotified) {
|
|
t.Errorf("Clone LastNotified = %v, want %v", *clone.LastNotified, *original.LastNotified)
|
|
}
|
|
}
|
|
|
|
func TestAlert_Clone_EscalationTimes(t *testing.T) {
|
|
now := time.Now()
|
|
original := &Alert{
|
|
ID: "test-1",
|
|
LastEscalation: 2,
|
|
EscalationTimes: []time.Time{now.Add(-2 * time.Hour), now.Add(-1 * time.Hour)},
|
|
}
|
|
|
|
clone := original.Clone()
|
|
|
|
if clone.LastEscalation != original.LastEscalation {
|
|
t.Errorf("Clone LastEscalation = %d, want %d", clone.LastEscalation, original.LastEscalation)
|
|
}
|
|
if len(clone.EscalationTimes) != len(original.EscalationTimes) {
|
|
t.Fatalf("Clone EscalationTimes length = %d, want %d", len(clone.EscalationTimes), len(original.EscalationTimes))
|
|
}
|
|
for i, et := range original.EscalationTimes {
|
|
if !clone.EscalationTimes[i].Equal(et) {
|
|
t.Errorf("Clone EscalationTimes[%d] = %v, want %v", i, clone.EscalationTimes[i], et)
|
|
}
|
|
}
|
|
|
|
// Verify slice independence
|
|
original.EscalationTimes[0] = time.Now()
|
|
if clone.EscalationTimes[0].Equal(original.EscalationTimes[0]) {
|
|
t.Error("Clone EscalationTimes should be independent of original")
|
|
}
|
|
}
|
|
|
|
func TestAlert_Clone_Metadata(t *testing.T) {
|
|
original := &Alert{
|
|
ID: "test-1",
|
|
Metadata: map[string]interface{}{
|
|
"key": "value",
|
|
"nested": map[string]interface{}{"inner": "data"},
|
|
},
|
|
}
|
|
|
|
clone := original.Clone()
|
|
|
|
if clone.Metadata == nil {
|
|
t.Fatal("Clone Metadata should not be nil")
|
|
}
|
|
if clone.Metadata["key"] != "value" {
|
|
t.Errorf("Clone Metadata[key] = %v, want 'value'", clone.Metadata["key"])
|
|
}
|
|
|
|
// Verify deep copy
|
|
original.Metadata["key"] = "modified"
|
|
if clone.Metadata["key"] == "modified" {
|
|
t.Error("Clone Metadata should be a deep copy")
|
|
}
|
|
|
|
// Verify nested map independence
|
|
originalNested := original.Metadata["nested"].(map[string]interface{})
|
|
originalNested["inner"] = "changed"
|
|
|
|
cloneNested := clone.Metadata["nested"].(map[string]interface{})
|
|
if cloneNested["inner"] == "changed" {
|
|
t.Error("Clone Metadata nested map should be independent")
|
|
}
|
|
}
|
|
|
|
func TestAlert_Clone_NilOptionalFields(t *testing.T) {
|
|
original := &Alert{
|
|
ID: "test-1",
|
|
AckTime: nil,
|
|
LastNotified: nil,
|
|
EscalationTimes: nil,
|
|
Metadata: nil,
|
|
}
|
|
|
|
clone := original.Clone()
|
|
|
|
if clone.AckTime != nil {
|
|
t.Error("Clone AckTime should be nil when original is nil")
|
|
}
|
|
if clone.LastNotified != nil {
|
|
t.Error("Clone LastNotified should be nil when original is nil")
|
|
}
|
|
if clone.EscalationTimes != nil {
|
|
t.Error("Clone EscalationTimes should be nil when original is nil")
|
|
}
|
|
if clone.Metadata != nil {
|
|
t.Error("Clone Metadata should be nil when original is nil")
|
|
}
|
|
}
|
|
|
|
func TestAlert_Clone_EmptyEscalationTimes(t *testing.T) {
|
|
original := &Alert{
|
|
ID: "test-1",
|
|
EscalationTimes: []time.Time{},
|
|
}
|
|
|
|
clone := original.Clone()
|
|
|
|
// Empty slice should remain nil after clone (len == 0 check)
|
|
if len(clone.EscalationTimes) != 0 {
|
|
t.Errorf("Clone EscalationTimes length = %d, want 0", len(clone.EscalationTimes))
|
|
}
|
|
}
|