This cleanup addresses transition debt from the unified resources migration: Frontend cleanup: - Move all Resource→Legacy type conversions to useResourcesAsLegacy() hook - Add asNodes() and asDockerHosts() adapter functions to the hook - Simplify DockerRoute, HostsRoute, DashboardView to use the centralized hook - Remove ~300 lines of duplicated adapter code from App.tsx - Remove debug console.log statements from Dashboard.tsx - Fix CPU value conversion (divide by 100) for Dashboard compatibility Backend fixes (from previous session): - Fix parentID format in converters (VM, Container, Storage) to match Node.ID - Format changed from 'instance/node/nodename' to 'instance-nodename' - Update tests to match new parentID format This consolidates all legacy type conversion logic in one place, making future cleanup easier when components are migrated to use unified resources directly.
546 lines
14 KiB
Go
546 lines
14 KiB
Go
package resources
|
|
|
|
import (
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/rcourtman/pulse-go-rewrite/internal/models"
|
|
)
|
|
|
|
func TestFromNode(t *testing.T) {
|
|
node := models.Node{
|
|
ID: "pve1/node/node1",
|
|
Name: "node1",
|
|
DisplayName: "Production Node 1",
|
|
Instance: "pve1",
|
|
Host: "https://192.168.1.100:8006",
|
|
Status: "online",
|
|
CPU: 0.25, // 25%
|
|
Memory: models.Memory{
|
|
Total: 16 * 1024 * 1024 * 1024, // 16GB
|
|
Used: 8 * 1024 * 1024 * 1024, // 8GB
|
|
Free: 8 * 1024 * 1024 * 1024,
|
|
Usage: 50.0,
|
|
},
|
|
Disk: models.Disk{
|
|
Total: 500 * 1024 * 1024 * 1024, // 500GB
|
|
Used: 200 * 1024 * 1024 * 1024, // 200GB
|
|
Free: 300 * 1024 * 1024 * 1024,
|
|
Usage: 40.0,
|
|
},
|
|
Uptime: 86400,
|
|
KernelVersion: "6.8.4-2-pve",
|
|
PVEVersion: "8.2.2",
|
|
IsClusterMember: true,
|
|
ClusterName: "production",
|
|
LastSeen: time.Now(),
|
|
LoadAverage: []float64{1.5, 2.0, 1.8},
|
|
}
|
|
|
|
r := FromNode(node)
|
|
|
|
// Check basic fields
|
|
if r.ID != node.ID {
|
|
t.Errorf("Expected ID %s, got %s", node.ID, r.ID)
|
|
}
|
|
if r.Type != ResourceTypeNode {
|
|
t.Errorf("Expected type %s, got %s", ResourceTypeNode, r.Type)
|
|
}
|
|
if r.Name != node.Name {
|
|
t.Errorf("Expected name %s, got %s", node.Name, r.Name)
|
|
}
|
|
if r.DisplayName != node.DisplayName {
|
|
t.Errorf("Expected displayName %s, got %s", node.DisplayName, r.DisplayName)
|
|
}
|
|
if r.PlatformType != PlatformProxmoxPVE {
|
|
t.Errorf("Expected platform %s, got %s", PlatformProxmoxPVE, r.PlatformType)
|
|
}
|
|
if r.SourceType != SourceAPI {
|
|
t.Errorf("Expected source %s, got %s", SourceAPI, r.SourceType)
|
|
}
|
|
if r.Status != StatusOnline {
|
|
t.Errorf("Expected status %s, got %s", StatusOnline, r.Status)
|
|
}
|
|
|
|
// Check metrics
|
|
if r.CPU == nil {
|
|
t.Fatal("CPU should not be nil")
|
|
}
|
|
if r.CPU.Current != 25.0 { // Converted from 0-1 to percentage
|
|
t.Errorf("Expected CPU 25%%, got %f%%", r.CPU.Current)
|
|
}
|
|
|
|
if r.Memory == nil {
|
|
t.Fatal("Memory should not be nil")
|
|
}
|
|
if r.Memory.Current != 50.0 {
|
|
t.Errorf("Expected memory 50%%, got %f%%", r.Memory.Current)
|
|
}
|
|
|
|
if r.Disk == nil {
|
|
t.Fatal("Disk should not be nil")
|
|
}
|
|
if r.Disk.Current != 40.0 {
|
|
t.Errorf("Expected disk 40%%, got %f%%", r.Disk.Current)
|
|
}
|
|
|
|
if r.Uptime == nil || *r.Uptime != 86400 {
|
|
t.Errorf("Expected uptime 86400, got %v", r.Uptime)
|
|
}
|
|
|
|
// Check cluster info
|
|
if r.ClusterID != "pve-cluster/production" {
|
|
t.Errorf("Expected clusterID pve-cluster/production, got %s", r.ClusterID)
|
|
}
|
|
|
|
// Check identity
|
|
if r.Identity == nil {
|
|
t.Fatal("Identity should not be nil")
|
|
}
|
|
if r.Identity.Hostname != "node1" {
|
|
t.Errorf("Expected hostname node1, got %s", r.Identity.Hostname)
|
|
}
|
|
|
|
// Check platform data
|
|
var pd NodePlatformData
|
|
if err := r.GetPlatformData(&pd); err != nil {
|
|
t.Fatalf("Failed to get platform data: %v", err)
|
|
}
|
|
if pd.PVEVersion != "8.2.2" {
|
|
t.Errorf("Expected PVE version 8.2.2, got %s", pd.PVEVersion)
|
|
}
|
|
if pd.KernelVersion != "6.8.4-2-pve" {
|
|
t.Errorf("Expected kernel version 6.8.4-2-pve, got %s", pd.KernelVersion)
|
|
}
|
|
if len(pd.LoadAverage) != 3 {
|
|
t.Errorf("Expected 3 load average values, got %d", len(pd.LoadAverage))
|
|
}
|
|
}
|
|
|
|
func TestFromVM(t *testing.T) {
|
|
vm := models.VM{
|
|
ID: "pve1/qemu/100",
|
|
VMID: 100,
|
|
Name: "webserver",
|
|
Node: "node1",
|
|
Instance: "pve1",
|
|
Status: "running",
|
|
Type: "qemu",
|
|
CPU: 0.15,
|
|
CPUs: 4,
|
|
Memory: models.Memory{
|
|
Total: 8 * 1024 * 1024 * 1024,
|
|
Used: 4 * 1024 * 1024 * 1024,
|
|
Free: 4 * 1024 * 1024 * 1024,
|
|
Usage: 50.0,
|
|
},
|
|
Disk: models.Disk{
|
|
Total: 100 * 1024 * 1024 * 1024,
|
|
Used: 30 * 1024 * 1024 * 1024,
|
|
Free: 70 * 1024 * 1024 * 1024,
|
|
Usage: 30.0,
|
|
},
|
|
NetworkIn: 1000000,
|
|
NetworkOut: 500000,
|
|
DiskRead: 2000000,
|
|
DiskWrite: 1000000,
|
|
Uptime: 3600,
|
|
Tags: []string{"production", "web"},
|
|
LastSeen: time.Now(),
|
|
}
|
|
|
|
r := FromVM(vm)
|
|
|
|
if r.ID != vm.ID {
|
|
t.Errorf("Expected ID %s, got %s", vm.ID, r.ID)
|
|
}
|
|
if r.Type != ResourceTypeVM {
|
|
t.Errorf("Expected type %s, got %s", ResourceTypeVM, r.Type)
|
|
}
|
|
if r.Status != StatusRunning {
|
|
t.Errorf("Expected status %s, got %s", StatusRunning, r.Status)
|
|
}
|
|
if r.ParentID != "pve1-node1" {
|
|
t.Errorf("Expected parent pve1-node1, got %s", r.ParentID)
|
|
}
|
|
|
|
// Check CPU is converted to percentage
|
|
if r.CPU.Current != 15.0 {
|
|
t.Errorf("Expected CPU 15%%, got %f%%", r.CPU.Current)
|
|
}
|
|
|
|
// Check network
|
|
if r.Network == nil {
|
|
t.Fatal("Network should not be nil")
|
|
}
|
|
if r.Network.RXBytes != 1000000 {
|
|
t.Errorf("Expected RXBytes 1000000, got %d", r.Network.RXBytes)
|
|
}
|
|
if r.Network.TXBytes != 500000 {
|
|
t.Errorf("Expected TXBytes 500000, got %d", r.Network.TXBytes)
|
|
}
|
|
|
|
// Check tags
|
|
if len(r.Tags) != 2 {
|
|
t.Errorf("Expected 2 tags, got %d", len(r.Tags))
|
|
}
|
|
|
|
// Check platform data
|
|
var pd VMPlatformData
|
|
if err := r.GetPlatformData(&pd); err != nil {
|
|
t.Fatalf("Failed to get platform data: %v", err)
|
|
}
|
|
if pd.VMID != 100 {
|
|
t.Errorf("Expected VMID 100, got %d", pd.VMID)
|
|
}
|
|
if pd.CPUs != 4 {
|
|
t.Errorf("Expected 4 CPUs, got %d", pd.CPUs)
|
|
}
|
|
}
|
|
|
|
func TestFromContainer(t *testing.T) {
|
|
ct := models.Container{
|
|
ID: "pve1/lxc/101",
|
|
VMID: 101,
|
|
Name: "database",
|
|
Node: "node1",
|
|
Instance: "pve1",
|
|
Status: "stopped",
|
|
Type: "lxc",
|
|
CPU: 0.0,
|
|
CPUs: 2,
|
|
Memory: models.Memory{
|
|
Total: 4 * 1024 * 1024 * 1024,
|
|
Used: 0,
|
|
Free: 4 * 1024 * 1024 * 1024,
|
|
Usage: 0.0,
|
|
},
|
|
Uptime: 0,
|
|
Tags: []string{"database"},
|
|
IPAddresses: []string{"192.168.1.50"},
|
|
LastSeen: time.Now(),
|
|
}
|
|
|
|
r := FromContainer(ct)
|
|
|
|
if r.Type != ResourceTypeContainer {
|
|
t.Errorf("Expected type %s, got %s", ResourceTypeContainer, r.Type)
|
|
}
|
|
if r.Status != StatusStopped {
|
|
t.Errorf("Expected status %s, got %s", StatusStopped, r.Status)
|
|
}
|
|
if r.ParentID != "pve1-node1" {
|
|
t.Errorf("Expected parent pve1-node1, got %s", r.ParentID)
|
|
}
|
|
|
|
var pd ContainerPlatformData
|
|
if err := r.GetPlatformData(&pd); err != nil {
|
|
t.Fatalf("Failed to get platform data: %v", err)
|
|
}
|
|
if len(pd.IPAddresses) != 1 || pd.IPAddresses[0] != "192.168.1.50" {
|
|
t.Errorf("Expected IP 192.168.1.50, got %v", pd.IPAddresses)
|
|
}
|
|
}
|
|
|
|
func TestFromHost(t *testing.T) {
|
|
host := models.Host{
|
|
ID: "host-abc123",
|
|
Hostname: "standalone-server",
|
|
DisplayName: "Standalone Server",
|
|
Platform: "linux",
|
|
OSName: "Ubuntu",
|
|
OSVersion: "22.04",
|
|
KernelVersion: "5.15.0-generic",
|
|
Architecture: "amd64",
|
|
CPUCount: 8,
|
|
CPUUsage: 45.5,
|
|
Memory: models.Memory{
|
|
Total: 32 * 1024 * 1024 * 1024,
|
|
Used: 16 * 1024 * 1024 * 1024,
|
|
Free: 16 * 1024 * 1024 * 1024,
|
|
Usage: 50.0,
|
|
},
|
|
LoadAverage: []float64{2.5, 2.0, 1.5},
|
|
Disks: []models.Disk{
|
|
{
|
|
Mountpoint: "/",
|
|
Total: 500 * 1024 * 1024 * 1024,
|
|
Used: 200 * 1024 * 1024 * 1024,
|
|
Free: 300 * 1024 * 1024 * 1024,
|
|
Usage: 40.0,
|
|
},
|
|
},
|
|
NetworkInterfaces: []models.HostNetworkInterface{
|
|
{
|
|
Name: "eth0",
|
|
MAC: "aa:bb:cc:dd:ee:ff",
|
|
Addresses: []string{"192.168.1.100", "fe80::1"},
|
|
RXBytes: 10000000,
|
|
TXBytes: 5000000,
|
|
},
|
|
},
|
|
Sensors: models.HostSensorSummary{
|
|
TemperatureCelsius: map[string]float64{
|
|
"cpu_temp": 55.0,
|
|
},
|
|
},
|
|
Status: "online",
|
|
UptimeSeconds: 86400 * 30, // 30 days
|
|
AgentVersion: "1.2.3",
|
|
Tags: []string{"production"},
|
|
LastSeen: time.Now(),
|
|
}
|
|
|
|
r := FromHost(host)
|
|
|
|
if r.Type != ResourceTypeHost {
|
|
t.Errorf("Expected type %s, got %s", ResourceTypeHost, r.Type)
|
|
}
|
|
if r.PlatformType != PlatformHostAgent {
|
|
t.Errorf("Expected platform %s, got %s", PlatformHostAgent, r.PlatformType)
|
|
}
|
|
if r.SourceType != SourceAgent {
|
|
t.Errorf("Expected source %s, got %s", SourceAgent, r.SourceType)
|
|
}
|
|
|
|
// CPU is already a percentage for hosts
|
|
if r.CPU.Current != 45.5 {
|
|
t.Errorf("Expected CPU 45.5%%, got %f%%", r.CPU.Current)
|
|
}
|
|
|
|
// Check temperature
|
|
if r.Temperature == nil || *r.Temperature != 55.0 {
|
|
t.Errorf("Expected temperature 55.0, got %v", r.Temperature)
|
|
}
|
|
|
|
// Check identity includes IP
|
|
if r.Identity == nil {
|
|
t.Fatal("Identity should not be nil")
|
|
}
|
|
if r.Identity.Hostname != "standalone-server" {
|
|
t.Errorf("Expected hostname standalone-server, got %s", r.Identity.Hostname)
|
|
}
|
|
if len(r.Identity.IPs) < 1 {
|
|
t.Error("Expected at least 1 IP in identity")
|
|
}
|
|
|
|
// Check platform data
|
|
var pd HostPlatformData
|
|
if err := r.GetPlatformData(&pd); err != nil {
|
|
t.Fatalf("Failed to get platform data: %v", err)
|
|
}
|
|
if pd.CPUCount != 8 {
|
|
t.Errorf("Expected 8 CPUs, got %d", pd.CPUCount)
|
|
}
|
|
if pd.AgentVersion != "1.2.3" {
|
|
t.Errorf("Expected agent version 1.2.3, got %s", pd.AgentVersion)
|
|
}
|
|
}
|
|
|
|
func TestFromDockerHost(t *testing.T) {
|
|
dh := models.DockerHost{
|
|
ID: "docker-host-1",
|
|
AgentID: "agent-xyz",
|
|
Hostname: "docker-server",
|
|
DisplayName: "Docker Server",
|
|
MachineID: "machine-id-123",
|
|
OS: "linux",
|
|
Architecture: "amd64",
|
|
Runtime: "docker",
|
|
DockerVersion: "24.0.5",
|
|
CPUs: 16,
|
|
CPUUsage: 35.0,
|
|
Memory: models.Memory{
|
|
Total: 64 * 1024 * 1024 * 1024,
|
|
Used: 32 * 1024 * 1024 * 1024,
|
|
Free: 32 * 1024 * 1024 * 1024,
|
|
Usage: 50.0,
|
|
},
|
|
UptimeSeconds: 86400,
|
|
Status: "online",
|
|
LastSeen: time.Now(),
|
|
Swarm: &models.DockerSwarmInfo{
|
|
NodeID: "swarm-node-1",
|
|
NodeRole: "manager",
|
|
ClusterID: "swarm-cluster-1",
|
|
ClusterName: "production-swarm",
|
|
},
|
|
NetworkInterfaces: []models.HostNetworkInterface{
|
|
{
|
|
Name: "docker0",
|
|
Addresses: []string{"172.17.0.1"},
|
|
},
|
|
},
|
|
}
|
|
|
|
r := FromDockerHost(dh)
|
|
|
|
if r.Type != ResourceTypeDockerHost {
|
|
t.Errorf("Expected type %s, got %s", ResourceTypeDockerHost, r.Type)
|
|
}
|
|
if r.PlatformType != PlatformDocker {
|
|
t.Errorf("Expected platform %s, got %s", PlatformDocker, r.PlatformType)
|
|
}
|
|
|
|
// Check cluster ID from swarm
|
|
if r.ClusterID != "docker-swarm/swarm-cluster-1" {
|
|
t.Errorf("Expected clusterID docker-swarm/swarm-cluster-1, got %s", r.ClusterID)
|
|
}
|
|
|
|
// Check identity includes machine ID
|
|
if r.Identity == nil {
|
|
t.Fatal("Identity should not be nil")
|
|
}
|
|
if r.Identity.MachineID != "machine-id-123" {
|
|
t.Errorf("Expected machineID machine-id-123, got %s", r.Identity.MachineID)
|
|
}
|
|
|
|
var pd DockerHostPlatformData
|
|
if err := r.GetPlatformData(&pd); err != nil {
|
|
t.Fatalf("Failed to get platform data: %v", err)
|
|
}
|
|
if pd.DockerVersion != "24.0.5" {
|
|
t.Errorf("Expected Docker version 24.0.5, got %s", pd.DockerVersion)
|
|
}
|
|
if pd.Swarm == nil || pd.Swarm.NodeRole != "manager" {
|
|
t.Error("Expected swarm info with manager role")
|
|
}
|
|
}
|
|
|
|
func TestFromDockerContainer(t *testing.T) {
|
|
dc := models.DockerContainer{
|
|
ID: "container-123",
|
|
Name: "nginx",
|
|
Image: "nginx:latest",
|
|
State: "running",
|
|
Status: "Up 2 hours",
|
|
Health: "healthy",
|
|
CPUPercent: 5.5,
|
|
MemoryUsage: 256 * 1024 * 1024, // 256MB
|
|
MemoryLimit: 512 * 1024 * 1024, // 512MB
|
|
MemoryPercent: 50.0,
|
|
UptimeSeconds: 7200,
|
|
RestartCount: 0,
|
|
CreatedAt: time.Now().Add(-24 * time.Hour),
|
|
Labels: map[string]string{
|
|
"app": "web",
|
|
},
|
|
Ports: []models.DockerContainerPort{
|
|
{PrivatePort: 80, PublicPort: 8080, Protocol: "tcp"},
|
|
},
|
|
}
|
|
|
|
r := FromDockerContainer(dc, "docker-host-1", "docker-server")
|
|
|
|
if r.Type != ResourceTypeDockerContainer {
|
|
t.Errorf("Expected type %s, got %s", ResourceTypeDockerContainer, r.Type)
|
|
}
|
|
if r.ParentID != "docker-host-1" {
|
|
t.Errorf("Expected parent docker-host-1, got %s", r.ParentID)
|
|
}
|
|
if r.Status != StatusRunning {
|
|
t.Errorf("Expected status %s, got %s", StatusRunning, r.Status)
|
|
}
|
|
|
|
if r.CPU.Current != 5.5 {
|
|
t.Errorf("Expected CPU 5.5%%, got %f%%", r.CPU.Current)
|
|
}
|
|
if r.Memory == nil || r.Memory.Current != 50.0 {
|
|
t.Errorf("Expected memory 50%%, got %v", r.Memory)
|
|
}
|
|
|
|
if r.Labels["app"] != "web" {
|
|
t.Error("Expected label app=web")
|
|
}
|
|
|
|
var pd DockerContainerPlatformData
|
|
if err := r.GetPlatformData(&pd); err != nil {
|
|
t.Fatalf("Failed to get platform data: %v", err)
|
|
}
|
|
if pd.Image != "nginx:latest" {
|
|
t.Errorf("Expected image nginx:latest, got %s", pd.Image)
|
|
}
|
|
if pd.Health != "healthy" {
|
|
t.Errorf("Expected health healthy, got %s", pd.Health)
|
|
}
|
|
if len(pd.Ports) != 1 || pd.Ports[0].PublicPort != 8080 {
|
|
t.Error("Expected port 8080 mapping")
|
|
}
|
|
}
|
|
|
|
func TestResourceMethods(t *testing.T) {
|
|
// Test IsInfrastructure
|
|
nodeResource := Resource{Type: ResourceTypeNode}
|
|
if !nodeResource.IsInfrastructure() {
|
|
t.Error("Node should be infrastructure")
|
|
}
|
|
if nodeResource.IsWorkload() {
|
|
t.Error("Node should not be workload")
|
|
}
|
|
|
|
vmResource := Resource{Type: ResourceTypeVM}
|
|
if vmResource.IsInfrastructure() {
|
|
t.Error("VM should not be infrastructure")
|
|
}
|
|
if !vmResource.IsWorkload() {
|
|
t.Error("VM should be workload")
|
|
}
|
|
|
|
// Test EffectiveDisplayName
|
|
r := Resource{Name: "name", DisplayName: ""}
|
|
if r.EffectiveDisplayName() != "name" {
|
|
t.Error("Should return Name when DisplayName is empty")
|
|
}
|
|
r.DisplayName = "custom"
|
|
if r.EffectiveDisplayName() != "custom" {
|
|
t.Error("Should return DisplayName when set")
|
|
}
|
|
|
|
// Test CPUPercent
|
|
r = Resource{}
|
|
if r.CPUPercent() != 0 {
|
|
t.Error("CPUPercent should be 0 when CPU is nil")
|
|
}
|
|
r.CPU = &MetricValue{Current: 75.5}
|
|
if r.CPUPercent() != 75.5 {
|
|
t.Errorf("Expected 75.5, got %f", r.CPUPercent())
|
|
}
|
|
|
|
// Test MemoryPercent
|
|
r = Resource{}
|
|
if r.MemoryPercent() != 0 {
|
|
t.Error("MemoryPercent should be 0 when Memory is nil")
|
|
}
|
|
r.Memory = &MetricValue{Current: 60.0}
|
|
if r.MemoryPercent() != 60.0 {
|
|
t.Errorf("Expected 60.0, got %f", r.MemoryPercent())
|
|
}
|
|
}
|
|
|
|
func TestStatusMapping(t *testing.T) {
|
|
tests := []struct {
|
|
input string
|
|
expected ResourceStatus
|
|
mapper func(string) ResourceStatus
|
|
}{
|
|
{"online", StatusOnline, mapNodeStatus},
|
|
{"offline", StatusOffline, mapNodeStatus},
|
|
{"unknown", StatusUnknown, mapNodeStatus},
|
|
{"running", StatusRunning, mapGuestStatus},
|
|
{"stopped", StatusStopped, mapGuestStatus},
|
|
{"paused", StatusPaused, mapGuestStatus},
|
|
{"online", StatusOnline, mapHostStatus},
|
|
{"degraded", StatusDegraded, mapHostStatus},
|
|
{"running", StatusRunning, mapDockerContainerStatus},
|
|
{"exited", StatusStopped, mapDockerContainerStatus},
|
|
{"dead", StatusStopped, mapDockerContainerStatus},
|
|
{"paused", StatusPaused, mapDockerContainerStatus},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
result := test.mapper(test.input)
|
|
if result != test.expected {
|
|
t.Errorf("Mapping %s: expected %s, got %s", test.input, test.expected, result)
|
|
}
|
|
}
|
|
}
|