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 }