package lenovo_xcc import ( "testing" "git.mchus.pro/mchus/logpile/internal/models" "git.mchus.pro/mchus/logpile/internal/parser" ) const exampleArchive = "/Users/mchusavitin/Documents/git/logpile/example/7D76CTO1WW_JF0002KT_xcc_mini-log_20260413-122150.zip" func TestDetect_LenovoXCCMiniLog(t *testing.T) { files, err := parser.ExtractArchive(exampleArchive) if err != nil { t.Skipf("example archive not available: %v", err) } p := &Parser{} score := p.Detect(files) if score < 80 { t.Errorf("expected Detect score >= 80 for XCC mini-log archive, got %d", score) } } func TestParse_LenovoXCCMiniLog_BasicSysInfo(t *testing.T) { files, err := parser.ExtractArchive(exampleArchive) if err != nil { t.Skipf("example archive not available: %v", err) } p := &Parser{} result, err := p.Parse(files) if err != nil { t.Fatalf("Parse returned error: %v", err) } if result == nil || result.Hardware == nil { t.Fatal("Parse returned nil result or hardware") } hw := result.Hardware if hw.BoardInfo.SerialNumber == "" { t.Error("BoardInfo.SerialNumber is empty") } if hw.BoardInfo.ProductName == "" { t.Error("BoardInfo.ProductName is empty") } t.Logf("BoardInfo: serial=%s model=%s uuid=%s", hw.BoardInfo.SerialNumber, hw.BoardInfo.ProductName, hw.BoardInfo.UUID) } func TestParse_LenovoXCCMiniLog_CPUs(t *testing.T) { files, err := parser.ExtractArchive(exampleArchive) if err != nil { t.Skipf("example archive not available: %v", err) } p := &Parser{} result, _ := p.Parse(files) if result == nil || result.Hardware == nil { t.Fatal("Parse returned nil") } if len(result.Hardware.CPUs) == 0 { t.Error("expected at least one CPU, got none") } for i, cpu := range result.Hardware.CPUs { t.Logf("CPU[%d]: socket=%d model=%q cores=%d threads=%d freq=%dMHz", i, cpu.Socket, cpu.Model, cpu.Cores, cpu.Threads, cpu.FrequencyMHz) } } func TestParse_LenovoXCCMiniLog_Memory(t *testing.T) { files, err := parser.ExtractArchive(exampleArchive) if err != nil { t.Skipf("example archive not available: %v", err) } p := &Parser{} result, _ := p.Parse(files) if result == nil || result.Hardware == nil { t.Fatal("Parse returned nil") } if len(result.Hardware.Memory) == 0 { t.Error("expected memory DIMMs, got none") } t.Logf("Memory: %d DIMMs", len(result.Hardware.Memory)) for i, m := range result.Hardware.Memory { t.Logf("DIMM[%d]: slot=%s present=%v size=%dMB sn=%s", i, m.Slot, m.Present, m.SizeMB, m.SerialNumber) } } func TestParse_LenovoXCCMiniLog_Storage(t *testing.T) { files, err := parser.ExtractArchive(exampleArchive) if err != nil { t.Skipf("example archive not available: %v", err) } p := &Parser{} result, _ := p.Parse(files) if result == nil || result.Hardware == nil { t.Fatal("Parse returned nil") } t.Logf("Storage: %d disks", len(result.Hardware.Storage)) for i, s := range result.Hardware.Storage { t.Logf("Disk[%d]: slot=%s model=%q size=%dGB sn=%s", i, s.Slot, s.Model, s.SizeGB, s.SerialNumber) } } func TestParse_LenovoXCCMiniLog_PCIeCards(t *testing.T) { files, err := parser.ExtractArchive(exampleArchive) if err != nil { t.Skipf("example archive not available: %v", err) } p := &Parser{} result, _ := p.Parse(files) if result == nil || result.Hardware == nil { t.Fatal("Parse returned nil") } t.Logf("PCIe cards: %d", len(result.Hardware.PCIeDevices)) for i, c := range result.Hardware.PCIeDevices { t.Logf("Card[%d]: slot=%s desc=%q bdf=%s", i, c.Slot, c.Description, c.BDF) } } func TestParse_LenovoXCCMiniLog_PSUs(t *testing.T) { files, err := parser.ExtractArchive(exampleArchive) if err != nil { t.Skipf("example archive not available: %v", err) } p := &Parser{} result, _ := p.Parse(files) if result == nil || result.Hardware == nil { t.Fatal("Parse returned nil") } if len(result.Hardware.PowerSupply) == 0 { t.Error("expected PSUs, got none") } for i, p := range result.Hardware.PowerSupply { t.Logf("PSU[%d]: slot=%s wattage=%dW status=%s sn=%s", i, p.Slot, p.WattageW, p.Status, p.SerialNumber) } } func TestParse_LenovoXCCMiniLog_Sensors(t *testing.T) { files, err := parser.ExtractArchive(exampleArchive) if err != nil { t.Skipf("example archive not available: %v", err) } p := &Parser{} result, _ := p.Parse(files) if result == nil { t.Fatal("Parse returned nil") } if len(result.Sensors) == 0 { t.Error("expected sensors, got none") } t.Logf("Sensors: %d", len(result.Sensors)) } func TestParse_LenovoXCCMiniLog_Events(t *testing.T) { files, err := parser.ExtractArchive(exampleArchive) if err != nil { t.Skipf("example archive not available: %v", err) } p := &Parser{} result, _ := p.Parse(files) if result == nil { t.Fatal("Parse returned nil") } if len(result.Events) == 0 { t.Error("expected events, got none") } t.Logf("Events: %d", len(result.Events)) for i, e := range result.Events { if i >= 5 { break } t.Logf("Event[%d]: severity=%s ts=%s desc=%q", i, e.Severity, e.Timestamp.Format("2006-01-02T15:04:05"), e.Description) } } func TestParse_LenovoXCCMiniLog_FRU(t *testing.T) { files, err := parser.ExtractArchive(exampleArchive) if err != nil { t.Skipf("example archive not available: %v", err) } p := &Parser{} result, _ := p.Parse(files) if result == nil { t.Fatal("Parse returned nil") } t.Logf("FRU: %d entries", len(result.FRU)) for i, f := range result.FRU { t.Logf("FRU[%d]: desc=%q product=%q serial=%q", i, f.Description, f.ProductName, f.SerialNumber) } } func TestParse_LenovoXCCMiniLog_Firmware(t *testing.T) { files, err := parser.ExtractArchive(exampleArchive) if err != nil { t.Skipf("example archive not available: %v", err) } p := &Parser{} result, _ := p.Parse(files) if result == nil || result.Hardware == nil { t.Fatal("Parse returned nil") } if len(result.Hardware.Firmware) == 0 { t.Error("expected firmware entries, got none") } for i, f := range result.Hardware.Firmware { t.Logf("FW[%d]: name=%q version=%q buildtime=%q", i, f.DeviceName, f.Version, f.BuildTime) } } func TestParseDIMMs_UnqualifiedDIMMAddsWarningEvent(t *testing.T) { content := []byte(`{ "items": [{ "memory": [{ "memory_name": "DIMM A1", "memory_status": "Unqualified DIMM", "memory_type": "DDR5", "memory_capacity": 32 }] }] }`) memory, events := parseDIMMs(content) if len(memory) != 1 { t.Fatalf("expected 1 DIMM, got %d", len(memory)) } if len(events) != 1 { t.Fatalf("expected 1 warning event, got %d", len(events)) } if events[0].Severity != models.SeverityWarning { t.Fatalf("expected warning severity, got %q", events[0].Severity) } if events[0].SensorName != "DIMM A1" { t.Fatalf("unexpected sensor name: %q", events[0].SensorName) } } func TestSeverity_UnqualifiedDIMMMessageBecomesWarning(t *testing.T) { if got := xccSeverity("I", "System found Unqualified DIMM in slot DIMM A1"); got != models.SeverityWarning { t.Fatalf("expected warning severity, got %q", got) } } func TestParseBasicSysInfo_CleansPlaceholderValuesAndSetsTargetHost(t *testing.T) { result := &models.AnalysisResult{Hardware: &models.HardwareConfig{}} content := []byte(`{ "items": [{ "machine_name": " sr650v3-node01 ", "machine_typemodel": " 7D76CTO1WW ", "serial_number": " Not Specified ", "uuid": "N/A" }] }`) parseBasicSysInfo(content, result) if result.TargetHost != "sr650v3-node01" { t.Fatalf("unexpected target host: %q", result.TargetHost) } if result.Hardware.BoardInfo.ProductName != "7D76CTO1WW" { t.Fatalf("unexpected product name: %q", result.Hardware.BoardInfo.ProductName) } if result.Hardware.BoardInfo.SerialNumber != "" { t.Fatalf("expected serial number to be cleaned, got %q", result.Hardware.BoardInfo.SerialNumber) } if result.Hardware.BoardInfo.UUID != "" { t.Fatalf("expected UUID to be cleaned, got %q", result.Hardware.BoardInfo.UUID) } } func TestEnrichBoardFromFRU_SystemBoardManufacturerOnly(t *testing.T) { result := &models.AnalysisResult{ Hardware: &models.HardwareConfig{}, FRU: []models.FRUInfo{ {Description: "Power Supply 1", Manufacturer: "Ignore Me"}, {Description: "System Board", Manufacturer: " Lenovo "}, }, } enrichBoardFromFRU(result) if result.Hardware.BoardInfo.Manufacturer != "Lenovo" { t.Fatalf("unexpected manufacturer: %q", result.Hardware.BoardInfo.Manufacturer) } } func TestEnrichPSUsFromSensors_AssignsTelemetryBySlot(t *testing.T) { psus := []models.PSU{ {Slot: "1"}, {Slot: "2"}, } sensors := []models.SensorReading{ {Name: "PSU1 Input Power", Value: 430}, {Name: "Power Supply 1 Output Power", Value: 390}, {Name: "PWS1 AC Voltage", Value: 230.5}, {Name: "PSU2 Input Power", Value: 0}, {Name: "PSU3 Input Power", Value: 999}, {Name: "Fan 1", Value: 12000}, } got := enrichPSUsFromSensors(psus, sensors) if got[0].InputPowerW != 430 { t.Fatalf("unexpected PSU1 input power: %d", got[0].InputPowerW) } if got[0].OutputPowerW != 390 { t.Fatalf("unexpected PSU1 output power: %d", got[0].OutputPowerW) } if got[0].InputVoltage != 230.5 { t.Fatalf("unexpected PSU1 input voltage: %v", got[0].InputVoltage) } if got[1].InputPowerW != 0 || got[1].OutputPowerW != 0 || got[1].InputVoltage != 0 { t.Fatalf("unexpected telemetry assigned to PSU2: %+v", got[1]) } } func TestMapDiskHealthStatus(t *testing.T) { tests := []struct { name string code int stateStr string want string }{ {name: "normal", code: 2, stateStr: "Online", want: "OK"}, {name: "warning", code: 1, stateStr: "Online", want: "Warning"}, {name: "predictive failure", code: 4, stateStr: "Online", want: "Warning"}, {name: "critical", code: 3, stateStr: "Failed", want: "Critical"}, {name: "fallback state", code: 0, stateStr: "Rebuilding", want: "Rebuilding"}, {name: "unknown", code: 0, stateStr: "", want: "Unknown"}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := mapDiskHealthStatus(tt.code, tt.stateStr); got != tt.want { t.Fatalf("got %q, want %q", got, tt.want) } }) } } func TestClassifySensorType(t *testing.T) { tests := []struct { name string in string unit string want string }{ {name: "unit rpm", in: "Fan 1", unit: "RPM", want: "fan"}, {name: "unit celsius", in: "CPU Temp", unit: "C", want: "temperature"}, {name: "unit watts", in: "PSU1 Input Power", unit: "W", want: "power"}, {name: "unit volts", in: "PWS1 AC Voltage", unit: "V", want: "voltage"}, {name: "unit amps", in: "PSU1 Current", unit: "A", want: "current"}, {name: "name fallback", in: "GPU Temp", unit: "", want: "temperature"}, {name: "other", in: "Presence", unit: "", want: "other"}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := classifySensorType(tt.in, tt.unit); got != tt.want { t.Fatalf("got %q, want %q", got, tt.want) } }) } } func TestCleanXCCValue(t *testing.T) { tests := []struct { in string want string }{ {in: " Lenovo ", want: "Lenovo"}, {in: "N/A", want: ""}, {in: " not specified ", want: ""}, {in: "-", want: ""}, } for _, tt := range tests { if got := cleanXCCValue(tt.in); got != tt.want { t.Fatalf("cleanXCCValue(%q) = %q, want %q", tt.in, got, tt.want) } } }