package models import ( "testing" "time" ) func TestUpsertHost(t *testing.T) { state := NewState() // Test insert new host host1 := Host{ ID: "host-1", Hostname: "server-1", Status: "online", } state.UpsertHost(host1) hosts := state.GetHosts() if len(hosts) != 1 { t.Fatalf("Expected 1 host, got %d", len(hosts)) } if hosts[0].ID != "host-1" { t.Errorf("Expected host ID 'host-1', got %q", hosts[0].ID) } // Test update existing host host1Updated := Host{ ID: "host-1", Hostname: "server-1", Status: "offline", } state.UpsertHost(host1Updated) hosts = state.GetHosts() if len(hosts) != 1 { t.Fatalf("Expected 1 host after update, got %d", len(hosts)) } if hosts[0].Status != "offline" { t.Errorf("Expected status 'offline', got %q", hosts[0].Status) } // Test multiple hosts and sorting state.UpsertHost(Host{ID: "host-2", Hostname: "alpha-server"}) state.UpsertHost(Host{ID: "host-3", Hostname: "zulu-server"}) hosts = state.GetHosts() if len(hosts) != 3 { t.Fatalf("Expected 3 hosts, got %d", len(hosts)) } // Hosts should be sorted by hostname if hosts[0].Hostname != "alpha-server" { t.Errorf("First host should be 'alpha-server', got %q", hosts[0].Hostname) } } func TestGetHosts_Copy(t *testing.T) { state := NewState() state.UpsertHost(Host{ID: "host-1", Hostname: "server-1"}) hosts1 := state.GetHosts() hosts2 := state.GetHosts() // Modify one slice if len(hosts1) > 0 { hosts1[0].Hostname = "modified" } // Other slice should be unchanged if len(hosts2) > 0 && hosts2[0].Hostname == "modified" { t.Error("GetHosts should return a copy, not the same slice") } // Original state should be unchanged hosts3 := state.GetHosts() if hosts3[0].Hostname == "modified" { t.Error("State should be unchanged by modifications to returned slice") } } func TestRemoveHost(t *testing.T) { state := NewState() // Insert hosts state.UpsertHost(Host{ID: "host-1", Hostname: "server1"}) state.UpsertHost(Host{ID: "host-2", Hostname: "server2"}) // Remove existing host removed, ok := state.RemoveHost("host-1") if !ok { t.Error("Expected RemoveHost to return true for existing host") } if removed.ID != "host-1" { t.Errorf("Expected removed host ID 'host-1', got %q", removed.ID) } hosts := state.GetHosts() if len(hosts) != 1 { t.Fatalf("Expected 1 host after removal, got %d", len(hosts)) } if hosts[0].ID != "host-2" { t.Errorf("Remaining host should be 'host-2', got %q", hosts[0].ID) } // Remove non-existing host removed, ok = state.RemoveHost("non-existent") if ok { t.Error("Expected RemoveHost to return false for non-existent host") } if removed.ID != "" { t.Errorf("Expected empty Host for non-existent removal, got ID %q", removed.ID) } } func TestSetHostStatus(t *testing.T) { state := NewState() // Test with no hosts changed := state.SetHostStatus("host-1", "online") if changed { t.Error("SetHostStatus should return false when host doesn't exist") } // Add host and set status state.UpsertHost(Host{ID: "host-1", Hostname: "server1", Status: "offline"}) changed = state.SetHostStatus("host-1", "online") if !changed { t.Error("SetHostStatus should return true when host exists") } hosts := state.GetHosts() if hosts[0].Status != "online" { t.Errorf("Expected status 'online', got %q", hosts[0].Status) } // Set same status (no change) changed = state.SetHostStatus("host-1", "online") if !changed { t.Error("SetHostStatus should return true even when status unchanged") } } func TestTouchHost(t *testing.T) { state := NewState() now := time.Now() // Test with non-existent host ok := state.TouchHost("host-1", now) if ok { t.Error("TouchHost should return false for non-existent host") } // Add host and touch it state.UpsertHost(Host{ID: "host-1", Hostname: "server1"}) later := now.Add(time.Hour) ok = state.TouchHost("host-1", later) if !ok { t.Error("TouchHost should return true for existing host") } hosts := state.GetHosts() if !hosts[0].LastSeen.Equal(later) { t.Errorf("LastSeen should be updated to %v, got %v", later, hosts[0].LastSeen) } } func TestUpdateRecentlyResolved(t *testing.T) { state := NewState() now := time.Now() alerts := []ResolvedAlert{ { Alert: Alert{ ID: "alert-1", Type: "cpu_high", Level: "warning", ResourceName: "server-1", Message: "High CPU usage", }, ResolvedTime: now, }, { Alert: Alert{ ID: "alert-2", Type: "disk_low", Level: "critical", ResourceName: "server-2", Message: "Low disk space", }, ResolvedTime: now.Add(-time.Hour), }, } state.UpdateRecentlyResolved(alerts) // The method updates internal state - verify via snapshot or other means // Since RecentlyResolvedAlerts might be accessed differently, just verify no panic // and method executes correctly } func TestSetConnectionHealth(t *testing.T) { state := NewState() // Set connection healthy state.SetConnectionHealth("pve-cluster-1", true) snapshot := state.GetSnapshot() if healthy, ok := snapshot.ConnectionHealth["pve-cluster-1"]; !ok || !healthy { t.Error("Expected connection to be healthy") } // Set connection unhealthy state.SetConnectionHealth("pve-cluster-1", false) snapshot = state.GetSnapshot() if healthy, ok := snapshot.ConnectionHealth["pve-cluster-1"]; !ok || healthy { t.Error("Expected connection to be unhealthy") } // Multiple connections state.SetConnectionHealth("pve-cluster-2", true) state.SetConnectionHealth("pbs-instance-1", true) snapshot = state.GetSnapshot() if len(snapshot.ConnectionHealth) != 3 { t.Errorf("Expected 3 connection health entries, got %d", len(snapshot.ConnectionHealth)) } } func TestRemoveConnectionHealth(t *testing.T) { state := NewState() // Set up connection health entries state.SetConnectionHealth("pve-cluster-1", true) state.SetConnectionHealth("pve-cluster-2", false) // Remove one state.RemoveConnectionHealth("pve-cluster-1") snapshot := state.GetSnapshot() if _, ok := snapshot.ConnectionHealth["pve-cluster-1"]; ok { t.Error("Expected pve-cluster-1 to be removed") } if _, ok := snapshot.ConnectionHealth["pve-cluster-2"]; !ok { t.Error("Expected pve-cluster-2 to still exist") } // Remove non-existent (should not panic) state.RemoveConnectionHealth("non-existent") // Remove remaining state.RemoveConnectionHealth("pve-cluster-2") snapshot = state.GetSnapshot() if len(snapshot.ConnectionHealth) != 0 { t.Errorf("Expected empty connection health map, got %d entries", len(snapshot.ConnectionHealth)) } } func TestUpdatePBSBackups(t *testing.T) { state := NewState() now := time.Now() // Add backups from first instance backups1 := []PBSBackup{ {ID: "backup-1", Instance: "pbs-1", BackupTime: now}, {ID: "backup-2", Instance: "pbs-1", BackupTime: now.Add(-time.Hour)}, } state.UpdatePBSBackups("pbs-1", backups1) snapshot := state.GetSnapshot() if len(snapshot.PBSBackups) != 2 { t.Fatalf("Expected 2 backups, got %d", len(snapshot.PBSBackups)) } // Add backups from second instance backups2 := []PBSBackup{ {ID: "backup-3", Instance: "pbs-2", BackupTime: now.Add(-30 * time.Minute)}, } state.UpdatePBSBackups("pbs-2", backups2) snapshot = state.GetSnapshot() if len(snapshot.PBSBackups) != 3 { t.Fatalf("Expected 3 backups, got %d", len(snapshot.PBSBackups)) } // Update first instance (should replace its backups) backups1Updated := []PBSBackup{ {ID: "backup-4", Instance: "pbs-1", BackupTime: now.Add(time.Hour)}, } state.UpdatePBSBackups("pbs-1", backups1Updated) snapshot = state.GetSnapshot() if len(snapshot.PBSBackups) != 2 { t.Fatalf("Expected 2 backups after update, got %d", len(snapshot.PBSBackups)) } // Verify pbs-1 backups were replaced hasOldBackup := false for _, b := range snapshot.PBSBackups { if b.ID == "backup-1" || b.ID == "backup-2" { hasOldBackup = true break } } if hasOldBackup { t.Error("Old pbs-1 backups should have been replaced") } } func TestUpdatePMGBackups(t *testing.T) { state := NewState() now := time.Now() // Add backups from first instance backups1 := []PMGBackup{ {Filename: "backup-1.conf", Instance: "pmg-1", BackupTime: now}, {Filename: "backup-2.conf", Instance: "pmg-1", BackupTime: now.Add(-time.Hour)}, } state.UpdatePMGBackups("pmg-1", backups1) snapshot := state.GetSnapshot() if len(snapshot.PMGBackups) != 2 { t.Fatalf("Expected 2 backups, got %d", len(snapshot.PMGBackups)) } // Backups should be sorted by time (newest first) if snapshot.PMGBackups[0].BackupTime.Before(snapshot.PMGBackups[1].BackupTime) { t.Error("Backups should be sorted by time descending") } // Add backups from second instance backups2 := []PMGBackup{ {Filename: "backup-3.conf", Instance: "pmg-2", BackupTime: now.Add(-30 * time.Minute)}, } state.UpdatePMGBackups("pmg-2", backups2) snapshot = state.GetSnapshot() if len(snapshot.PMGBackups) != 3 { t.Fatalf("Expected 3 backups, got %d", len(snapshot.PMGBackups)) } // Update first instance with empty (should remove its backups) state.UpdatePMGBackups("pmg-1", []PMGBackup{}) snapshot = state.GetSnapshot() if len(snapshot.PMGBackups) != 1 { t.Fatalf("Expected 1 backup after clearing pmg-1, got %d", len(snapshot.PMGBackups)) } if snapshot.PMGBackups[0].Instance != "pmg-2" { t.Error("Remaining backup should be from pmg-2") } }