Pulse/internal/resources/converters_test.go
rcourtman 3fdf753a5b Enhance devcontainer and CI workflows
- Add persistent volume mounts for Go/npm caches (faster rebuilds)
- Add shell config with helpful aliases and custom prompt
- Add comprehensive devcontainer documentation
- Add pre-commit hooks for Go formatting and linting
- Use go-version-file in CI workflows instead of hardcoded versions
- Simplify docker compose commands with --wait flag
- Add gitignore entries for devcontainer auth files

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-01 22:29:15 +00:00

598 lines
16 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) {
now := time.Now()
t.Run("lxc", func(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: 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 pd.Type != "lxc" {
t.Errorf("Expected platformData type lxc, got %q", pd.Type)
}
if pd.IsOCI {
t.Errorf("Expected IsOCI false, got %v", pd.IsOCI)
}
if len(pd.IPAddresses) != 1 || pd.IPAddresses[0] != "192.168.1.50" {
t.Errorf("Expected IP 192.168.1.50, got %v", pd.IPAddresses)
}
})
t.Run("oci", func(t *testing.T) {
ct := models.Container{
ID: "pve1/lxc/300",
VMID: 300,
Name: "oci-alpine",
Node: "node1",
Instance: "pve1",
Status: "running",
Type: "oci",
IsOCI: true,
OSTemplate: "oci:docker.io/library/alpine:latest",
CPU: 0.01,
CPUs: 1,
Memory: models.Memory{
Total: 1024 * 1024 * 1024,
Used: 128 * 1024 * 1024,
Free: 896 * 1024 * 1024,
Usage: 12.5,
},
LastSeen: now,
}
r := FromContainer(ct)
if r.Type != ResourceTypeOCIContainer {
t.Errorf("Expected type %s, got %s", ResourceTypeOCIContainer, r.Type)
}
var pd ContainerPlatformData
if err := r.GetPlatformData(&pd); err != nil {
t.Fatalf("Failed to get platform data: %v", err)
}
if !pd.IsOCI {
t.Errorf("Expected IsOCI true, got %v", pd.IsOCI)
}
if pd.Type != "oci" {
t.Errorf("Expected platformData type oci, got %q", pd.Type)
}
if pd.OSTemplate != ct.OSTemplate {
t.Errorf("Expected OSTemplate %q, got %q", ct.OSTemplate, pd.OSTemplate)
}
})
}
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)
}
}
}