Pulse/internal/alerts/default_normalization_test.go
2026-05-13 10:44:11 +01:00

416 lines
12 KiB
Go

package alerts
import (
"testing"
alertconfig "github.com/rcourtman/pulse-go-rewrite/internal/alerts/config"
)
func TestNormalizeSnapshotDefaults(t *testing.T) {
tests := []struct {
name string
in SnapshotAlertConfig
want SnapshotAlertConfig
}{
{
name: "negative values clamp to zero",
in: SnapshotAlertConfig{
WarningDays: -1,
CriticalDays: -2,
WarningSizeGiB: -3,
CriticalSizeGiB: -4,
},
want: SnapshotAlertConfig{},
},
{
name: "warning values clamp down to critical",
in: SnapshotAlertConfig{
WarningDays: 10,
CriticalDays: 3,
WarningSizeGiB: 40,
CriticalSizeGiB: 8,
},
want: SnapshotAlertConfig{
WarningDays: 3,
CriticalDays: 3,
WarningSizeGiB: 8,
CriticalSizeGiB: 8,
},
},
{
name: "critical inherits warning when critical unset",
in: SnapshotAlertConfig{
WarningDays: 7,
CriticalDays: 0,
WarningSizeGiB: 12,
CriticalSizeGiB: 0,
},
want: SnapshotAlertConfig{
WarningDays: 7,
CriticalDays: 7,
WarningSizeGiB: 12,
CriticalSizeGiB: 12,
},
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
cfg := &AlertConfig{SnapshotDefaults: tc.in}
normalizeSnapshotDefaults(cfg)
got := cfg.SnapshotDefaults
if got != tc.want {
t.Fatalf("normalizeSnapshotDefaults() = %+v, want %+v", got, tc.want)
}
})
}
}
func TestNormalizeBackupDefaults(t *testing.T) {
falseValue := false
cfg := &AlertConfig{
BackupDefaults: BackupAlertConfig{
WarningDays: 10,
CriticalDays: 3,
AlertOrphaned: &falseValue,
IgnoreVMIDs: []string{
" 101 ",
"",
"101",
" 10* ",
" ",
"10*",
},
},
}
normalizeBackupDefaults(cfg)
if cfg.BackupDefaults.WarningDays != 3 {
t.Fatalf("WarningDays = %d, want 3", cfg.BackupDefaults.WarningDays)
}
if cfg.BackupDefaults.CriticalDays != 3 {
t.Fatalf("CriticalDays = %d, want 3", cfg.BackupDefaults.CriticalDays)
}
if cfg.BackupDefaults.AlertOrphaned == nil {
t.Fatal("AlertOrphaned should not be nil")
}
if *cfg.BackupDefaults.AlertOrphaned != false {
t.Fatalf("AlertOrphaned = %v, want false", *cfg.BackupDefaults.AlertOrphaned)
}
wantIgnore := []string{"101", "10*"}
if len(cfg.BackupDefaults.IgnoreVMIDs) != len(wantIgnore) {
t.Fatalf("IgnoreVMIDs length = %d, want %d (%v)", len(cfg.BackupDefaults.IgnoreVMIDs), len(wantIgnore), cfg.BackupDefaults.IgnoreVMIDs)
}
for i := range wantIgnore {
if cfg.BackupDefaults.IgnoreVMIDs[i] != wantIgnore[i] {
t.Fatalf("IgnoreVMIDs[%d] = %q, want %q", i, cfg.BackupDefaults.IgnoreVMIDs[i], wantIgnore[i])
}
}
}
func TestNormalizeBackupDefaultsSetsAlertOrphanedDefaultAndClampsNegativeDays(t *testing.T) {
cfg := &AlertConfig{
BackupDefaults: BackupAlertConfig{
WarningDays: -2,
CriticalDays: -1,
},
}
normalizeBackupDefaults(cfg)
if cfg.BackupDefaults.WarningDays != 0 {
t.Fatalf("WarningDays = %d, want 0", cfg.BackupDefaults.WarningDays)
}
if cfg.BackupDefaults.CriticalDays != 0 {
t.Fatalf("CriticalDays = %d, want 0", cfg.BackupDefaults.CriticalDays)
}
if cfg.BackupDefaults.AlertOrphaned == nil {
t.Fatal("AlertOrphaned should be defaulted to true")
}
if !*cfg.BackupDefaults.AlertOrphaned {
t.Fatalf("AlertOrphaned = %v, want true", *cfg.BackupDefaults.AlertOrphaned)
}
}
func TestBackupIgnoreVMID(t *testing.T) {
tests := []struct {
name string
vmID string
ignoreList []string
want bool
}{
{
name: "empty vmid returns false",
vmID: "",
ignoreList: []string{"101"},
want: false,
},
{
name: "exact match",
vmID: "101",
ignoreList: []string{"101"},
want: true,
},
{
name: "prefix wildcard match",
vmID: "10123",
ignoreList: []string{"101*"},
want: true,
},
{
name: "trim spaces before matching",
vmID: "200",
ignoreList: []string{" 200 "},
want: true,
},
{
name: "star only pattern does not match everything",
vmID: "101",
ignoreList: []string{"*"},
want: false,
},
{
name: "blank entries ignored",
vmID: "101",
ignoreList: []string{" ", ""},
want: false,
},
{
name: "no match",
vmID: "999",
ignoreList: []string{"101", "10*"},
want: false,
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
if got := backupIgnoreVMID(tc.vmID, tc.ignoreList); got != tc.want {
t.Fatalf("backupIgnoreVMID(%q, %v) = %v, want %v", tc.vmID, tc.ignoreList, got, tc.want)
}
})
}
}
func TestNormalizeDiskFillByType(t *testing.T) {
t.Run("nil map seeds nvme/sata/hdd defaults", func(t *testing.T) {
cfg := &AlertConfig{}
alertconfig.NormalizeDiskFillByType(cfg)
if cfg.DiskFillByType == nil {
t.Fatal("expected DiskFillByType to be seeded, got nil")
}
nvme, ok := cfg.DiskFillByType["nvme"]
if !ok {
t.Fatal("nvme key not seeded")
}
if nvme.Trigger != 92 || nvme.Clear != 87 {
t.Fatalf("nvme = %+v, want {Trigger:92 Clear:87}", nvme)
}
sata, ok := cfg.DiskFillByType["sata"]
if !ok {
t.Fatal("sata key not seeded")
}
if sata.Trigger != 90 || sata.Clear != 85 {
t.Fatalf("sata = %+v, want {Trigger:90 Clear:85}", sata)
}
hdd, ok := cfg.DiskFillByType["hdd"]
if !ok {
t.Fatal("hdd key not seeded")
}
if hdd.Trigger != 85 || hdd.Clear != 80 {
t.Fatalf("hdd = %+v, want {Trigger:85 Clear:80}", hdd)
}
})
t.Run("operator customized values survive", func(t *testing.T) {
cfg := &AlertConfig{
DiskFillByType: map[string]HysteresisThreshold{
"nvme": {Trigger: 95, Clear: 90},
},
}
alertconfig.NormalizeDiskFillByType(cfg)
nvme := cfg.DiskFillByType["nvme"]
if nvme.Trigger != 95 || nvme.Clear != 90 {
t.Fatalf("nvme = %+v, want operator value {Trigger:95 Clear:90}", nvme)
}
// Missing keys still seeded.
if sata, ok := cfg.DiskFillByType["sata"]; !ok || sata.Trigger != 90 || sata.Clear != 85 {
t.Fatalf("sata = %+v ok=%v, want default {Trigger:90 Clear:85}", sata, ok)
}
if hdd, ok := cfg.DiskFillByType["hdd"]; !ok || hdd.Trigger != 85 || hdd.Clear != 80 {
t.Fatalf("hdd = %+v ok=%v, want default {Trigger:85 Clear:80}", hdd, ok)
}
})
t.Run("negative trigger resets to default", func(t *testing.T) {
cfg := &AlertConfig{
DiskFillByType: map[string]HysteresisThreshold{
"nvme": {Trigger: -1, Clear: 10},
"sata": {Trigger: 88, Clear: -5},
},
}
alertconfig.NormalizeDiskFillByType(cfg)
nvme := cfg.DiskFillByType["nvme"]
if nvme.Trigger != 92 || nvme.Clear != 87 {
t.Fatalf("nvme = %+v, want default reset {Trigger:92 Clear:87}", nvme)
}
sata := cfg.DiskFillByType["sata"]
if sata.Trigger != 90 || sata.Clear != 85 {
t.Fatalf("sata = %+v, want default reset {Trigger:90 Clear:85}", sata)
}
})
t.Run("mixed case keys lowercase to canonical", func(t *testing.T) {
cfg := &AlertConfig{
DiskFillByType: map[string]HysteresisThreshold{
"NVMe": {Trigger: 93, Clear: 88},
},
}
alertconfig.NormalizeDiskFillByType(cfg)
if _, exists := cfg.DiskFillByType["NVMe"]; exists {
t.Fatalf("expected mixed-case key NVMe to be removed, map=%+v", cfg.DiskFillByType)
}
nvme, ok := cfg.DiskFillByType["nvme"]
if !ok {
t.Fatalf("expected lowercase nvme key, map=%+v", cfg.DiskFillByType)
}
if nvme.Trigger != 93 || nvme.Clear != 88 {
t.Fatalf("nvme = %+v, want preserved operator value {Trigger:93 Clear:88}", nvme)
}
})
}
func TestDefaultAlertConfigSeedsDiskFillByType(t *testing.T) {
cfg := defaultAlertConfig()
if cfg.DiskFillByType == nil {
t.Fatal("expected defaultAlertConfig to seed DiskFillByType, got nil")
}
if nvme, ok := cfg.DiskFillByType["nvme"]; !ok || nvme.Trigger != 92 || nvme.Clear != 87 {
t.Fatalf("nvme = %+v ok=%v, want {Trigger:92 Clear:87}", nvme, ok)
}
if sata, ok := cfg.DiskFillByType["sata"]; !ok || sata.Trigger != 90 || sata.Clear != 85 {
t.Fatalf("sata = %+v ok=%v, want {Trigger:90 Clear:85}", sata, ok)
}
if hdd, ok := cfg.DiskFillByType["hdd"]; !ok || hdd.Trigger != 85 || hdd.Clear != 80 {
t.Fatalf("hdd = %+v ok=%v, want {Trigger:85 Clear:80}", hdd, ok)
}
}
func TestNormalizeDiskTempByType(t *testing.T) {
t.Run("nil map seeds nvme/sas/sata defaults", func(t *testing.T) {
cfg := &AlertConfig{}
alertconfig.NormalizeDiskTempByType(cfg)
if cfg.DiskTempByType == nil {
t.Fatal("expected DiskTempByType to be seeded, got nil")
}
nvme, ok := cfg.DiskTempByType["nvme"]
if !ok {
t.Fatal("nvme key not seeded")
}
if nvme.Trigger != 70 || nvme.Clear != 65 {
t.Fatalf("nvme = %+v, want {Trigger:70 Clear:65}", nvme)
}
sas, ok := cfg.DiskTempByType["sas"]
if !ok {
t.Fatal("sas key not seeded")
}
if sas.Trigger != 65 || sas.Clear != 60 {
t.Fatalf("sas = %+v, want {Trigger:65 Clear:60}", sas)
}
sata, ok := cfg.DiskTempByType["sata"]
if !ok {
t.Fatal("sata key not seeded")
}
if sata.Trigger != 55 || sata.Clear != 50 {
t.Fatalf("sata = %+v, want {Trigger:55 Clear:50}", sata)
}
if _, ok := cfg.DiskTempByType["hdd"]; ok {
t.Fatalf("hdd key should not be seeded for SMART temperature, map=%+v", cfg.DiskTempByType)
}
})
t.Run("operator customized values survive", func(t *testing.T) {
cfg := &AlertConfig{
DiskTempByType: map[string]HysteresisThreshold{
"nvme": {Trigger: 75, Clear: 68},
},
}
alertconfig.NormalizeDiskTempByType(cfg)
nvme := cfg.DiskTempByType["nvme"]
if nvme.Trigger != 75 || nvme.Clear != 68 {
t.Fatalf("nvme = %+v, want operator value {Trigger:75 Clear:68}", nvme)
}
if sas, ok := cfg.DiskTempByType["sas"]; !ok || sas.Trigger != 65 || sas.Clear != 60 {
t.Fatalf("sas = %+v ok=%v, want default {Trigger:65 Clear:60}", sas, ok)
}
if sata, ok := cfg.DiskTempByType["sata"]; !ok || sata.Trigger != 55 || sata.Clear != 50 {
t.Fatalf("sata = %+v ok=%v, want default {Trigger:55 Clear:50}", sata, ok)
}
})
t.Run("negative trigger resets to default", func(t *testing.T) {
cfg := &AlertConfig{
DiskTempByType: map[string]HysteresisThreshold{
"nvme": {Trigger: -1, Clear: 10},
"sas": {Trigger: 64, Clear: -5},
},
}
alertconfig.NormalizeDiskTempByType(cfg)
nvme := cfg.DiskTempByType["nvme"]
if nvme.Trigger != 70 || nvme.Clear != 65 {
t.Fatalf("nvme = %+v, want default reset {Trigger:70 Clear:65}", nvme)
}
sas := cfg.DiskTempByType["sas"]
if sas.Trigger != 65 || sas.Clear != 60 {
t.Fatalf("sas = %+v, want default reset {Trigger:65 Clear:60}", sas)
}
})
t.Run("mixed case keys lowercase to canonical", func(t *testing.T) {
cfg := &AlertConfig{
DiskTempByType: map[string]HysteresisThreshold{
"NVMe": {Trigger: 72, Clear: 67},
},
}
alertconfig.NormalizeDiskTempByType(cfg)
if _, exists := cfg.DiskTempByType["NVMe"]; exists {
t.Fatalf("expected mixed-case key NVMe to be removed, map=%+v", cfg.DiskTempByType)
}
nvme, ok := cfg.DiskTempByType["nvme"]
if !ok {
t.Fatalf("expected lowercase nvme key, map=%+v", cfg.DiskTempByType)
}
if nvme.Trigger != 72 || nvme.Clear != 67 {
t.Fatalf("nvme = %+v, want preserved operator value {Trigger:72 Clear:67}", nvme)
}
})
}
func TestDefaultAlertConfigSeedsDiskTempByType(t *testing.T) {
cfg := defaultAlertConfig()
if cfg.DiskTempByType == nil {
t.Fatal("expected defaultAlertConfig to seed DiskTempByType, got nil")
}
if nvme, ok := cfg.DiskTempByType["nvme"]; !ok || nvme.Trigger != 70 || nvme.Clear != 65 {
t.Fatalf("nvme = %+v ok=%v, want {Trigger:70 Clear:65}", nvme, ok)
}
if sas, ok := cfg.DiskTempByType["sas"]; !ok || sas.Trigger != 65 || sas.Clear != 60 {
t.Fatalf("sas = %+v ok=%v, want {Trigger:65 Clear:60}", sas, ok)
}
if sata, ok := cfg.DiskTempByType["sata"]; !ok || sata.Trigger != 55 || sata.Clear != 50 {
t.Fatalf("sata = %+v ok=%v, want {Trigger:55 Clear:50}", sata, ok)
}
if _, ok := cfg.DiskTempByType["hdd"]; ok {
t.Fatalf("hdd key should not be seeded for SMART temperature, map=%+v", cfg.DiskTempByType)
}
}