Files
logpile/internal/exporter/reanimator_converter_test.go
2026-04-01 17:49:00 +03:00

1980 lines
58 KiB
Go

package exporter
import (
"encoding/json"
"strings"
"testing"
"time"
"git.mchus.pro/mchus/logpile/internal/models"
)
func TestConvertToReanimator(t *testing.T) {
tests := []struct {
name string
input *models.AnalysisResult
wantErr bool
errMsg string
}{
{
name: "nil result",
input: nil,
wantErr: true,
errMsg: "no data available",
},
{
name: "no hardware",
input: &models.AnalysisResult{
Filename: "test.json",
},
wantErr: true,
errMsg: "no hardware data available",
},
{
name: "no board serial",
input: &models.AnalysisResult{
Filename: "test.json",
Hardware: &models.HardwareConfig{
BoardInfo: models.BoardInfo{},
},
},
wantErr: true,
errMsg: "board serial_number is required",
},
{
name: "valid minimal data",
input: &models.AnalysisResult{
Filename: "test.json",
SourceType: "api",
Protocol: "redfish",
TargetHost: "10.10.10.10",
CollectedAt: time.Date(2026, 2, 10, 15, 30, 0, 0, time.UTC),
Hardware: &models.HardwareConfig{
BoardInfo: models.BoardInfo{
Manufacturer: "Supermicro",
ProductName: "X12DPG-QT6",
SerialNumber: "TEST123",
},
},
},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result, err := ConvertToReanimator(tt.input)
if tt.wantErr {
if err == nil {
t.Errorf("expected error containing %q, got nil", tt.errMsg)
}
return
}
if err != nil {
t.Errorf("unexpected error: %v", err)
return
}
if result == nil {
t.Error("expected non-nil result")
return
}
if result.Hardware.Board.SerialNumber != tt.input.Hardware.BoardInfo.SerialNumber {
t.Errorf("board serial mismatch: got %q, want %q",
result.Hardware.Board.SerialNumber,
tt.input.Hardware.BoardInfo.SerialNumber)
}
})
}
}
func TestInferCPUManufacturer(t *testing.T) {
tests := []struct {
model string
want string
}{
{"INTEL(R) XEON(R) GOLD 6530", "Intel"},
{"Intel Core i9-12900K", "Intel"},
{"AMD EPYC 7763", "AMD"},
{"AMD Ryzen 9 5950X", "AMD"},
{"ARM Cortex-A78", "ARM"},
{"Ampere Altra Max", "Ampere"},
{"Unknown CPU Model", ""},
}
for _, tt := range tests {
t.Run(tt.model, func(t *testing.T) {
got := inferCPUManufacturer(tt.model)
if got != tt.want {
t.Errorf("inferCPUManufacturer(%q) = %q, want %q", tt.model, got, tt.want)
}
})
}
}
func TestNormalizedSerial(t *testing.T) {
tests := []struct {
name string
in string
want string
}{
{
name: "empty",
in: "",
want: "",
},
{
name: "n_a",
in: "N/A",
want: "",
},
{
name: "unknown",
in: "unknown",
want: "",
},
{
name: "normal",
in: "SN123",
want: "SN123",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := normalizedSerial(tt.in)
if got != tt.want {
t.Errorf("normalizedSerial() = %q, want %q", got, tt.want)
}
})
}
}
func TestInferStorageStatus(t *testing.T) {
tests := []struct {
name string
stor models.Storage
want string
}{
{
name: "present",
stor: models.Storage{
Present: true,
},
want: "Unknown",
},
{
name: "not present",
stor: models.Storage{
Present: false,
},
want: "Unknown",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := inferStorageStatus(tt.stor)
if got != tt.want {
t.Errorf("inferStorageStatus() = %q, want %q", got, tt.want)
}
})
}
}
func TestNormalizeStatus_PassFail(t *testing.T) {
if got := normalizeStatus("PASS", false); got != "OK" {
t.Fatalf("expected PASS -> OK, got %q", got)
}
if got := normalizeStatus("FAIL", false); got != "Critical" {
t.Fatalf("expected FAIL -> Critical, got %q", got)
}
}
func TestConvertCPUs(t *testing.T) {
cpus := []models.CPU{
{
Socket: 0,
Model: "INTEL(R) XEON(R) GOLD 6530",
Cores: 32,
Threads: 64,
FrequencyMHz: 2100,
MaxFreqMHz: 4000,
},
{
Socket: 1,
Model: "AMD EPYC 7763",
Cores: 64,
Threads: 128,
FrequencyMHz: 2450,
MaxFreqMHz: 3500,
},
}
result := convertCPUs(cpus, "2026-02-10T15:30:00Z")
if len(result) != 2 {
t.Fatalf("expected 2 CPUs, got %d", len(result))
}
if result[0].Manufacturer != "Intel" {
t.Errorf("expected Intel manufacturer for first CPU, got %q", result[0].Manufacturer)
}
if result[1].Manufacturer != "AMD" {
t.Errorf("expected AMD manufacturer for second CPU, got %q", result[1].Manufacturer)
}
if result[0].Status != "Unknown" {
t.Errorf("expected Unknown status, got %q", result[0].Status)
}
if result[0].SerialNumber != "" {
t.Errorf("expected empty CPU serial when source serial is absent, got %q", result[0].SerialNumber)
}
}
func TestConvertMemory(t *testing.T) {
memory := []models.MemoryDIMM{
{
Slot: "CPU0_C0D0",
Present: true,
SizeMB: 32768,
Type: "DDR5",
SerialNumber: "TEST-MEM-001",
Status: "OK",
},
{
Slot: "CPU0_C1D0",
Present: false,
},
}
result := convertMemory(memory, "2026-02-10T15:30:00Z")
if len(result) != 1 {
t.Fatalf("expected 1 populated memory module, got %d", len(result))
}
if result[0].Status != "OK" {
t.Errorf("expected OK status for first module, got %q", result[0].Status)
}
}
func TestConvertMemory_KeepsInstalledDIMMWithUnknownSize(t *testing.T) {
memory := []models.MemoryDIMM{
{
Slot: "PROC 1 DIMM 3",
Present: true,
SizeMB: 0,
Manufacturer: "Hynix",
PartNumber: "HMCG88AEBRA115N",
SerialNumber: "2B5F92C6",
Status: "OK",
},
}
result := convertMemory(memory, "2026-03-30T10:00:00Z")
if len(result) != 1 {
t.Fatalf("expected 1 inventory-only DIMM, got %d", len(result))
}
if result[0].PartNumber != "HMCG88AEBRA115N" || result[0].SerialNumber != "2B5F92C6" || result[0].SizeMB != 0 {
t.Fatalf("unexpected converted memory: %+v", result[0])
}
}
func TestConvertToReanimator_CPUSerialIsNotSynthesizedAndSocketIsDeduped(t *testing.T) {
input := &models.AnalysisResult{
Filename: "cpu-dedupe.json",
Hardware: &models.HardwareConfig{
BoardInfo: models.BoardInfo{SerialNumber: "BOARD-001"},
Devices: []models.HardwareDevice{
{
Kind: models.DeviceKindCPU,
Slot: "CPU1",
Model: "Xeon Platinum",
Cores: 56,
Status: "OK",
Details: map[string]any{
"socket": 1,
},
},
},
CPUs: []models.CPU{
{Socket: 1, Model: "Xeon Platinum", Cores: 56, Status: "OK"},
{Socket: 2, Model: "Xeon Platinum", Cores: 56, Status: "OK"},
},
},
}
out, err := ConvertToReanimator(input)
if err != nil {
t.Fatalf("ConvertToReanimator() failed: %v", err)
}
if len(out.Hardware.CPUs) != 2 {
t.Fatalf("expected exactly two CPUs after socket dedupe, got %d", len(out.Hardware.CPUs))
}
for _, cpu := range out.Hardware.CPUs {
if cpu.SerialNumber != "" {
t.Fatalf("expected CPU serial to stay empty when source serial is absent, got %q", cpu.SerialNumber)
}
}
}
func TestConvertToReanimator_ExportsEventLogsAndOmitsPCIeBDFJSON(t *testing.T) {
input := &models.AnalysisResult{
Filename: "events.json",
CollectedAt: time.Date(2026, 3, 15, 12, 0, 0, 0, time.UTC),
Events: []models.Event{
{
ID: "0x0042",
Timestamp: time.Date(2026, 3, 15, 11, 59, 0, 0, time.UTC),
Source: "SEL",
SensorName: "CPU0_C0D0",
Severity: models.SeverityWarning,
Description: "Correctable ECC error threshold exceeded",
RawData: "sel_record_id=42",
},
{
Source: "LOGPile",
Severity: models.SeverityWarning,
Description: "internal warning should not leak to event_logs",
},
},
Hardware: &models.HardwareConfig{
BoardInfo: models.BoardInfo{SerialNumber: "BOARD-001"},
Devices: []models.HardwareDevice{
{
Kind: models.DeviceKindPCIe,
Slot: "",
BDF: "0000:18:00.0",
DeviceClass: "NetworkController",
Manufacturer: "Mellanox",
Model: "ConnectX-6",
Status: "OK",
Details: map[string]any{
"manufactured_year_week": "2024-W07",
},
},
},
},
}
out, err := ConvertToReanimator(input)
if err != nil {
t.Fatalf("ConvertToReanimator() failed: %v", err)
}
if len(out.Hardware.EventLogs) != 1 {
t.Fatalf("expected 1 exported event log, got %d", len(out.Hardware.EventLogs))
}
log := out.Hardware.EventLogs[0]
if log.Source != "bmc" {
t.Fatalf("expected SEL source to map to bmc, got %#v", log)
}
if log.ComponentRef != "CPU0_C0D0" {
t.Fatalf("expected sensor name to map to component_ref, got %#v", log)
}
if len(out.Hardware.PCIeDevices) != 1 {
t.Fatalf("expected 1 pcie device, got %d", len(out.Hardware.PCIeDevices))
}
if out.Hardware.PCIeDevices[0].Slot != "0000:18:00.0" {
t.Fatalf("expected slot to fall back to BDF, got %#v", out.Hardware.PCIeDevices[0])
}
if out.Hardware.PCIeDevices[0].ManufacturedYearWeek != "2024-W07" {
t.Fatalf("expected manufactured_year_week to be exported, got %#v", out.Hardware.PCIeDevices[0])
}
payload, err := json.Marshal(out)
if err != nil {
t.Fatalf("json.Marshal() failed: %v", err)
}
if strings.Contains(string(payload), `"bdf"`) {
t.Fatalf("expected pcie bdf field to stay out of JSON payload: %s", payload)
}
}
func TestConvertToReanimator_EventLogSourceMappingSupportsDellAndHostSyslog(t *testing.T) {
input := &models.AnalysisResult{
Filename: "event-source-map.json",
CollectedAt: time.Date(2026, 3, 15, 12, 0, 0, 0, time.UTC),
Events: []models.Event{
{
ID: "SYS1001",
Timestamp: time.Date(2026, 3, 15, 11, 58, 0, 0, time.UTC),
Source: "iDRAC",
SensorName: "NIC.Slot.1-1-1",
Severity: models.SeverityWarning,
Description: "Link is down",
},
{
ID: "syslog_1",
Timestamp: time.Date(2026, 3, 15, 11, 59, 0, 0, time.UTC),
Source: "syslog",
SensorName: "systemd[1]",
Severity: models.SeverityInfo,
Description: "Started Example Service",
},
},
Hardware: &models.HardwareConfig{
BoardInfo: models.BoardInfo{SerialNumber: "BOARD-001"},
},
}
out, err := ConvertToReanimator(input)
if err != nil {
t.Fatalf("ConvertToReanimator() failed: %v", err)
}
if len(out.Hardware.EventLogs) != 2 {
t.Fatalf("expected 2 event logs, got %d", len(out.Hardware.EventLogs))
}
if out.Hardware.EventLogs[0].Source != "bmc" {
t.Fatalf("expected iDRAC event to map to bmc, got %#v", out.Hardware.EventLogs[0])
}
if out.Hardware.EventLogs[1].Source != "host" {
t.Fatalf("expected syslog event to map to host, got %#v", out.Hardware.EventLogs[1])
}
}
func TestConvertStorage(t *testing.T) {
storage := []models.Storage{
{
Slot: "OB01",
Type: "NVMe",
Model: "INTEL SSDPF2KX076T1",
SerialNumber: "BTAX41900GF87P6DGN",
Present: true,
},
{
Slot: "OB02",
Type: "NVMe",
Model: "INTEL SSDPF2KX076T1",
SerialNumber: "",
Present: true,
},
}
result := convertStorage(storage, "2026-02-10T15:30:00Z")
if len(result) != 2 {
t.Fatalf("expected both inventory slots to be exported, got %d", len(result))
}
if result[0].Status != "Unknown" {
t.Errorf("expected Unknown status, got %q", result[0].Status)
}
if result[1].SerialNumber != "" {
t.Errorf("expected empty serial for second storage slot, got %q", result[1].SerialNumber)
}
if result[1].Present == nil || !*result[1].Present {
t.Fatalf("expected present=true to be preserved for populated slot without serial")
}
}
func TestConvertToReanimator_SkipsAMIVirtualStorageDevices(t *testing.T) {
input := &models.AnalysisResult{
Filename: "virtual-media.json",
Hardware: &models.HardwareConfig{
BoardInfo: models.BoardInfo{SerialNumber: "BOARD-001"},
Storage: []models.Storage{
{
Slot: "USB_Device1_Port4",
Type: "HDD",
Model: "Virtual Cdrom Device",
SerialNumber: "AAAABBBBCCCC1",
Manufacturer: "American Megatrends Inc.",
Interface: "USB",
Present: true,
},
{
Slot: "OB01",
Type: "NVMe",
Model: "Memblaze PBlaze7",
SerialNumber: "REAL-NVME-001",
Manufacturer: "Memblaze",
Interface: "NVMe",
Present: true,
},
},
},
}
out, err := ConvertToReanimator(input)
if err != nil {
t.Fatalf("ConvertToReanimator() failed: %v", err)
}
if len(out.Hardware.Storage) != 1 {
t.Fatalf("expected only one real storage device to remain, got %d", len(out.Hardware.Storage))
}
if out.Hardware.Storage[0].SerialNumber != "REAL-NVME-001" {
t.Fatalf("expected virtual AMI storage to be skipped, got %#v", out.Hardware.Storage)
}
}
func TestConvertStorage_RemainingEndurance(t *testing.T) {
pct100 := 100
pct3 := 3
storage := []models.Storage{
{
Slot: "0",
Model: "HFS480G3H2X069N",
SerialNumber: "ESEAN5254I030B26B",
Present: true,
RemainingEndurancePct: &pct100,
},
{
Slot: "1",
Model: "HFS480G3H2X069N",
SerialNumber: "ESEAN5254I030B26C",
Present: true,
// no endurance data
},
{
Slot: "2",
Model: "HFS480G3H2X069N",
SerialNumber: "ESEAN5254I030B26D",
Present: true,
RemainingEndurancePct: &pct3,
},
}
result := convertStorage(storage, "2026-03-15T00:00:00Z")
if len(result) != 3 {
t.Fatalf("expected 3 results, got %d", len(result))
}
if result[0].RemainingEndurancePct == nil || *result[0].RemainingEndurancePct != 100 {
t.Errorf("slot 0: expected remaining_endurance_pct=100, got %v", result[0].RemainingEndurancePct)
}
if result[1].RemainingEndurancePct != nil {
t.Errorf("slot 1: expected remaining_endurance_pct absent, got %v", *result[1].RemainingEndurancePct)
}
if result[2].RemainingEndurancePct == nil || *result[2].RemainingEndurancePct != 3 {
t.Errorf("slot 2: expected remaining_endurance_pct=3, got %v", result[2].RemainingEndurancePct)
}
}
func TestConvertPCIeDevices(t *testing.T) {
hw := &models.HardwareConfig{
PCIeDevices: []models.PCIeDevice{
{
Slot: "PCIeCard1",
VendorID: 32902,
DeviceID: 2912,
BDF: "0000:18:00.0",
DeviceClass: "MassStorageController",
Manufacturer: "Intel",
PartNumber: "RSP3DD080F",
SerialNumber: "RAID-001",
},
{
Slot: "PCIeCard2",
DeviceClass: "NetworkController",
Manufacturer: "Mellanox",
SerialNumber: "", // Should be generated
},
},
GPUs: []models.GPU{
{
Slot: "GPU1",
Model: "NVIDIA A100",
Manufacturer: "NVIDIA",
SerialNumber: "GPU-001",
Status: "OK",
},
},
NetworkAdapters: []models.NetworkAdapter{
{
Slot: "NIC1",
Model: "ConnectX-6",
Vendor: "Mellanox",
Present: true,
SerialNumber: "NIC-001",
},
},
}
result := convertPCIeDevices(hw, "2026-02-10T15:30:00Z")
// Should have: 2 PCIe devices + 1 GPU + 1 NIC = 4 total
if len(result) != 4 {
t.Fatalf("expected 4 PCIe devices total, got %d", len(result))
}
// Missing serials must remain absent.
if result[1].SerialNumber != "" {
t.Errorf("expected empty serial for missing device serial, got %q", result[1].SerialNumber)
}
// Check GPU was included
foundGPU := false
for _, dev := range result {
if dev.SerialNumber == "GPU-001" {
foundGPU = true
if dev.DeviceClass != "VideoController" {
t.Errorf("expected GPU device_class VideoController, got %q", dev.DeviceClass)
}
break
}
}
if !foundGPU {
t.Error("expected GPU to be included in PCIe devices")
}
}
func TestConvertPCIeDevices_NVSwitchWithoutSerialRemainsEmpty(t *testing.T) {
hw := &models.HardwareConfig{
Firmware: []models.FirmwareInfo{
{
DeviceName: "NVSwitch NVSWITCH1 (965-25612-0002-000)",
Version: "96.10.6D.00.01",
},
},
PCIeDevices: []models.PCIeDevice{
{
Slot: "NVSWITCH1",
DeviceClass: "NVSwitch",
BDF: "0000:06:00.0",
// SerialNumber empty on purpose; should remain empty.
},
},
}
result := convertPCIeDevices(hw, "2026-02-10T15:30:00Z")
if len(result) != 1 {
t.Fatalf("expected 1 PCIe device, got %d", len(result))
}
if result[0].SerialNumber != "" {
t.Fatalf("expected empty NVSwitch serial, got %q", result[0].SerialNumber)
}
if result[0].Firmware != "96.10.6D.00.01" {
t.Fatalf("expected NVSwitch firmware 96.10.6D.00.01, got %q", result[0].Firmware)
}
}
func TestConvertToReanimator_MapsHGXNVSwitchFirmwareToPCIeDevice(t *testing.T) {
input := &models.AnalysisResult{
Hardware: &models.HardwareConfig{
BoardInfo: models.BoardInfo{SerialNumber: "BOARD-001"},
Firmware: []models.FirmwareInfo{
{DeviceName: "HGX_FW_ERoT_NVSwitch_0", Version: "00.02.0192.0000_n00"},
{DeviceName: "HGX_FW_NVSwitch_0", Version: "96.10.73.00.01"},
},
PCIeDevices: []models.PCIeDevice{
{
Slot: "NVSwitch_0",
DeviceClass: "NVSwitch",
Manufacturer: "NVIDIA",
Details: map[string]any{
"temperature_c": 31.59375,
},
},
},
},
}
out, err := ConvertToReanimator(input)
if err != nil {
t.Fatalf("ConvertToReanimator() failed: %v", err)
}
if len(out.Hardware.PCIeDevices) != 1 {
t.Fatalf("expected one NVSwitch PCIe device, got %d", len(out.Hardware.PCIeDevices))
}
got := out.Hardware.PCIeDevices[0]
if got.Firmware != "96.10.73.00.01" {
t.Fatalf("expected HGX NVSwitch firmware to map to device, got %#v", got)
}
if got.TemperatureC != 31.59375 {
t.Fatalf("expected NVSwitch temperature to be exported, got %#v", got)
}
}
func TestBuildNVSwitchFirmwareBySlot_SkipsERoTFirmware(t *testing.T) {
got := buildNVSwitchFirmwareBySlot([]models.FirmwareInfo{
{DeviceName: "HGX_FW_ERoT_NVSwitch_0", Version: "00.02.0192.0000_n00"},
{DeviceName: "HGX_FW_NVSwitch_0", Version: "96.10.73.00.01"},
})
if len(got) != 1 {
t.Fatalf("expected only main NVSwitch firmware to remain, got %#v", got)
}
if got["NVSWITCH_0"] != "96.10.73.00.01" {
t.Fatalf("expected main NVSwitch firmware, got %#v", got)
}
}
func TestConvertPCIeDevices_SkipsDisplayControllerDuplicates(t *testing.T) {
hw := &models.HardwareConfig{
PCIeDevices: []models.PCIeDevice{
{
Slot: "#GPU0",
DeviceClass: "3D Controller",
},
},
GPUs: []models.GPU{
{
Slot: "#GPU0",
Model: "B200 180GB HBM3e",
Manufacturer: "NVIDIA",
SerialNumber: "1655024043371",
Status: "OK",
},
},
}
result := convertPCIeDevices(hw, "2026-02-10T15:30:00Z")
if len(result) != 1 {
t.Fatalf("expected only dedicated GPU record without duplicate display PCIe, got %d", len(result))
}
if result[0].DeviceClass != "VideoController" {
t.Fatalf("expected GPU record with VideoController class, got %q", result[0].DeviceClass)
}
if result[0].Status != "OK" {
t.Fatalf("expected GPU status OK, got %q", result[0].Status)
}
}
func TestConvertPCIeDevices_MapsGPUStatusHistory(t *testing.T) {
hw := &models.HardwareConfig{
GPUs: []models.GPU{
{
Slot: "#GPU6",
Model: "B200 180GB HBM3e",
Manufacturer: "NVIDIA",
SerialNumber: "1655024043204",
Status: "Critical",
StatusHistory: []models.StatusHistoryEntry{
{
Status: "Critical",
ChangedAt: time.Date(2026, 1, 12, 15, 5, 18, 0, time.UTC),
Details: "BIOS miss F_GPU6",
},
},
ErrorDescription: "BIOS miss F_GPU6",
},
},
}
result := convertPCIeDevices(hw, "2026-02-10T15:30:00Z")
if len(result) != 1 {
t.Fatalf("expected 1 converted GPU, got %d", len(result))
}
if len(result[0].StatusHistory) != 1 {
t.Fatalf("expected 1 history entry, got %d", len(result[0].StatusHistory))
}
if result[0].StatusHistory[0].ChangedAt != "2026-01-12T15:05:18Z" {
t.Fatalf("unexpected history changed_at: %q", result[0].StatusHistory[0].ChangedAt)
}
}
func TestConvertPowerSupplies(t *testing.T) {
psus := []models.PSU{
{
Slot: "0",
Present: true,
Model: "GW-CRPS3000LW",
Vendor: "Great Wall",
WattageW: 3000,
SerialNumber: "PSU-001",
Status: "OK",
},
{
Slot: "1",
Present: false,
SerialNumber: "", // Not present, should be skipped
},
}
result := convertPowerSupplies(psus, "2026-02-10T15:30:00Z")
if len(result) != 1 {
t.Fatalf("expected 1 PSU (skipped empty), got %d", len(result))
}
if result[0].Status != "OK" {
t.Errorf("expected OK status, got %q", result[0].Status)
}
}
func TestConvertBoardNormalizesNULL(t *testing.T) {
board := convertBoard(models.BoardInfo{
Manufacturer: " NULL ",
ProductName: "null",
SerialNumber: "TEST123",
})
if board.Manufacturer != "" {
t.Fatalf("expected empty manufacturer, got %q", board.Manufacturer)
}
if board.ProductName != "" {
t.Fatalf("expected empty product_name, got %q", board.ProductName)
}
}
func TestSourceTypeOmittedWhenInvalidOrEmpty(t *testing.T) {
result, err := ConvertToReanimator(&models.AnalysisResult{
Filename: "redfish://10.0.0.1",
SourceType: "archive",
TargetHost: "10.0.0.1",
Hardware: &models.HardwareConfig{
BoardInfo: models.BoardInfo{SerialNumber: "TEST123"},
},
})
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
payload, err := json.Marshal(result)
if err != nil {
t.Fatalf("marshal failed: %v", err)
}
if !strings.Contains(string(payload), `"source_type":"logfile"`) {
t.Fatalf("expected archive source_type to map to logfile, got %s", string(payload))
}
}
func TestTargetHostOmittedWhenUnavailable(t *testing.T) {
result, err := ConvertToReanimator(&models.AnalysisResult{
Filename: "test.json",
SourceType: "api",
Hardware: &models.HardwareConfig{
BoardInfo: models.BoardInfo{SerialNumber: "TEST123"},
},
})
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
payload, err := json.Marshal(result)
if err != nil {
t.Fatalf("marshal failed: %v", err)
}
if strings.Contains(string(payload), `"target_host"`) {
t.Fatalf("expected target_host to be omitted when unavailable, got %s", string(payload))
}
}
func TestInferTargetHost(t *testing.T) {
tests := []struct {
name string
targetHost string
filename string
want string
}{
{
name: "explicit target host wins",
targetHost: "10.0.0.10",
filename: "redfish://10.0.0.20",
want: "10.0.0.10",
},
{
name: "hostname from URL",
filename: "redfish://10.10.10.103",
want: "10.10.10.103",
},
{
name: "ip extracted from archive name",
filename: "nvidia_bug_report_192.168.12.34.tar.gz",
want: "192.168.12.34",
},
{
name: "no host available",
filename: "test.json",
want: "",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := inferTargetHost(tt.targetHost, tt.filename)
if got != tt.want {
t.Fatalf("inferTargetHost() = %q, want %q", got, tt.want)
}
})
}
}
func TestConvertToReanimator_DeduplicatesAllSections(t *testing.T) {
input := &models.AnalysisResult{
Filename: "dup-test.json",
CollectedAt: time.Date(2026, 2, 10, 15, 30, 0, 0, time.UTC),
Hardware: &models.HardwareConfig{
BoardInfo: models.BoardInfo{SerialNumber: "BOARD-001"},
Firmware: []models.FirmwareInfo{
{DeviceName: "BMC", Version: "1.0"},
{DeviceName: "BMC", Version: "1.1"},
},
CPUs: []models.CPU{
{Socket: 0, Model: "CPU-A"},
{Socket: 0, Model: "CPU-A-DUP"},
},
Memory: []models.MemoryDIMM{
{Slot: "DIMM_A1", Present: true, SizeMB: 32768, SerialNumber: "MEM-1", Status: "OK"},
{Slot: "DIMM_A1", Present: true, SizeMB: 32768, SerialNumber: "MEM-1-DUP", Status: "OK"},
},
Storage: []models.Storage{
{Slot: "U.2-1", SerialNumber: "SSD-1", Model: "Disk1", Present: true},
{Slot: "U.2-2", SerialNumber: "SSD-1", Model: "Disk1-dup", Present: true},
},
PCIeDevices: []models.PCIeDevice{
{Slot: "#GPU0", DeviceClass: "3D Controller", BDF: "17:00.0"},
{Slot: "SLOT-NIC1", DeviceClass: "NetworkController", BDF: "18:00.0"},
{Slot: "SLOT-NIC1", DeviceClass: "NetworkController", BDF: "18:00.1"},
},
GPUs: []models.GPU{
{Slot: "#GPU0", Model: "B200 180GB HBM3e", SerialNumber: "GPU-1", Status: "OK"},
},
PowerSupply: []models.PSU{
{Slot: "0", Present: true, SerialNumber: "PSU-1", Status: "OK"},
{Slot: "1", Present: true, SerialNumber: "PSU-1", Status: "OK"},
},
},
}
out, err := ConvertToReanimator(input)
if err != nil {
t.Fatalf("ConvertToReanimator() failed: %v", err)
}
if len(out.Hardware.Firmware) != 1 {
t.Fatalf("expected deduped firmware len=1, got %d", len(out.Hardware.Firmware))
}
if len(out.Hardware.CPUs) != 1 {
t.Fatalf("expected cpus len=1 after socket dedupe, got %d", len(out.Hardware.CPUs))
}
if len(out.Hardware.Memory) != 1 {
t.Fatalf("expected memory len=1 after slot dedupe, got %d", len(out.Hardware.Memory))
}
if len(out.Hardware.Storage) != 1 {
t.Fatalf("expected deduped storage len=1, got %d", len(out.Hardware.Storage))
}
if len(out.Hardware.PowerSupplies) != 1 {
t.Fatalf("expected deduped psu len=1, got %d", len(out.Hardware.PowerSupplies))
}
if len(out.Hardware.PCIeDevices) != 2 {
t.Fatalf("expected pcie len=2 after final pcie-class dedupe, got %d", len(out.Hardware.PCIeDevices))
}
gpuCount := 0
for _, dev := range out.Hardware.PCIeDevices {
if dev.Slot == "#GPU0" {
gpuCount++
}
}
if gpuCount != 1 {
t.Fatalf("expected one merged #GPU0 record, got %d", gpuCount)
}
}
func TestConvertToReanimator_StatusFallbackUsesCollectedAt(t *testing.T) {
collectedAt := time.Date(2026, 2, 10, 15, 30, 0, 0, time.UTC)
input := &models.AnalysisResult{
Filename: "status-fallback.json",
CollectedAt: collectedAt,
Hardware: &models.HardwareConfig{
BoardInfo: models.BoardInfo{SerialNumber: "BOARD-001"},
Storage: []models.Storage{
{
Slot: "U.2-1",
Model: "PM9A3",
SerialNumber: "SSD-001",
Present: true,
Status: "OK",
},
},
},
}
out, err := ConvertToReanimator(input)
if err != nil {
t.Fatalf("ConvertToReanimator() failed: %v", err)
}
if len(out.Hardware.Storage) != 1 {
t.Fatalf("expected 1 storage entry, got %d", len(out.Hardware.Storage))
}
wantTs := collectedAt.UTC().Format(time.RFC3339)
got := out.Hardware.Storage[0]
if got.StatusCheckedAt != wantTs {
t.Fatalf("expected status_checked_at=%q, got %q", wantTs, got.StatusCheckedAt)
}
}
func TestConvertToReanimator_ExportsStorageInventoryWithoutSerial(t *testing.T) {
collectedAt := time.Date(2026, 4, 1, 9, 0, 0, 0, time.UTC)
input := &models.AnalysisResult{
Filename: "nvme-inventory.json",
CollectedAt: collectedAt,
Hardware: &models.HardwareConfig{
BoardInfo: models.BoardInfo{SerialNumber: "BOARD-001"},
Storage: []models.Storage{
{
Slot: "OB01",
Type: "NVMe",
Model: "PM9A3",
SerialNumber: "SSD-001",
Present: true,
},
{
Slot: "OB02",
Type: "NVMe",
Model: "PM9A3",
Present: true,
},
{
Slot: "OB03",
Type: "NVMe",
Model: "PM9A3",
Present: false,
},
},
},
}
out, err := ConvertToReanimator(input)
if err != nil {
t.Fatalf("ConvertToReanimator() failed: %v", err)
}
if len(out.Hardware.Storage) != 3 {
t.Fatalf("expected 3 storage entries including inventory slots without serial, got %d", len(out.Hardware.Storage))
}
if out.Hardware.Storage[1].Slot != "OB02" || out.Hardware.Storage[1].SerialNumber != "" {
t.Fatalf("expected OB02 storage slot without serial to survive export, got %#v", out.Hardware.Storage[1])
}
if out.Hardware.Storage[2].Present == nil || *out.Hardware.Storage[2].Present {
t.Fatalf("expected OB03 to preserve present=false, got %#v", out.Hardware.Storage[2])
}
}
func TestConvertToReanimator_FirmwareExcludesDeviceBoundEntries(t *testing.T) {
input := &models.AnalysisResult{
Filename: "fw-filter-test.json",
Hardware: &models.HardwareConfig{
BoardInfo: models.BoardInfo{SerialNumber: "BOARD-001"},
CPUs: []models.CPU{
{Socket: 0, Model: "Intel Xeon Gold"},
},
Firmware: []models.FirmwareInfo{
{DeviceName: "BIOS", Version: "1.0.0"},
{DeviceName: "BMC", Version: "2.0.0"},
{DeviceName: "GPU GPUSXM1 (692-2G520-0280-501)", Version: "96.00.D0.00.03"},
{DeviceName: "NVSwitch NVSWITCH0 (965-25612-0002-000)", Version: "96.10.6D.00.01"},
{DeviceName: "NIC #CPU1_PCIE9 (MCX512A-ACAT)", Version: "28.38.1900"},
{DeviceName: "CPU0 Microcode", Version: "0x2b000643"},
},
},
}
out, err := ConvertToReanimator(input)
if err != nil {
t.Fatalf("ConvertToReanimator() failed: %v", err)
}
if len(out.Hardware.Firmware) != 2 {
t.Fatalf("expected only machine-level firmware entries, got %d", len(out.Hardware.Firmware))
}
got := map[string]string{}
for _, fw := range out.Hardware.Firmware {
got[fw.DeviceName] = fw.Version
}
if got["BIOS"] != "1.0.0" {
t.Fatalf("expected BIOS firmware to be kept")
}
if got["BMC"] != "2.0.0" {
t.Fatalf("expected BMC firmware to be kept")
}
if _, exists := got["GPU GPUSXM1 (692-2G520-0280-501)"]; exists {
t.Fatalf("expected GPU firmware to be excluded from hardware.firmware")
}
if _, exists := got["NVSwitch NVSWITCH0 (965-25612-0002-000)"]; exists {
t.Fatalf("expected NVSwitch firmware to be excluded from hardware.firmware")
}
if len(out.Hardware.CPUs) != 1 {
t.Fatalf("expected 1 CPU entry, got %d", len(out.Hardware.CPUs))
}
if out.Hardware.CPUs[0].Firmware != "0x2b000643" {
t.Fatalf("expected CPU firmware field to carry microcode, got %q", out.Hardware.CPUs[0].Firmware)
}
}
// TestConvertToReanimator_FirmwareExcludesDellFQDDEntries verifies that Dell TSR
// SoftwareIdentity firmware entries whose Description contains a device-bound FQDD
// (InfiniBand.Slot.*, RAID.SL.*, etc.) are filtered from hardware.firmware.
//
// Regression guard: PowerEdge R6625 (8VS2LG4) — "Mellanox Network Adapter" (FQDD
// InfiniBand.Slot.1-1) and "PERC H755 Front" (FQDD RAID.SL.3-1) leaked into
// hardware.firmware. (2026-03-15)
func TestConvertToReanimator_FirmwareExcludesDellFQDDEntries(t *testing.T) {
input := &models.AnalysisResult{
Filename: "dell-fw-filter-test.json",
Hardware: &models.HardwareConfig{
BoardInfo: models.BoardInfo{SerialNumber: "8VS2LG4"},
Firmware: []models.FirmwareInfo{
// system-level — must be kept
{DeviceName: "BIOS", Version: "1.15.3", Description: "system bios"},
{DeviceName: "iDRAC", Version: "7.20.80.50", Description: "idrac card"},
{DeviceName: "Lifecycle Controller", Version: "7.20.80.50", Description: "idrac lifecycle"},
// device-bound via FQDD — must be filtered
{DeviceName: "Mellanox Network Adapter", Version: "20.39.35.60", Description: "InfiniBand.Slot.1-1"},
{DeviceName: "PERC H755 Front", Version: "52.30.0-6115", Description: "RAID.SL.3-1"},
},
},
}
out, err := ConvertToReanimator(input)
if err != nil {
t.Fatalf("ConvertToReanimator() failed: %v", err)
}
got := make(map[string]string, len(out.Hardware.Firmware))
for _, fw := range out.Hardware.Firmware {
got[fw.DeviceName] = fw.Version
}
for _, keep := range []string{"BIOS", "iDRAC", "Lifecycle Controller"} {
if _, ok := got[keep]; !ok {
t.Errorf("expected %q in hardware.firmware, but it was missing", keep)
}
}
for _, drop := range []string{"Mellanox Network Adapter", "PERC H755 Front"} {
if _, ok := got[drop]; ok {
t.Errorf("%q must not appear in hardware.firmware (device-bound FQDD)", drop)
}
}
}
func TestConvertToReanimator_UsesCanonicalDevices(t *testing.T) {
input := &models.AnalysisResult{
Filename: "canonical.json",
Hardware: &models.HardwareConfig{
BoardInfo: models.BoardInfo{SerialNumber: "BOARD-001"},
Devices: []models.HardwareDevice{
{
Kind: models.DeviceKindCPU,
Slot: "CPU0",
Model: "INTEL(R) XEON(R)",
Cores: 32,
Threads: 64,
FrequencyMHz: 2100,
},
{
Kind: models.DeviceKindStorage,
Slot: "U.2-1",
Model: "Disk1",
SerialNumber: "SSD-1",
Present: boolPtr(true),
},
},
},
}
out, err := ConvertToReanimator(input)
if err != nil {
t.Fatalf("ConvertToReanimator() failed: %v", err)
}
if len(out.Hardware.CPUs) != 1 {
t.Fatalf("expected cpu from hardware.devices, got %d", len(out.Hardware.CPUs))
}
if len(out.Hardware.Storage) != 1 {
t.Fatalf("expected storage from hardware.devices, got %d", len(out.Hardware.Storage))
}
}
func TestConvertToReanimator_MergesCanonicalAndLegacyDevices(t *testing.T) {
input := &models.AnalysisResult{
Filename: "merged.json",
Hardware: &models.HardwareConfig{
BoardInfo: models.BoardInfo{SerialNumber: "BOARD-001"},
Devices: []models.HardwareDevice{
{
Kind: models.DeviceKindPCIe,
Slot: "PCIe 3",
Model: "RAID Controller",
DeviceClass: "raid_controller",
Status: "ok",
},
},
CPUs: []models.CPU{
{Socket: 0, Model: "Xeon Platinum", SerialNumber: "CPU-001"},
},
Memory: []models.MemoryDIMM{
{Slot: "DIMM0", Location: "DIMM0", Present: true, SizeMB: 32768, Type: "DDR5", SerialNumber: "MEM-001"},
},
Storage: []models.Storage{
{Slot: "U.2-1", Type: "NVMe", Model: "Drive1", SerialNumber: "SSD-001", Present: true},
},
PowerSupply: []models.PSU{
{Slot: "PSU0", Model: "PSU", SerialNumber: "PSU-001", Present: true},
},
},
}
out, err := ConvertToReanimator(input)
if err != nil {
t.Fatalf("ConvertToReanimator() failed: %v", err)
}
if len(out.Hardware.CPUs) != 1 {
t.Fatalf("expected cpu from legacy inventory to survive canonical merge, got %d", len(out.Hardware.CPUs))
}
if len(out.Hardware.Memory) != 1 {
t.Fatalf("expected memory from legacy inventory to survive canonical merge, got %d", len(out.Hardware.Memory))
}
if len(out.Hardware.Storage) != 1 {
t.Fatalf("expected storage from legacy inventory to survive canonical merge, got %d", len(out.Hardware.Storage))
}
if len(out.Hardware.PowerSupplies) != 1 {
t.Fatalf("expected psu from legacy inventory to survive canonical merge, got %d", len(out.Hardware.PowerSupplies))
}
if len(out.Hardware.PCIeDevices) != 1 {
t.Fatalf("expected supplemental canonical pcie device to remain present, got %d", len(out.Hardware.PCIeDevices))
}
}
func TestConvertToReanimator_DoesNotMergeStorageIntoPCIeBySharedSerial(t *testing.T) {
input := &models.AnalysisResult{
Filename: "nvme-redfish.json",
Hardware: &models.HardwareConfig{
BoardInfo: models.BoardInfo{SerialNumber: "BOARD-001"},
Storage: []models.Storage{
{
Slot: "Disk.Bay.0",
Type: "NVMe",
Model: "MZQL21T9HCJR-00A07",
SerialNumber: "S64GNNFX612200",
Manufacturer: "Samsung",
Firmware: "GDC5A02Q",
Present: true,
},
},
PCIeDevices: []models.PCIeDevice{
{
Slot: "NVMeSSD1",
BDF: "0000:81:00.0",
DeviceClass: "MassStorageController",
Description: "MZQL21T9HCJR-00A07",
SerialNumber: "S64GNNFX612200",
Manufacturer: "Samsung",
},
},
},
}
out, err := ConvertToReanimator(input)
if err != nil {
t.Fatalf("ConvertToReanimator() failed: %v", err)
}
if len(out.Hardware.Storage) != 1 {
t.Fatalf("expected storage record to survive shared-serial canonical merge, got %d", len(out.Hardware.Storage))
}
if out.Hardware.Storage[0].Slot != "Disk.Bay.0" {
t.Fatalf("expected storage slot Disk.Bay.0, got %q", out.Hardware.Storage[0].Slot)
}
if len(out.Hardware.PCIeDevices) != 0 {
t.Fatalf("expected NVMe storage endpoint to be excluded from pcie export, got %d records", len(out.Hardware.PCIeDevices))
}
}
func TestConvertToReanimator_LeavesStorageControllersInPCIe(t *testing.T) {
input := &models.AnalysisResult{
Hardware: &models.HardwareConfig{
BoardInfo: models.BoardInfo{SerialNumber: "BOARD-123"},
PCIeDevices: []models.PCIeDevice{
{
Slot: "PCIe Slot 3",
BDF: "0000:5e:00.0",
DeviceClass: "MassStorageController",
Description: "MegaRAID Controller",
PartNumber: "PERC H755",
SerialNumber: "RAID-001",
Manufacturer: "Dell",
},
},
},
}
out, err := ConvertToReanimator(input)
if err != nil {
t.Fatalf("ConvertToReanimator() failed: %v", err)
}
if len(out.Hardware.PCIeDevices) != 1 {
t.Fatalf("expected RAID controller to remain in pcie export, got %d", len(out.Hardware.PCIeDevices))
}
}
func TestConvertToReanimator_PCIePlaceholderModelFallsBackToPCIIDs(t *testing.T) {
input := &models.AnalysisResult{
Hardware: &models.HardwareConfig{
BoardInfo: models.BoardInfo{SerialNumber: "BOARD-123"},
Devices: []models.HardwareDevice{
{
Kind: models.DeviceKindNetwork,
Slot: "NIC1",
Model: "Network Device View",
VendorID: 0x15b3,
DeviceID: 0x101d,
Manufacturer: "Mellanox",
Present: boolPtr(true),
},
},
},
}
out, err := ConvertToReanimator(input)
if err != nil {
t.Fatalf("ConvertToReanimator() failed: %v", err)
}
if len(out.Hardware.PCIeDevices) != 1 {
t.Fatalf("expected one pcie export, got %d", len(out.Hardware.PCIeDevices))
}
if strings.EqualFold(out.Hardware.PCIeDevices[0].Model, "Network Device View") {
t.Fatalf("expected placeholder model to be replaced, got %q", out.Hardware.PCIeDevices[0].Model)
}
if out.Hardware.PCIeDevices[0].SerialNumber != "" {
t.Fatalf("expected missing pcie serial to stay empty, got %q", out.Hardware.PCIeDevices[0].SerialNumber)
}
}
func TestConvertToReanimator_SkipsPlaceholderNetworkPCIeRecords(t *testing.T) {
input := &models.AnalysisResult{
Hardware: &models.HardwareConfig{
BoardInfo: models.BoardInfo{SerialNumber: "BOARD-123"},
Devices: []models.HardwareDevice{
{
Kind: models.DeviceKindNetwork,
Slot: "1",
Status: "Unknown",
},
{
Kind: models.DeviceKindNetwork,
Slot: "NIC2",
Model: "ConnectX-7",
Manufacturer: "NVIDIA",
Present: boolPtr(true),
},
},
},
}
out, err := ConvertToReanimator(input)
if err != nil {
t.Fatalf("ConvertToReanimator() failed: %v", err)
}
if len(out.Hardware.PCIeDevices) != 1 {
t.Fatalf("expected only one meaningful pcie-class device, got %d", len(out.Hardware.PCIeDevices))
}
if out.Hardware.PCIeDevices[0].Slot != "NIC2" {
t.Fatalf("expected placeholder numeric-slot NIC to be skipped, got %+v", out.Hardware.PCIeDevices)
}
}
func TestConvertToReanimator_ExportsSensorsAndPSUTelemetry(t *testing.T) {
input := &models.AnalysisResult{
Filename: "vitals.json",
Sensors: []models.SensorReading{
{Name: "FAN1", Type: "fan", Value: 4200, Unit: "RPM", Status: "OK"},
{Name: "12V Rail", Type: "voltage", Value: 12.1, Unit: "V", Status: "OK"},
{Name: "CPU0 Temp", Type: "temperature", Value: 71, Unit: "C", Status: "Warning"},
{Name: "Humidity", Type: "humidity", Value: 38.5, Unit: "%", Status: "OK"},
},
Hardware: &models.HardwareConfig{
BoardInfo: models.BoardInfo{SerialNumber: "BOARD-001"},
Devices: []models.HardwareDevice{
{
Kind: models.DeviceKindGPU,
Slot: "#GPU0",
Model: "B200 180GB HBM3e",
SerialNumber: "GPU-001",
BDF: "0000:17:00.0",
},
{
Kind: models.DeviceKindPSU,
Slot: "PSU0",
SerialNumber: "PSU-001",
Present: boolPtr(true),
InputPowerW: 1400,
OutputPowerW: 1300,
InputVoltage: 229.5,
TemperatureC: 44,
},
},
},
}
out, err := ConvertToReanimator(input)
if err != nil {
t.Fatalf("ConvertToReanimator() failed: %v", err)
}
if len(out.Hardware.PCIeDevices) != 1 {
t.Fatalf("expected one pcie device, got %d", len(out.Hardware.PCIeDevices))
}
pcie := out.Hardware.PCIeDevices[0]
if pcie.TemperatureC != 0 {
t.Fatalf("expected canonical GPU telemetry to stay off the component unless sourced from details/gpu path, got %.2f", pcie.TemperatureC)
}
if len(out.Hardware.PowerSupplies) != 1 {
t.Fatalf("expected one PSU, got %d", len(out.Hardware.PowerSupplies))
}
psu := out.Hardware.PowerSupplies[0]
if psu.InputPowerW != 1400 {
t.Fatalf("expected PSU input power 1400W, got %.2f", psu.InputPowerW)
}
if psu.TemperatureC != 44 {
t.Fatalf("expected PSU temperature 44C, got %.2f", psu.TemperatureC)
}
if out.Hardware.Sensors == nil {
t.Fatalf("expected sensors section")
}
if len(out.Hardware.Sensors.Fans) != 1 || out.Hardware.Sensors.Fans[0].RPM != 4200 {
t.Fatalf("expected fan sensor export, got %#v", out.Hardware.Sensors.Fans)
}
if len(out.Hardware.Sensors.Power) != 1 || out.Hardware.Sensors.Power[0].VoltageV != 12.1 {
t.Fatalf("expected power sensor export, got %#v", out.Hardware.Sensors.Power)
}
if len(out.Hardware.Sensors.Temperatures) != 1 || out.Hardware.Sensors.Temperatures[0].Celsius != 71 {
t.Fatalf("expected temperature sensor export, got %#v", out.Hardware.Sensors.Temperatures)
}
if len(out.Hardware.Sensors.Other) != 1 || out.Hardware.Sensors.Other[0].Unit != "%" {
t.Fatalf("expected other sensor export, got %#v", out.Hardware.Sensors.Other)
}
}
func TestConvertToReanimator_SkipsSensorsWithoutNumericReadings(t *testing.T) {
input := &models.AnalysisResult{
Filename: "sensor-gaps.json",
Sensors: []models.SensorReading{
{Name: "CPU0 Temp", Type: "temperature", Status: "OK", RawValue: "N/A"},
{Name: "PSU1 Power", Type: "power", Status: "OK", RawValue: ""},
{Name: "Fan1", Type: "fan", Status: "OK", RawValue: "not present"},
{Name: "Humidity", Type: "humidity", Status: "OK", RawValue: "unknown"},
},
Hardware: &models.HardwareConfig{
BoardInfo: models.BoardInfo{SerialNumber: "BOARD-001"},
},
}
out, err := ConvertToReanimator(input)
if err != nil {
t.Fatalf("ConvertToReanimator() failed: %v", err)
}
if out.Hardware.Sensors != nil {
t.Fatalf("expected sensors to be omitted when all readings are non-numeric, got %+v", out.Hardware.Sensors)
}
}
func TestConvertToReanimator_MergesSiblingPowerSensors(t *testing.T) {
input := &models.AnalysisResult{
Filename: "power-sensors.json",
Sensors: []models.SensorReading{
{Name: "Power Supply Bay 8_InputPower", Type: "power", Value: 231, Unit: "W", Status: "OK"},
{Name: "Power Supply Bay 8_InputVoltage", Type: "voltage", Value: 228, Unit: "V", Status: "OK"},
},
Hardware: &models.HardwareConfig{
BoardInfo: models.BoardInfo{SerialNumber: "BOARD-001"},
},
}
out, err := ConvertToReanimator(input)
if err != nil {
t.Fatalf("ConvertToReanimator() failed: %v", err)
}
if out.Hardware.Sensors == nil || len(out.Hardware.Sensors.Power) != 1 {
t.Fatalf("expected one merged power sensor, got %#v", out.Hardware.Sensors)
}
got := out.Hardware.Sensors.Power[0]
if got.Name != "Power Supply Bay 8" {
t.Fatalf("expected merged sensor name, got %q", got.Name)
}
if got.PowerW != 231 || got.VoltageV != 228 {
t.Fatalf("expected merged power/voltage readings, got %#v", got)
}
if got.Location != "" {
t.Fatalf("expected sensor location to be omitted, got %#v", got)
}
}
func TestConvertToReanimator_MergesInspurPSUInputAndOutputSensors(t *testing.T) {
input := &models.AnalysisResult{
Hardware: &models.HardwareConfig{
BoardInfo: models.BoardInfo{SerialNumber: "BOARD-123"},
},
Sensors: []models.SensorReading{
{Name: "PSU0_VIN", Type: "voltage", Value: 224, Unit: "V", Status: "OK"},
{Name: "PSU0_PIN", Type: "power", Value: 120, Unit: "W", Status: "OK"},
{Name: "PSU0_VOUT", Type: "voltage", Value: 12, Unit: "V", Status: "OK"},
{Name: "PSU0_POUT", Type: "power", Value: 88, Unit: "W", Status: "OK"},
{Name: "PSU0_OutputPower", Type: "power", Value: 95, Unit: "W", Status: "OK"},
},
}
out, err := ConvertToReanimator(input)
if err != nil {
t.Fatalf("ConvertToReanimator() failed: %v", err)
}
if out.Hardware.Sensors == nil || len(out.Hardware.Sensors.Power) != 2 {
t.Fatalf("expected 2 grouped PSU power sensors, got %#v", out.Hardware.Sensors)
}
byName := map[string]ReanimatorPowerSensor{}
for _, item := range out.Hardware.Sensors.Power {
byName[item.Name] = item
}
if got, ok := byName["PSU0"]; !ok || got.VoltageV != 224 || got.PowerW != 120 {
t.Fatalf("expected PSU0 input group with VIN/PIN merged, got %#v", byName)
}
if got, ok := byName["PSU0_Output"]; !ok || got.VoltageV != 12 || got.PowerW != 95 {
t.Fatalf("expected PSU0 output group with VOUT/POUT merged, got %#v", byName)
}
}
func TestConvertToReanimator_PreservesCanonicalDedupWithoutDeviceVitals(t *testing.T) {
input := &models.AnalysisResult{
Filename: "dedup-vitals.json",
Hardware: &models.HardwareConfig{
BoardInfo: models.BoardInfo{SerialNumber: "BOARD-001"},
PCIeDevices: []models.PCIeDevice{
{
Slot: "#GPU0",
BDF: "0000:17:00.0",
DeviceClass: "3D Controller",
PartNumber: "Generic Display",
Manufacturer: "NVIDIA",
SerialNumber: "GPU-SN-001",
},
},
GPUs: []models.GPU{
{
Slot: "#GPU0",
BDF: "0000:17:00.0",
Model: "B200 180GB HBM3e",
Manufacturer: "NVIDIA",
SerialNumber: "GPU-SN-001",
Temperature: 67,
Power: 330,
Status: "OK",
},
},
},
}
out, err := ConvertToReanimator(input)
if err != nil {
t.Fatalf("ConvertToReanimator() failed: %v", err)
}
if len(out.Hardware.PCIeDevices) != 1 {
t.Fatalf("expected deduped one pcie entry, got %d", len(out.Hardware.PCIeDevices))
}
got := out.Hardware.PCIeDevices[0]
if got.DeviceClass != "VideoController" {
t.Fatalf("expected GPU to export as VideoController, got %q", got.DeviceClass)
}
if got.TemperatureC != 67 {
t.Fatalf("expected deduped GPU temperature 67C, got %.2f", got.TemperatureC)
}
if got.PowerW != 330 {
t.Fatalf("expected deduped GPU power 330W, got %.2f", got.PowerW)
}
}
func TestConvertToReanimator_DedupesLooseCanonicalNICAndPCIeEntries(t *testing.T) {
input := &models.AnalysisResult{
Filename: "loose-dedup.json",
Hardware: &models.HardwareConfig{
BoardInfo: models.BoardInfo{SerialNumber: "BOARD-001"},
PCIeDevices: []models.PCIeDevice{
{
Slot: "Slot 4",
DeviceClass: "NetworkController",
VendorID: 0x15b3,
DeviceID: 0x1021,
Manufacturer: "Mellanox",
PartNumber: "MCX623106AC-CDAT",
},
},
NetworkAdapters: []models.NetworkAdapter{
{
Slot: "Slot 4",
Model: "ConnectX-6",
VendorID: 0x15b3,
DeviceID: 0x1021,
Vendor: "Mellanox",
MACAddresses: []string{"00:11:22:33:44:55"},
PortCount: 2,
Present: true,
},
},
},
}
out, err := ConvertToReanimator(input)
if err != nil {
t.Fatalf("ConvertToReanimator() failed: %v", err)
}
if len(out.Hardware.PCIeDevices) != 1 {
t.Fatalf("expected one merged loose-key pcie entry, got %d", len(out.Hardware.PCIeDevices))
}
got := out.Hardware.PCIeDevices[0]
if got.Model == "" {
t.Fatalf("expected merged pcie entry to retain a model, got empty")
}
if len(got.MACAddresses) != 1 || got.MACAddresses[0] != "00:11:22:33:44:55" {
t.Fatalf("expected MACs from NIC side after loose merge, got %#v", got.MACAddresses)
}
}
func TestConvertToReanimator_ExportsContractV24Telemetry(t *testing.T) {
input := &models.AnalysisResult{
Filename: "contract-v24.json",
Hardware: &models.HardwareConfig{
BoardInfo: models.BoardInfo{SerialNumber: "BOARD-001"},
Devices: []models.HardwareDevice{
{
Kind: models.DeviceKindCPU,
Slot: "CPU0",
Model: "INTEL(R) XEON(R) GOLD 6530",
Details: map[string]any{
"socket": 0,
"temperature_c": 61.5,
"power_w": 182.0,
"throttled": false,
"correctable_error_count": int64(4),
"uncorrectable_error_count": int64(1),
"life_remaining_pct": 98.5,
"life_used_pct": 1.5,
},
},
{
Kind: models.DeviceKindMemory,
Slot: "DIMM_A1",
SerialNumber: "MEM-001",
Present: boolPtr(true),
SizeMB: 32768,
Type: "DDR5",
Details: map[string]any{
"temperature_c": 43.0,
"correctable_ecc_error_count": int64(2),
"uncorrectable_ecc_error_count": int64(0),
"life_remaining_pct": 99.0,
"spare_blocks_remaining_pct": 97.0,
"performance_degraded": false,
},
},
{
Kind: models.DeviceKindStorage,
Slot: "U.2-1",
SerialNumber: "SSD-001",
Model: "PM9A3",
Present: boolPtr(true),
Details: map[string]any{
"temperature_c": 38.5,
"power_on_hours": int64(12450),
"unsafe_shutdowns": int64(3),
"written_bytes": int64(9876543210),
"life_remaining_pct": 91.0,
"available_spare_pct": 88.0,
"offline_uncorrectable": int64(0),
},
},
{
Kind: models.DeviceKindPCIe,
Slot: "PCIeCard2",
SerialNumber: "NIC-001",
DeviceClass: "EthernetController",
NUMANode: 1,
Details: map[string]any{
"temperature_c": 48.5,
"power_w": 18.2,
"life_remaining_pct": 95.0,
"ecc_corrected_total": int64(12),
"battery_health_pct": 87.0,
"sfp_temperature_c": 36.2,
"sfp_tx_power_dbm": -1.8,
"sfp_rx_power_dbm": -2.1,
"sfp_bias_ma": 5.5,
},
},
{
Kind: models.DeviceKindPSU,
Slot: "PSU0",
SerialNumber: "PSU-001",
Present: boolPtr(true),
Details: map[string]any{
"life_remaining_pct": 97.0,
"life_used_pct": 3.0,
},
TemperatureC: 39,
},
},
},
}
out, err := ConvertToReanimator(input)
if err != nil {
t.Fatalf("ConvertToReanimator() failed: %v", err)
}
if got := out.Hardware.CPUs[0]; got.TemperatureC != 61.5 || got.PowerW != 182.0 || got.Throttled == nil || *got.Throttled {
t.Fatalf("unexpected CPU telemetry: %#v", got)
}
if got := out.Hardware.Memory[0]; got.TemperatureC != 43.0 || got.CorrectableECCErrorCount != 2 || got.PerformanceDegraded == nil || *got.PerformanceDegraded {
t.Fatalf("unexpected memory telemetry: %#v", got)
}
if got := out.Hardware.Storage[0]; got.TemperatureC != 38.5 || got.PowerOnHours != 12450 || got.LifeRemainingPct != 91.0 {
t.Fatalf("unexpected storage telemetry: %#v", got)
}
if got := out.Hardware.PCIeDevices[0]; got.NUMANode != 1 || got.TemperatureC != 48.5 || got.PowerW != 18.2 || got.SFPTemperatureC != 36.2 {
t.Fatalf("unexpected PCIe telemetry: %#v", got)
}
if got := out.Hardware.PowerSupplies[0]; got.TemperatureC != 39 || got.LifeRemainingPct != 97.0 || got.LifeUsedPct != 3.0 {
t.Fatalf("unexpected PSU telemetry: %#v", got)
}
}
func TestConvertToReanimator_UnifiesEthernetAndNetworkControllers(t *testing.T) {
input := &models.AnalysisResult{
Hardware: &models.HardwareConfig{
BoardInfo: models.BoardInfo{SerialNumber: "BOARD-123"},
Devices: []models.HardwareDevice{
{
Kind: models.DeviceKindPCIe,
Slot: "PCIe1",
DeviceClass: "EthernetController",
Present: boolPtr(true),
SerialNumber: "ETH-001",
},
{
Kind: models.DeviceKindNetwork,
Slot: "NIC1",
Model: "Ethernet Adapter",
Present: boolPtr(true),
SerialNumber: "NIC-001",
},
},
},
}
out, err := ConvertToReanimator(input)
if err != nil {
t.Fatalf("ConvertToReanimator() failed: %v", err)
}
if len(out.Hardware.PCIeDevices) != 2 {
t.Fatalf("expected two pcie-class exports, got %d", len(out.Hardware.PCIeDevices))
}
for _, dev := range out.Hardware.PCIeDevices {
if dev.DeviceClass != "NetworkController" {
t.Fatalf("expected unified NetworkController class, got %+v", dev)
}
}
}
func TestConvertToReanimator_PreservesLegacyStorageAndPSUDetails(t *testing.T) {
input := &models.AnalysisResult{
Filename: "legacy-details.json",
Hardware: &models.HardwareConfig{
BoardInfo: models.BoardInfo{SerialNumber: "BOARD-001"},
Storage: []models.Storage{
{
Slot: "Drive0",
Type: "NVMe",
Model: "NVMe SSD",
SerialNumber: "SSD-001",
Present: true,
Details: map[string]any{
"temperature_c": 38.5,
"power_on_hours": int64(12450),
"life_remaining_pct": 91.0,
},
},
},
PowerSupply: []models.PSU{
{
Slot: "PSU0",
Model: "PSU",
SerialNumber: "PSU-001",
Present: true,
Details: map[string]any{
"temperature_c": 41.0,
"life_remaining_pct": 96.0,
},
},
},
},
}
out, err := ConvertToReanimator(input)
if err != nil {
t.Fatalf("ConvertToReanimator() failed: %v", err)
}
if got := out.Hardware.Storage[0]; got.TemperatureC != 38.5 || got.PowerOnHours != 12450 || got.LifeRemainingPct != 91.0 {
t.Fatalf("expected storage details from legacy model to survive canonical conversion, got %+v", got)
}
if got := out.Hardware.PowerSupplies[0]; got.TemperatureC != 41.0 || got.LifeRemainingPct != 96.0 {
t.Fatalf("expected psu details from legacy model to survive canonical conversion, got %+v", got)
}
}
func TestConvertToReanimator_PreservesLegacyPCIeAndNICDetails(t *testing.T) {
input := &models.AnalysisResult{
Filename: "legacy-pcie-details.json",
Hardware: &models.HardwareConfig{
BoardInfo: models.BoardInfo{SerialNumber: "BOARD-001"},
PCIeDevices: []models.PCIeDevice{
{
Slot: "PCIe 1",
BDF: "0000:17:00.0",
VendorID: 0x10de,
DeviceID: 0x2331,
PartNumber: "H100",
Manufacturer: "NVIDIA",
SerialNumber: "GPU-001",
Details: map[string]any{
"temperature_c": 48.5,
"power_w": 315.0,
"ecc_corrected_total": int64(12),
"battery_health_pct": 87.0,
},
},
},
NetworkAdapters: []models.NetworkAdapter{
{
Slot: "Slot 4",
BDF: "0000:18:00.0",
VendorID: 0x15b3,
DeviceID: 0x1021,
Model: "ConnectX-6",
SerialNumber: "NIC-001",
Present: true,
Details: map[string]any{
"sfp_temperature_c": 34.0,
"sfp_tx_power_dbm": -1.8,
"sfp_rx_power_dbm": -2.1,
},
},
},
},
}
out, err := ConvertToReanimator(input)
if err != nil {
t.Fatalf("ConvertToReanimator() failed: %v", err)
}
if len(out.Hardware.PCIeDevices) != 2 {
t.Fatalf("expected two pcie-class devices, got %d", len(out.Hardware.PCIeDevices))
}
foundGPU := false
foundNIC := false
for _, dev := range out.Hardware.PCIeDevices {
switch dev.SerialNumber {
case "GPU-001":
foundGPU = true
if dev.TemperatureC != 48.5 || dev.PowerW != 315.0 || dev.ECCCorrectedTotal != 12 || dev.BatteryHealthPct != 87.0 {
t.Fatalf("expected GPU telemetry preserved, got %+v", dev)
}
case "NIC-001":
foundNIC = true
if dev.SFPTemperatureC != 34.0 || dev.SFPTXPowerDBm != -1.8 || dev.SFPRXPowerDBm != -2.1 {
t.Fatalf("expected NIC sfp telemetry preserved, got %+v", dev)
}
}
}
if !foundGPU || !foundNIC {
t.Fatalf("expected both gpu and nic pcie-class exports, got %+v", out.Hardware.PCIeDevices)
}
}
func boolPtr(v bool) *bool { return &v }
// TestIsDeviceBoundFirmwareName verifies that device-bound firmware entries from
// Supermicro Redfish FirmwareInventory are correctly identified and excluded from
// hardware.firmware.
//
// Regression guard: names like "GPU1 System Slot0" and "NIC1 System Slot0 ..." were
// not caught because the old check required "gpu " / "nic " (with space), while
// Supermicro places a digit immediately after the type prefix. (2026-03-12)
func TestIsDeviceBoundFirmwareName(t *testing.T) {
cases := []struct {
name string
want bool
}{
// Supermicro Redfish — device-bound, must be excluded
{"GPU1 System Slot0", true},
{"GPU8 System Slot0", true},
{"NIC1 System Slot0 AOM-DP805-IO", true},
{"NIC9 System Slot8 MCX75310AAS-NEAT", true},
{"NVMeController1", true},
{"Power supply 1", true},
{"Power supply 6", true},
{"Software Inventory", true},
{"software inventory", true}, // case-insensitive
// Generic / legacy names already covered before this fix
{"GPU SomeDevice", true},
{"NIC OnboardLAN", true},
{"PSU1", true},
{"NVMe Drive", true},
// HGX FW ID patterns (in case Id is used as name)
{"HGX_FW_GPU_SXM_1", true},
{"HGX_FW_ERoT_NVSwitch_0", true},
{"HGX_InfoROM_GPU_SXM_2", true},
// System-level firmware — must NOT be excluded
{"BIOS", false},
{"BMC", false},
{"BMC Backup", false},
{"Capsule BIOS", false},
{"Capsule ME", false},
{"CPLD Motherboard Golden", false},
{"CPLD AOMboard", false},
{"FrontFanboard CPLD", false},
{"Motherboard PCIeSwitch 1", false}, // board-integrated, no device record
{"SecureBoot", false},
{"BIOS ME", false},
}
for _, tc := range cases {
got := isDeviceBoundFirmwareName(tc.name)
if got != tc.want {
t.Errorf("isDeviceBoundFirmwareName(%q) = %v, want %v", tc.name, got, tc.want)
}
}
}
// TestIsDeviceBoundFirmwareFQDD verifies that Dell TSR SoftwareIdentity FQDD strings
// (stored in FirmwareInfo.Description) correctly identify device-bound entries.
//
// Regression guard: "InfiniBand.Slot.1-1" (Mellanox ConnectX-6) and "RAID.SL.3-1"
// (PERC H755 Front) were not filtered because only "raid.backplane." was listed and
// "infiniband." was absent. Both firmware entries leaked into hardware.firmware on
// PowerEdge R6625 (8VS2LG4). (2026-03-15)
func TestIsDeviceBoundFirmwareFQDD(t *testing.T) {
cases := []struct {
desc string
want bool
}{
// Dell TSR SoftwareIdentity FQDDs — device-bound, must be excluded
{"InfiniBand.Slot.1-1", true}, // Mellanox ConnectX-6
{"InfiniBand.Slot.2-1", true}, // any InfiniBand slot
{"RAID.SL.3-1", true}, // PERC H755 Front
{"RAID.Integrated.1-1", true}, // embedded RAID controller
{"RAID.Backplane.Firmware.0", true}, // backplane (previously covered)
{"NIC.Integrated.1-1-1", true}, // embedded NIC
{"NIC.Slot.1-1-1", true}, // slotted NIC
{"PSU.Slot.1", true}, // PSU
{"Disk.Bay.0:Enclosure.Internal.0-1:RAID.SL.3-1", true},
{"GPU.Slot.1-1", true},
{"FC.Slot.1-1", true}, // Fibre Channel HBA
// System-level descriptions — must NOT be excluded
{"system bios", false},
{"idrac lifecycle", false},
{"idrac card", false},
{"storage controller", false}, // legacy description before fqdd fix
{"", false},
}
for _, tc := range cases {
got := isDeviceBoundFirmwareFQDD(tc.desc)
if got != tc.want {
t.Errorf("isDeviceBoundFirmwareFQDD(%q) = %v, want %v", tc.desc, got, tc.want)
}
}
}