From 219c568649a08cd2a16a123dd5b066d8d3fe10bb Mon Sep 17 00:00:00 2001 From: rcourtman Date: Tue, 2 Dec 2025 10:59:48 +0000 Subject: [PATCH] test: Add CheckDiskHealth normal path tests Coverage for CheckDiskHealth: 51% -> 98% Tests cover: - Healthy disk (PASSED/OK) creates no alert - Failed non-Samsung disk creates critical alert - Alert cleared when disk health recovers - Low wearout (<10%) creates warning alert - Wearout alert updates on subsequent checks - Wearout alert cleared when wearout >= 10% - Empty/UNKNOWN health creates no alert --- internal/alerts/alerts_test.go | 286 +++++++++++++++++++++++++++++++++ 1 file changed, 286 insertions(+) diff --git a/internal/alerts/alerts_test.go b/internal/alerts/alerts_test.go index 460b450..b09086f 100644 --- a/internal/alerts/alerts_test.go +++ b/internal/alerts/alerts_test.go @@ -1715,6 +1715,292 @@ func TestCheckDiskHealthClearsExistingSamsung980Alerts(t *testing.T) { } } +func TestCheckDiskHealthHealthyDiskNoAlert(t *testing.T) { + m := newTestManager(t) + m.ClearActiveAlerts() + + // Non-Samsung disk with PASSED health should not create alert + disk := proxmox.Disk{ + DevPath: "/dev/sda", + Model: "Western Digital WD40EFZX", + Serial: "WD-WCC4E0123456", + Type: "hdd", + Health: "PASSED", + Wearout: 0, // N/A for HDD + Size: 4000787030016, + } + + m.CheckDiskHealth("test-instance", "pve-node1", disk) + + m.mu.RLock() + healthAlertID := "disk-health-test-instance-pve-node1-/dev/sda" + if _, exists := m.activeAlerts[healthAlertID]; exists { + m.mu.RUnlock() + t.Fatalf("expected no health alert for healthy disk with PASSED status") + } + m.mu.RUnlock() + + // Also test with "OK" status + disk.Health = "OK" + m.CheckDiskHealth("test-instance", "pve-node1", disk) + + m.mu.RLock() + defer m.mu.RUnlock() + if _, exists := m.activeAlerts[healthAlertID]; exists { + t.Fatalf("expected no health alert for healthy disk with OK status") + } +} + +func TestCheckDiskHealthFailedDiskCreatesAlert(t *testing.T) { + m := newTestManager(t) + m.ClearActiveAlerts() + + // Non-Samsung disk with FAILED health should create alert + disk := proxmox.Disk{ + DevPath: "/dev/sdb", + Model: "Seagate ST2000DM008", + Serial: "ZA123456", + Type: "hdd", + Health: "FAILED", + Wearout: 0, + Size: 2000398934016, + } + + m.CheckDiskHealth("test-instance", "pve-node1", disk) + + m.mu.RLock() + defer m.mu.RUnlock() + + healthAlertID := "disk-health-test-instance-pve-node1-/dev/sdb" + alert, exists := m.activeAlerts[healthAlertID] + if !exists { + t.Fatalf("expected health alert to be created for failed disk") + } + + if alert.Level != AlertLevelCritical { + t.Errorf("expected critical alert level, got %s", alert.Level) + } + if alert.Type != "disk-health" { + t.Errorf("expected type disk-health, got %s", alert.Type) + } + if alert.Node != "pve-node1" { + t.Errorf("expected node pve-node1, got %s", alert.Node) + } + if alert.Instance != "test-instance" { + t.Errorf("expected instance test-instance, got %s", alert.Instance) + } +} + +func TestCheckDiskHealthRecoveryAlertCleared(t *testing.T) { + m := newTestManager(t) + m.ClearActiveAlerts() + + disk := proxmox.Disk{ + DevPath: "/dev/sdc", + Model: "Intel SSDSC2BB480G4", + Serial: "BTWL123456789", + Type: "ssd", + Health: "FAILED", + Wearout: 50, + Size: 480103981056, + } + + // First check creates alert + m.CheckDiskHealth("test-instance", "pve-node1", disk) + + healthAlertID := "disk-health-test-instance-pve-node1-/dev/sdc" + m.mu.RLock() + if _, exists := m.activeAlerts[healthAlertID]; !exists { + m.mu.RUnlock() + t.Fatalf("expected health alert to be created") + } + m.mu.RUnlock() + + // Disk health recovers + disk.Health = "PASSED" + m.CheckDiskHealth("test-instance", "pve-node1", disk) + + m.mu.RLock() + defer m.mu.RUnlock() + if _, exists := m.activeAlerts[healthAlertID]; exists { + t.Fatalf("expected health alert to be cleared after recovery") + } +} + +func TestCheckDiskHealthLowWearoutCreatesAlert(t *testing.T) { + m := newTestManager(t) + m.ClearActiveAlerts() + + // SSD with low wearout (less than 10% life remaining) + disk := proxmox.Disk{ + DevPath: "/dev/nvme1n1", + Model: "Crucial CT1000MX500", + Serial: "12345678ABCD", + Type: "nvme", + Health: "PASSED", + Wearout: 5, // Only 5% life remaining + Size: 1000204886016, + } + + m.CheckDiskHealth("test-instance", "pve-node1", disk) + + m.mu.RLock() + defer m.mu.RUnlock() + + wearoutAlertID := "disk-wearout-test-instance-pve-node1-/dev/nvme1n1" + alert, exists := m.activeAlerts[wearoutAlertID] + if !exists { + t.Fatalf("expected wearout alert to be created for disk with low life remaining") + } + + if alert.Level != AlertLevelWarning { + t.Errorf("expected warning alert level, got %s", alert.Level) + } + if alert.Type != "disk-wearout" { + t.Errorf("expected type disk-wearout, got %s", alert.Type) + } + if alert.Value != 5 { + t.Errorf("expected value 5, got %f", alert.Value) + } + if alert.Threshold != 10.0 { + t.Errorf("expected threshold 10.0, got %f", alert.Threshold) + } +} + +func TestCheckDiskHealthWearoutAlertUpdatesOnSubsequentChecks(t *testing.T) { + m := newTestManager(t) + m.ClearActiveAlerts() + + disk := proxmox.Disk{ + DevPath: "/dev/nvme2n1", + Model: "Kingston SA2000M8", + Serial: "50026B768A123456", + Type: "nvme", + Health: "PASSED", + Wearout: 8, + Size: 500107862016, + } + + // First check creates alert + m.CheckDiskHealth("test-instance", "pve-node1", disk) + + wearoutAlertID := "disk-wearout-test-instance-pve-node1-/dev/nvme2n1" + m.mu.RLock() + alert, exists := m.activeAlerts[wearoutAlertID] + if !exists { + m.mu.RUnlock() + t.Fatalf("expected wearout alert to be created") + } + firstLastSeen := alert.LastSeen + m.mu.RUnlock() + + // Wait a moment to ensure time difference + time.Sleep(10 * time.Millisecond) + + // Wearout decreases further + disk.Wearout = 6 + m.CheckDiskHealth("test-instance", "pve-node1", disk) + + m.mu.RLock() + defer m.mu.RUnlock() + + alert, exists = m.activeAlerts[wearoutAlertID] + if !exists { + t.Fatalf("expected wearout alert to still exist") + } + + if !alert.LastSeen.After(firstLastSeen) { + t.Errorf("expected LastSeen to be updated, got %v (original: %v)", alert.LastSeen, firstLastSeen) + } + if alert.Value != 6 { + t.Errorf("expected value to be updated to 6, got %f", alert.Value) + } +} + +func TestCheckDiskHealthWearoutRecoveryAlertCleared(t *testing.T) { + m := newTestManager(t) + m.ClearActiveAlerts() + + disk := proxmox.Disk{ + DevPath: "/dev/sdd", + Model: "ADATA SU800", + Serial: "2J012345678", + Type: "ssd", + Health: "PASSED", + Wearout: 5, + Size: 256060514304, + } + + // First check creates wearout alert + m.CheckDiskHealth("test-instance", "pve-node1", disk) + + wearoutAlertID := "disk-wearout-test-instance-pve-node1-/dev/sdd" + m.mu.RLock() + if _, exists := m.activeAlerts[wearoutAlertID]; !exists { + m.mu.RUnlock() + t.Fatalf("expected wearout alert to be created") + } + m.mu.RUnlock() + + // Wearout recovers (replaced drive, or misread corrected) + disk.Wearout = 95 + m.CheckDiskHealth("test-instance", "pve-node1", disk) + + m.mu.RLock() + defer m.mu.RUnlock() + if _, exists := m.activeAlerts[wearoutAlertID]; exists { + t.Fatalf("expected wearout alert to be cleared after recovery") + } +} + +func TestCheckDiskHealthEmptyOrUnknownHealthNoAlert(t *testing.T) { + m := newTestManager(t) + m.ClearActiveAlerts() + + disk := proxmox.Disk{ + DevPath: "/dev/sde", + Model: "Generic USB Storage", + Serial: "USB123456", + Type: "hdd", + Health: "", // Empty health - SMART not supported + Wearout: 0, + Size: 128043712512, + } + + healthAlertID := "disk-health-test-instance-pve-node1-/dev/sde" + + // Empty health should not create alert + m.CheckDiskHealth("test-instance", "pve-node1", disk) + + m.mu.RLock() + if _, exists := m.activeAlerts[healthAlertID]; exists { + m.mu.RUnlock() + t.Fatalf("expected no health alert for disk with empty health status") + } + m.mu.RUnlock() + + // UNKNOWN health should not create alert + disk.Health = "UNKNOWN" + m.CheckDiskHealth("test-instance", "pve-node1", disk) + + m.mu.RLock() + if _, exists := m.activeAlerts[healthAlertID]; exists { + m.mu.RUnlock() + t.Fatalf("expected no health alert for disk with UNKNOWN health status") + } + m.mu.RUnlock() + + // Lowercase "unknown" should also not create alert (normalized to uppercase) + disk.Health = "unknown" + m.CheckDiskHealth("test-instance", "pve-node1", disk) + + m.mu.RLock() + defer m.mu.RUnlock() + if _, exists := m.activeAlerts[healthAlertID]; exists { + t.Fatalf("expected no health alert for disk with lowercase unknown health status") + } +} + func TestDisableAllStorageClearsExistingAlerts(t *testing.T) { m := newTestManager(t)