package xfusion import ( "strings" "testing" "git.mchus.pro/mchus/logpile/internal/models" "git.mchus.pro/mchus/logpile/internal/parser" ) // loadTestArchive extracts the given archive path for use in tests. // Skips the test if the file is not found (CI environments without testdata). func loadTestArchive(t *testing.T, path string) []parser.ExtractedFile { t.Helper() files, err := parser.ExtractArchive(path) if err != nil { t.Skipf("cannot load test archive %s: %v", path, err) } return files } func TestDetect_G5500V7(t *testing.T) { files := loadTestArchive(t, "../../../../example/G5500V7_210619KUGGXGS2000015_20260318-1128.tar.gz") p := &Parser{} score := p.Detect(files) if score < 80 { t.Fatalf("expected Detect score >= 80, got %d", score) } } func TestDetect_ServerFileExportMarkers(t *testing.T) { p := &Parser{} score := p.Detect([]parser.ExtractedFile{ {Path: "dump_info/RTOSDump/versioninfo/app_revision.txt", Content: []byte("Product Name: G5500 V7")}, {Path: "dump_info/LogDump/netcard/netcard_info.txt", Content: []byte("2026-02-04 03:54:06 UTC")}, {Path: "dump_info/AppDump/card_manage/card_info", Content: []byte("OCP Card Info")}, }) if score < 70 { t.Fatalf("expected Detect score >= 70 for xFusion file export markers, got %d", score) } } func TestDetect_Negative(t *testing.T) { p := &Parser{} score := p.Detect([]parser.ExtractedFile{ {Path: "logs/messages.txt", Content: []byte("plain text")}, {Path: "inventory.json", Content: []byte(`{"vendor":"other"}`)}, }) if score != 0 { t.Fatalf("expected Detect score 0 for non-xFusion input, got %d", score) } } func TestParse_G5500V7_BoardInfo(t *testing.T) { files := loadTestArchive(t, "../../../../example/G5500V7_210619KUGGXGS2000015_20260318-1128.tar.gz") p := &Parser{} result, err := p.Parse(files) if err != nil { t.Fatalf("Parse: %v", err) } if result.Hardware == nil { t.Fatal("Hardware is nil") } board := result.Hardware.BoardInfo if board.SerialNumber != "210619KUGGXGS2000015" { t.Errorf("BoardInfo.SerialNumber = %q, want 210619KUGGXGS2000015", board.SerialNumber) } if board.ProductName != "G5500 V7" { t.Errorf("BoardInfo.ProductName = %q, want G5500 V7", board.ProductName) } } func TestParse_G5500V7_CPUs(t *testing.T) { files := loadTestArchive(t, "../../../../example/G5500V7_210619KUGGXGS2000015_20260318-1128.tar.gz") p := &Parser{} result, err := p.Parse(files) if err != nil { t.Fatalf("Parse: %v", err) } if len(result.Hardware.CPUs) != 2 { t.Fatalf("expected 2 CPUs, got %d", len(result.Hardware.CPUs)) } cpu1 := result.Hardware.CPUs[0] if cpu1.Cores != 32 { t.Errorf("CPU1 cores = %d, want 32", cpu1.Cores) } if cpu1.Threads != 64 { t.Errorf("CPU1 threads = %d, want 64", cpu1.Threads) } if cpu1.SerialNumber == "" { t.Error("CPU1 SerialNumber is empty") } } func TestParse_G5500V7_Memory(t *testing.T) { files := loadTestArchive(t, "../../../../example/G5500V7_210619KUGGXGS2000015_20260318-1128.tar.gz") p := &Parser{} result, err := p.Parse(files) if err != nil { t.Fatalf("Parse: %v", err) } // Only 2 DIMMs are populated (rest are "NO DIMM") if len(result.Hardware.Memory) != 2 { t.Fatalf("expected 2 populated DIMMs, got %d", len(result.Hardware.Memory)) } dimm := result.Hardware.Memory[0] if dimm.SizeMB != 65536 { t.Errorf("DIMM0 SizeMB = %d, want 65536", dimm.SizeMB) } if dimm.Type != "DDR5" { t.Errorf("DIMM0 Type = %q, want DDR5", dimm.Type) } } func TestParse_G5500V7_GPUs(t *testing.T) { files := loadTestArchive(t, "../../../../example/G5500V7_210619KUGGXGS2000015_20260318-1128.tar.gz") p := &Parser{} result, err := p.Parse(files) if err != nil { t.Fatalf("Parse: %v", err) } if len(result.Hardware.GPUs) != 8 { t.Fatalf("expected 8 GPUs, got %d", len(result.Hardware.GPUs)) } for _, gpu := range result.Hardware.GPUs { if gpu.SerialNumber == "" { t.Errorf("GPU slot %s has empty SerialNumber", gpu.Slot) } if gpu.Model == "" { t.Errorf("GPU slot %s has empty Model", gpu.Slot) } if gpu.Firmware == "" { t.Errorf("GPU slot %s has empty Firmware", gpu.Slot) } } } func TestParse_G5500V7_NICs(t *testing.T) { files := loadTestArchive(t, "../../../../example/G5500V7_210619KUGGXGS2000015_20260318-1128.tar.gz") p := &Parser{} result, err := p.Parse(files) if err != nil { t.Fatalf("Parse: %v", err) } if len(result.Hardware.NetworkCards) < 1 { t.Fatal("expected at least 1 NIC (OCP CX6), got 0") } nic := result.Hardware.NetworkCards[0] if nic.SerialNumber == "" { t.Errorf("NIC SerialNumber is empty") } } func TestParse_ServerFileExport_NetworkAdaptersAndFirmware(t *testing.T) { p := &Parser{} files := []parser.ExtractedFile{ { Path: "dump_info/AppDump/card_manage/card_info", Content: []byte(strings.TrimSpace(` Pcie Card Info Slot | Vender Id | Device Id | Sub Vender Id | Sub Device Id | Segment Number | Bus Number | Device Number | Function Number | Card Desc | Board Id | PCB Version | CPLD Version | Sub Card Bom Id | PartNum | SerialNumber | OriginalPartNum 1 | 0x15b3 | 0x101f | 0x1f24 | 0x2011 | 0x00 | 0x27 | 0x00 | 0x00 | MT2894 Family [ConnectX-6 Lx] | N/A | N/A | N/A | N/A | 0302Y238 | 02Y238X6RC000058 | OCP Card Info Slot | Vender Id | Device Id | Sub Vender Id | Sub Device Id | Segment Number | Bus Number | Device Number | Function Number | Card Desc | Board Id | PCB Version | CPLD Version | Sub Card Bom Id | PartNum | SerialNumber | OriginalPartNum 1 | 0x15b3 | 0x101f | 0x1f24 | 0x2011 | 0x00 | 0x27 | 0x00 | 0x00 | MT2894 Family [ConnectX-6 Lx] | N/A | N/A | N/A | N/A | 0302Y238 | 02Y238X6RC000058 | `)), }, { Path: "dump_info/LogDump/netcard/netcard_info.txt", Content: []byte(strings.TrimSpace(` 2026-02-04 03:54:06 UTC ProductName :XC385 Manufacture :XFUSION FirmwareVersion :26.39.2048 SlotId :1 Port0 BDF:0000:27:00.0 MacAddr:44:1A:4C:16:E8:03 ActualMac:44:1A:4C:16:E8:03 Port1 BDF:0000:27:00.1 MacAddr:00:00:00:00:00:00 ActualMac:44:1A:4C:16:E8:04 `)), }, { Path: "dump_info/RTOSDump/versioninfo/app_revision.txt", Content: []byte(strings.TrimSpace(` ------------------- iBMC INFO ------------------- Active iBMC Version: (U68)3.08.05.85 Active iBMC Built: 16:46:26 Jan 4 2026 SDK Version: 13.16.30.16 SDK Built: 07:55:18 Dec 12 2025 Active BIOS Version: (U6216)01.02.08.17 Active BIOS Built: 00:00:00 Jan 05 2026 Product Name: G5500 V7 `)), }, } result, err := p.Parse(files) if err != nil { t.Fatalf("Parse: %v", err) } if result.Protocol != "ipmi" || result.SourceType != models.SourceTypeArchive { t.Fatalf("unexpected source metadata: protocol=%q source_type=%q", result.Protocol, result.SourceType) } if result.Hardware == nil { t.Fatal("Hardware is nil") } if len(result.Hardware.NetworkAdapters) != 1 { t.Fatalf("expected 1 network adapter, got %d", len(result.Hardware.NetworkAdapters)) } adapter := result.Hardware.NetworkAdapters[0] if adapter.BDF != "0000:27:00.0" { t.Fatalf("expected network adapter BDF 0000:27:00.0, got %q", adapter.BDF) } if adapter.Firmware != "26.39.2048" { t.Fatalf("expected network adapter firmware 26.39.2048, got %q", adapter.Firmware) } if adapter.SerialNumber != "02Y238X6RC000058" { t.Fatalf("expected network adapter serial from card_info, got %q", adapter.SerialNumber) } if len(adapter.MACAddresses) != 2 || adapter.MACAddresses[0] != "44:1A:4C:16:E8:03" || adapter.MACAddresses[1] != "44:1A:4C:16:E8:04" { t.Fatalf("unexpected MAC addresses: %#v", adapter.MACAddresses) } fwByDevice := make(map[string]models.FirmwareInfo) for _, fw := range result.Hardware.Firmware { fwByDevice[fw.DeviceName] = fw } if fwByDevice["iBMC"].Version != "(U68)3.08.05.85" { t.Fatalf("expected iBMC firmware from app_revision.txt, got %#v", fwByDevice["iBMC"]) } if fwByDevice["BIOS"].Version != "(U6216)01.02.08.17" { t.Fatalf("expected BIOS firmware from app_revision.txt, got %#v", fwByDevice["BIOS"]) } if result.Hardware.BoardInfo.ProductName != "G5500 V7" { t.Fatalf("expected board product fallback from app_revision.txt, got %q", result.Hardware.BoardInfo.ProductName) } } func TestParse_G5500V7_PSUs(t *testing.T) { files := loadTestArchive(t, "../../../../example/G5500V7_210619KUGGXGS2000015_20260318-1128.tar.gz") p := &Parser{} result, err := p.Parse(files) if err != nil { t.Fatalf("Parse: %v", err) } if len(result.Hardware.PowerSupply) != 4 { t.Fatalf("expected 4 PSUs, got %d", len(result.Hardware.PowerSupply)) } for _, psu := range result.Hardware.PowerSupply { if psu.WattageW != 3000 { t.Errorf("PSU slot %s wattage = %d, want 3000", psu.Slot, psu.WattageW) } if psu.SerialNumber == "" { t.Errorf("PSU slot %s has empty SerialNumber", psu.Slot) } } } func TestParse_G5500V7_Storage(t *testing.T) { files := loadTestArchive(t, "../../../../example/G5500V7_210619KUGGXGS2000015_20260318-1128.tar.gz") p := &Parser{} result, err := p.Parse(files) if err != nil { t.Fatalf("Parse: %v", err) } if len(result.Hardware.Storage) != 2 { t.Fatalf("expected 2 storage devices, got %d", len(result.Hardware.Storage)) } for _, disk := range result.Hardware.Storage { if disk.SerialNumber == "" { t.Errorf("disk slot %s has empty SerialNumber", disk.Slot) } if disk.Model == "" { t.Errorf("disk slot %s has empty Model", disk.Slot) } } } func TestParse_G5500V7_Sensors(t *testing.T) { files := loadTestArchive(t, "../../../../example/G5500V7_210619KUGGXGS2000015_20260318-1128.tar.gz") p := &Parser{} result, err := p.Parse(files) if err != nil { t.Fatalf("Parse: %v", err) } if len(result.Sensors) < 20 { t.Fatalf("expected at least 20 sensors, got %d", len(result.Sensors)) } } func TestParse_G5500V7_Events(t *testing.T) { files := loadTestArchive(t, "../../../../example/G5500V7_210619KUGGXGS2000015_20260318-1128.tar.gz") p := &Parser{} result, err := p.Parse(files) if err != nil { t.Fatalf("Parse: %v", err) } if len(result.Events) < 5 { t.Fatalf("expected at least 5 events, got %d", len(result.Events)) } // All events should have real timestamps (not epoch 0) for _, ev := range result.Events { if ev.Timestamp.Year() <= 1970 { t.Errorf("event has epoch timestamp: %v %s", ev.Timestamp, ev.Description) } } } func TestParse_G5500V7_FRU(t *testing.T) { files := loadTestArchive(t, "../../../../example/G5500V7_210619KUGGXGS2000015_20260318-1128.tar.gz") p := &Parser{} result, err := p.Parse(files) if err != nil { t.Fatalf("Parse: %v", err) } if len(result.FRU) < 3 { t.Fatalf("expected at least 3 FRU entries, got %d", len(result.FRU)) } // Check mainboard FRU serial found := false for _, f := range result.FRU { if f.SerialNumber == "210619KUGGXGS2000015" { found = true } } if !found { t.Error("mainboard serial 210619KUGGXGS2000015 not found in FRU") } }