From 12a63ffdc2fbc3ccf07ef9edd9c9bcd8f5fbef22 Mon Sep 17 00:00:00 2001 From: rcourtman Date: Wed, 26 Nov 2025 14:16:57 +0000 Subject: [PATCH] test: add unit tests for pkg/agents/host --- pkg/agents/host/report_test.go | 391 +++++++++++++++++++++++++++++++++ 1 file changed, 391 insertions(+) create mode 100644 pkg/agents/host/report_test.go diff --git a/pkg/agents/host/report_test.go b/pkg/agents/host/report_test.go new file mode 100644 index 000000000..8670a2c14 --- /dev/null +++ b/pkg/agents/host/report_test.go @@ -0,0 +1,391 @@ +package host + +import ( + "encoding/json" + "testing" + "time" +) + +func TestReport_JSONMarshal(t *testing.T) { + report := Report{ + Agent: AgentInfo{ + ID: "agent-1", + Version: "1.0.0", + Type: "unified", + IntervalSeconds: 30, + Hostname: "myhost", + }, + Host: HostInfo{ + ID: "host-1", + Hostname: "myhost", + DisplayName: "My Host", + Platform: "linux", + CPUCount: 4, + UptimeSeconds: 86400, + LoadAverage: []float64{0.5, 0.7, 0.9}, + }, + Metrics: Metrics{ + CPUUsagePercent: 25.5, + Memory: MemoryMetric{ + TotalBytes: 16000000000, + UsedBytes: 8000000000, + FreeBytes: 8000000000, + Usage: 50.0, + }, + }, + Timestamp: time.Now(), + } + + data, err := json.Marshal(report) + if err != nil { + t.Fatalf("Failed to marshal Report: %v", err) + } + + var decoded Report + if err := json.Unmarshal(data, &decoded); err != nil { + t.Fatalf("Failed to unmarshal Report: %v", err) + } + + if decoded.Agent.ID != report.Agent.ID { + t.Errorf("Agent.ID = %q, want %q", decoded.Agent.ID, report.Agent.ID) + } + if decoded.Host.Hostname != report.Host.Hostname { + t.Errorf("Host.Hostname = %q, want %q", decoded.Host.Hostname, report.Host.Hostname) + } + if decoded.Metrics.CPUUsagePercent != report.Metrics.CPUUsagePercent { + t.Errorf("Metrics.CPUUsagePercent = %f, want %f", decoded.Metrics.CPUUsagePercent, report.Metrics.CPUUsagePercent) + } +} + +func TestAgentInfo_Fields(t *testing.T) { + agent := AgentInfo{ + ID: "agent-123", + Version: "2.0.0", + Type: "host", + IntervalSeconds: 60, + Hostname: "server1", + } + + if agent.ID != "agent-123" { + t.Errorf("ID = %q, want agent-123", agent.ID) + } + if agent.Version != "2.0.0" { + t.Errorf("Version = %q, want 2.0.0", agent.Version) + } + if agent.Type != "host" { + t.Errorf("Type = %q, want host", agent.Type) + } + if agent.IntervalSeconds != 60 { + t.Errorf("IntervalSeconds = %d, want 60", agent.IntervalSeconds) + } + if agent.Hostname != "server1" { + t.Errorf("Hostname = %q, want server1", agent.Hostname) + } +} + +func TestHostInfo_Fields(t *testing.T) { + host := HostInfo{ + ID: "host-456", + Hostname: "myserver", + DisplayName: "My Server", + MachineID: "abc123", + Platform: "linux", + OSName: "Ubuntu", + OSVersion: "22.04", + KernelVersion: "5.15.0", + Architecture: "x86_64", + CPUModel: "Intel Core i7", + CPUCount: 8, + UptimeSeconds: 172800, + LoadAverage: []float64{1.0, 0.8, 0.6}, + } + + if host.ID != "host-456" { + t.Errorf("ID = %q, want host-456", host.ID) + } + if host.Platform != "linux" { + t.Errorf("Platform = %q, want linux", host.Platform) + } + if host.CPUCount != 8 { + t.Errorf("CPUCount = %d, want 8", host.CPUCount) + } + if len(host.LoadAverage) != 3 { + t.Errorf("LoadAverage length = %d, want 3", len(host.LoadAverage)) + } +} + +func TestMemoryMetric_Fields(t *testing.T) { + mem := MemoryMetric{ + TotalBytes: 32000000000, + UsedBytes: 16000000000, + FreeBytes: 16000000000, + Usage: 50.0, + SwapTotal: 8000000000, + SwapUsed: 2000000000, + } + + if mem.TotalBytes != 32000000000 { + t.Errorf("TotalBytes = %d, want 32000000000", mem.TotalBytes) + } + if mem.Usage != 50.0 { + t.Errorf("Usage = %f, want 50.0", mem.Usage) + } + if mem.SwapTotal != 8000000000 { + t.Errorf("SwapTotal = %d, want 8000000000", mem.SwapTotal) + } +} + +func TestDisk_Fields(t *testing.T) { + disk := Disk{ + Device: "/dev/sda1", + Mountpoint: "/", + Filesystem: "ext4", + Type: "ext4", + TotalBytes: 500000000000, + UsedBytes: 250000000000, + FreeBytes: 250000000000, + Usage: 50.0, + } + + if disk.Device != "/dev/sda1" { + t.Errorf("Device = %q, want /dev/sda1", disk.Device) + } + if disk.Mountpoint != "/" { + t.Errorf("Mountpoint = %q, want /", disk.Mountpoint) + } + if disk.Usage != 50.0 { + t.Errorf("Usage = %f, want 50.0", disk.Usage) + } +} + +func TestNetworkInterface_Fields(t *testing.T) { + speed := int64(1000) + net := NetworkInterface{ + Name: "eth0", + MAC: "00:11:22:33:44:55", + Addresses: []string{"192.168.1.100", "fe80::1"}, + RXBytes: 1000000, + TXBytes: 500000, + SpeedMbps: &speed, + } + + if net.Name != "eth0" { + t.Errorf("Name = %q, want eth0", net.Name) + } + if net.MAC != "00:11:22:33:44:55" { + t.Errorf("MAC = %q, want 00:11:22:33:44:55", net.MAC) + } + if len(net.Addresses) != 2 { + t.Errorf("Addresses length = %d, want 2", len(net.Addresses)) + } + if *net.SpeedMbps != 1000 { + t.Errorf("SpeedMbps = %d, want 1000", *net.SpeedMbps) + } +} + +func TestSensors_Fields(t *testing.T) { + sensors := Sensors{ + TemperatureCelsius: map[string]float64{ + "cpu_package": 45.0, + "cpu_core_0": 43.0, + "cpu_core_1": 44.0, + }, + FanRPM: map[string]float64{ + "cpu_fan": 1200.0, + }, + Additional: map[string]float64{ + "voltage": 12.0, + }, + } + + if len(sensors.TemperatureCelsius) != 3 { + t.Errorf("TemperatureCelsius count = %d, want 3", len(sensors.TemperatureCelsius)) + } + if sensors.TemperatureCelsius["cpu_package"] != 45.0 { + t.Errorf("cpu_package temp = %f, want 45.0", sensors.TemperatureCelsius["cpu_package"]) + } + if sensors.FanRPM["cpu_fan"] != 1200.0 { + t.Errorf("cpu_fan RPM = %f, want 1200.0", sensors.FanRPM["cpu_fan"]) + } +} + +func TestRAIDArray_Fields(t *testing.T) { + array := RAIDArray{ + Device: "/dev/md0", + Name: "myarray", + Level: "raid1", + State: "clean", + TotalDevices: 2, + ActiveDevices: 2, + WorkingDevices: 2, + FailedDevices: 0, + SpareDevices: 0, + UUID: "12345678:90abcdef:12345678:90abcdef", + RebuildPercent: 0, + RebuildSpeed: "", + Devices: []RAIDDevice{ + {Device: "/dev/sda1", State: "active sync", Slot: 0}, + {Device: "/dev/sdb1", State: "active sync", Slot: 1}, + }, + } + + if array.Device != "/dev/md0" { + t.Errorf("Device = %q, want /dev/md0", array.Device) + } + if array.Level != "raid1" { + t.Errorf("Level = %q, want raid1", array.Level) + } + if array.State != "clean" { + t.Errorf("State = %q, want clean", array.State) + } + if len(array.Devices) != 2 { + t.Errorf("Devices count = %d, want 2", len(array.Devices)) + } +} + +func TestRAIDDevice_Fields(t *testing.T) { + device := RAIDDevice{ + Device: "/dev/sda1", + State: "active sync", + Slot: 0, + } + + if device.Device != "/dev/sda1" { + t.Errorf("Device = %q, want /dev/sda1", device.Device) + } + if device.State != "active sync" { + t.Errorf("State = %q, want active sync", device.State) + } + if device.Slot != 0 { + t.Errorf("Slot = %d, want 0", device.Slot) + } +} + +func TestRAIDArray_Degraded(t *testing.T) { + array := RAIDArray{ + Device: "/dev/md1", + Level: "raid5", + State: "clean, degraded", + TotalDevices: 3, + ActiveDevices: 2, + WorkingDevices: 2, + FailedDevices: 1, + SpareDevices: 0, + Devices: []RAIDDevice{ + {Device: "/dev/sda1", State: "active sync", Slot: 0}, + {Device: "/dev/sdb1", State: "faulty", Slot: -1}, + {Device: "/dev/sdc1", State: "active sync", Slot: 2}, + }, + } + + if array.FailedDevices != 1 { + t.Errorf("FailedDevices = %d, want 1", array.FailedDevices) + } + if array.State != "clean, degraded" { + t.Errorf("State = %q, want 'clean, degraded'", array.State) + } +} + +func TestRAIDArray_Rebuilding(t *testing.T) { + array := RAIDArray{ + Device: "/dev/md2", + Level: "raid6", + State: "active, recovering", + RebuildPercent: 42.5, + RebuildSpeed: "50000K/sec", + } + + if array.RebuildPercent != 42.5 { + t.Errorf("RebuildPercent = %f, want 42.5", array.RebuildPercent) + } + if array.RebuildSpeed != "50000K/sec" { + t.Errorf("RebuildSpeed = %q, want 50000K/sec", array.RebuildSpeed) + } +} + +func TestReport_WithDisksAndNetwork(t *testing.T) { + report := Report{ + Disks: []Disk{ + {Device: "/dev/sda1", Mountpoint: "/", TotalBytes: 500000000000}, + {Device: "/dev/sdb1", Mountpoint: "/data", TotalBytes: 1000000000000}, + }, + Network: []NetworkInterface{ + {Name: "eth0", RXBytes: 1000000, TXBytes: 500000}, + {Name: "eth1", RXBytes: 2000000, TXBytes: 1000000}, + }, + } + + if len(report.Disks) != 2 { + t.Errorf("Disks count = %d, want 2", len(report.Disks)) + } + if len(report.Network) != 2 { + t.Errorf("Network count = %d, want 2", len(report.Network)) + } +} + +func TestReport_WithSensorsAndRAID(t *testing.T) { + report := Report{ + Sensors: Sensors{ + TemperatureCelsius: map[string]float64{"cpu": 45.0}, + }, + RAID: []RAIDArray{ + {Device: "/dev/md0", Level: "raid1", State: "clean"}, + }, + } + + if len(report.Sensors.TemperatureCelsius) != 1 { + t.Errorf("TemperatureCelsius count = %d, want 1", len(report.Sensors.TemperatureCelsius)) + } + if len(report.RAID) != 1 { + t.Errorf("RAID count = %d, want 1", len(report.RAID)) + } +} + +func TestReport_Tags(t *testing.T) { + report := Report{ + Tags: []string{"production", "web", "us-east"}, + } + + if len(report.Tags) != 3 { + t.Errorf("Tags count = %d, want 3", len(report.Tags)) + } + if report.Tags[0] != "production" { + t.Errorf("Tags[0] = %q, want production", report.Tags[0]) + } +} + +func TestReport_JSONOmitEmpty(t *testing.T) { + // Test that empty fields are omitted in JSON + report := Report{ + Agent: AgentInfo{ID: "agent-1"}, + Host: HostInfo{Hostname: "myhost"}, + } + + data, err := json.Marshal(report) + if err != nil { + t.Fatalf("Failed to marshal: %v", err) + } + + jsonStr := string(data) + + // These should not appear in JSON when empty + if contains(jsonStr, `"disks":[]`) { + t.Error("Empty disks should be omitted") + } + if contains(jsonStr, `"network":[]`) { + t.Error("Empty network should be omitted") + } + if contains(jsonStr, `"tags":[]`) { + t.Error("Empty tags should be omitted") + } +} + +// Helper function +func contains(s, substr string) bool { + for i := 0; i <= len(s)-len(substr); i++ { + if s[i:i+len(substr)] == substr { + return true + } + } + return false +}