package monitoring import ( "math" "testing" "github.com/rcourtman/pulse-go-rewrite/internal/models" ) func TestMergeNVMeTempsIntoDisks(t *testing.T) { tests := []struct { name string disks []models.PhysicalDisk nodes []models.Node expected []models.PhysicalDisk }{ { name: "empty disks returns empty", disks: []models.PhysicalDisk{}, nodes: []models.Node{{Name: "node1", Temperature: &models.Temperature{Available: true}}}, expected: []models.PhysicalDisk{}, }, { name: "empty nodes returns disks unchanged", disks: []models.PhysicalDisk{{Node: "node1", DevPath: "/dev/sda", Temperature: 0}}, nodes: []models.Node{}, expected: []models.PhysicalDisk{{Node: "node1", DevPath: "/dev/sda", Temperature: 0}}, }, { name: "nil temperature returns disks unchanged", disks: []models.PhysicalDisk{{Node: "node1", DevPath: "/dev/sda", Temperature: 0}}, nodes: []models.Node{{Name: "node1", Temperature: nil}}, expected: []models.PhysicalDisk{{Node: "node1", DevPath: "/dev/sda", Temperature: 0}}, }, { name: "temperature available false returns disks unchanged", disks: []models.PhysicalDisk{{Node: "node1", DevPath: "/dev/sda", Temperature: 0}}, nodes: []models.Node{{Name: "node1", Temperature: &models.Temperature{Available: false}}}, expected: []models.PhysicalDisk{{Node: "node1", DevPath: "/dev/sda", Temperature: 0}}, }, { name: "no SMART or NVMe temps returns disks unchanged", disks: []models.PhysicalDisk{{Node: "node1", DevPath: "/dev/sda", Temperature: 0}}, nodes: []models.Node{{Name: "node1", Temperature: &models.Temperature{Available: true, SMART: nil, NVMe: nil}}}, expected: []models.PhysicalDisk{{Node: "node1", DevPath: "/dev/sda", Temperature: 0}}, }, { name: "SMART temperature matched by WWN", disks: []models.PhysicalDisk{ {Node: "node1", DevPath: "/dev/sda", WWN: "5000c5001234abcd", Temperature: 0}, }, nodes: []models.Node{ { Name: "node1", Temperature: &models.Temperature{ Available: true, SMART: []models.DiskTemp{ {Device: "/dev/sda", WWN: "5000C5001234ABCD", Temperature: 42}, }, }, }, }, expected: []models.PhysicalDisk{ {Node: "node1", DevPath: "/dev/sda", WWN: "5000c5001234abcd", Temperature: 42}, }, }, { name: "SMART temperature matched by serial case insensitive", disks: []models.PhysicalDisk{ {Node: "node1", DevPath: "/dev/sdb", Serial: "WD-ABC123", Temperature: 0}, }, nodes: []models.Node{ { Name: "node1", Temperature: &models.Temperature{ Available: true, SMART: []models.DiskTemp{ {Device: "/dev/sdb", Serial: "wd-abc123", Temperature: 38}, }, }, }, }, expected: []models.PhysicalDisk{ {Node: "node1", DevPath: "/dev/sdb", Serial: "WD-ABC123", Temperature: 38}, }, }, { name: "SMART temperature matched by device path", disks: []models.PhysicalDisk{ {Node: "node1", DevPath: "/dev/sdc", Temperature: 0}, }, nodes: []models.Node{ { Name: "node1", Temperature: &models.Temperature{ Available: true, SMART: []models.DiskTemp{ {Device: "/dev/sdc", Temperature: 35}, }, }, }, }, expected: []models.PhysicalDisk{ {Node: "node1", DevPath: "/dev/sdc", Temperature: 35}, }, }, { name: "NVMe legacy fallback when no SMART match", disks: []models.PhysicalDisk{ {Node: "node1", DevPath: "/dev/nvme0n1", Type: "nvme", Temperature: 0}, }, nodes: []models.Node{ { Name: "node1", Temperature: &models.Temperature{ Available: true, NVMe: []models.NVMeTemp{ {Device: "nvme0", Temp: 45.5}, }, }, }, }, expected: []models.PhysicalDisk{ {Node: "node1", DevPath: "/dev/nvme0n1", Type: "nvme", Temperature: 46}, }, }, { name: "temperature zero is not applied", disks: []models.PhysicalDisk{ {Node: "node1", DevPath: "/dev/sda", WWN: "5000c500", Temperature: 0}, }, nodes: []models.Node{ { Name: "node1", Temperature: &models.Temperature{ Available: true, SMART: []models.DiskTemp{ {Device: "/dev/sda", WWN: "5000c500", Temperature: 0}, }, }, }, }, expected: []models.PhysicalDisk{ {Node: "node1", DevPath: "/dev/sda", WWN: "5000c500", Temperature: 0}, }, }, { name: "standby skipped is not applied", disks: []models.PhysicalDisk{ {Node: "node1", DevPath: "/dev/sda", WWN: "5000c500", Temperature: 0}, }, nodes: []models.Node{ { Name: "node1", Temperature: &models.Temperature{ Available: true, SMART: []models.DiskTemp{ {Device: "/dev/sda", WWN: "5000c500", Temperature: 40, StandbySkipped: true}, }, }, }, }, expected: []models.PhysicalDisk{ {Node: "node1", DevPath: "/dev/sda", WWN: "5000c500", Temperature: 0}, }, }, { name: "multiple nodes multiple disks with various matches", disks: []models.PhysicalDisk{ {Node: "node1", DevPath: "/dev/sda", WWN: "wwn1", Temperature: 0}, {Node: "node1", DevPath: "/dev/sdb", Serial: "SERIAL2", Temperature: 0}, {Node: "node1", DevPath: "/dev/nvme0n1", Type: "nvme", Temperature: 0}, {Node: "node2", DevPath: "/dev/sda", Temperature: 0}, {Node: "node2", DevPath: "/dev/nvme0n1", Type: "nvme", Temperature: 0}, {Node: "node2", DevPath: "/dev/nvme1n1", Type: "nvme", Temperature: 0}, }, nodes: []models.Node{ { Name: "node1", Temperature: &models.Temperature{ Available: true, SMART: []models.DiskTemp{ {Device: "/dev/sda", WWN: "WWN1", Temperature: 41}, {Device: "/dev/sdb", Serial: "serial2", Temperature: 42}, }, NVMe: []models.NVMeTemp{ {Device: "nvme0", Temp: 50.2}, }, }, }, { Name: "node2", Temperature: &models.Temperature{ Available: true, SMART: []models.DiskTemp{ {Device: "/dev/sda", Temperature: 43}, }, NVMe: []models.NVMeTemp{ {Device: "nvme0", Temp: 51.8}, {Device: "nvme1", Temp: 52.3}, }, }, }, }, expected: []models.PhysicalDisk{ {Node: "node1", DevPath: "/dev/sda", WWN: "wwn1", Temperature: 41}, {Node: "node1", DevPath: "/dev/sdb", Serial: "SERIAL2", Temperature: 42}, {Node: "node1", DevPath: "/dev/nvme0n1", Type: "nvme", Temperature: 50}, {Node: "node2", DevPath: "/dev/sda", Temperature: 43}, {Node: "node2", DevPath: "/dev/nvme0n1", Type: "nvme", Temperature: 52}, {Node: "node2", DevPath: "/dev/nvme1n1", Type: "nvme", Temperature: 52}, }, }, { name: "NaN temperature is not applied", disks: []models.PhysicalDisk{ {Node: "node1", DevPath: "/dev/nvme0n1", Type: "nvme", Temperature: 0}, }, nodes: []models.Node{ { Name: "node1", Temperature: &models.Temperature{ Available: true, NVMe: []models.NVMeTemp{ {Device: "nvme0", Temp: math.NaN()}, }, }, }, }, expected: []models.PhysicalDisk{ {Node: "node1", DevPath: "/dev/nvme0n1", Type: "nvme", Temperature: 0}, }, }, { name: "NVMe disk with no legacy temps for node (continue branch)", disks: []models.PhysicalDisk{ {Node: "node1", DevPath: "/dev/nvme0n1", Type: "nvme", Temperature: 0}, {Node: "node2", DevPath: "/dev/nvme0n1", Type: "nvme", Temperature: 0}, }, nodes: []models.Node{ { Name: "node1", Temperature: &models.Temperature{ Available: true, NVMe: []models.NVMeTemp{ {Device: "nvme0", Temp: 45.0}, }, }, }, { Name: "node2", Temperature: &models.Temperature{ Available: true, // No NVMe temps for node2, but SMART is empty too }, }, }, expected: []models.PhysicalDisk{ {Node: "node1", DevPath: "/dev/nvme0n1", Type: "nvme", Temperature: 45}, {Node: "node2", DevPath: "/dev/nvme0n1", Type: "nvme", Temperature: 0}, }, }, { name: "more NVMe disks than temps (break branch)", disks: []models.PhysicalDisk{ {Node: "node1", DevPath: "/dev/nvme0n1", Type: "nvme", Temperature: 0}, {Node: "node1", DevPath: "/dev/nvme1n1", Type: "nvme", Temperature: 0}, {Node: "node1", DevPath: "/dev/nvme2n1", Type: "nvme", Temperature: 0}, }, nodes: []models.Node{ { Name: "node1", Temperature: &models.Temperature{ Available: true, NVMe: []models.NVMeTemp{ {Device: "nvme0", Temp: 40.0}, }, }, }, }, expected: []models.PhysicalDisk{ {Node: "node1", DevPath: "/dev/nvme0n1", Type: "nvme", Temperature: 40}, {Node: "node1", DevPath: "/dev/nvme1n1", Type: "nvme", Temperature: 0}, {Node: "node1", DevPath: "/dev/nvme2n1", Type: "nvme", Temperature: 0}, }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result := mergeNVMeTempsIntoDisks(tt.disks, tt.nodes) if len(result) != len(tt.expected) { t.Fatalf("got %d disks, want %d", len(result), len(tt.expected)) } for i := range result { if result[i].Temperature != tt.expected[i].Temperature { t.Errorf("disk[%d] %s: got temperature %d, want %d", i, result[i].DevPath, result[i].Temperature, tt.expected[i].Temperature) } } }) } } func TestMergeNVMeTempsIntoDisks_OriginalSliceUnchanged(t *testing.T) { original := []models.PhysicalDisk{ {Node: "node1", DevPath: "/dev/nvme0n1", Type: "nvme", Temperature: 0}, } nodes := []models.Node{ { Name: "node1", Temperature: &models.Temperature{ Available: true, NVMe: []models.NVMeTemp{ {Device: "nvme0", Temp: 45.0}, }, }, }, } result := mergeNVMeTempsIntoDisks(original, nodes) if result[0].Temperature != 45 { t.Errorf("merged disk temperature = %d, want 45", result[0].Temperature) } if original[0].Temperature != 0 { t.Errorf("original disk temperature was modified: got %d, want 0", original[0].Temperature) } }