package unraid import ( "testing" "git.mchus.pro/mchus/logpile/internal/parser" ) func TestDetect(t *testing.T) { tests := []struct { name string files []parser.ExtractedFile wantMin int wantMax int shouldFind bool }{ { name: "typical unraid diagnostics", files: []parser.ExtractedFile{ { Path: "box3-diagnostics-20260205-2333/unraid-7.2.0.txt", Content: []byte("7.2.0\n"), }, { Path: "box3-diagnostics-20260205-2333/system/vars.txt", Content: []byte("[parity] => Array\n[disk1] => Array\n"), }, }, wantMin: 50, wantMax: 100, shouldFind: true, }, { name: "unraid with kernel marker", files: []parser.ExtractedFile{ { Path: "diagnostics/system/lscpu.txt", Content: []byte("Unraid kernel build 6.12.54"), }, }, wantMin: 50, wantMax: 100, shouldFind: true, }, { name: "not unraid", files: []parser.ExtractedFile{ { Path: "some/random/file.txt", Content: []byte("just some random content"), }, }, wantMin: 0, wantMax: 0, shouldFind: false, }, } p := &Parser{} for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got := p.Detect(tt.files) if tt.shouldFind && got < tt.wantMin { t.Errorf("Detect() = %v, want at least %v", got, tt.wantMin) } if got > tt.wantMax { t.Errorf("Detect() = %v, want at most %v", got, tt.wantMax) } if !tt.shouldFind && got > 0 { t.Errorf("Detect() = %v, want 0 (should not detect)", got) } }) } } func TestParse_Version(t *testing.T) { files := []parser.ExtractedFile{ { Path: "unraid-7.2.0.txt", Content: []byte("7.2.0\n"), }, } p := &Parser{} result, err := p.Parse(files) if err != nil { t.Fatalf("Parse() error = %v", err) } if len(result.Hardware.Firmware) == 0 { t.Fatal("expected firmware info") } fw := result.Hardware.Firmware[0] if fw.DeviceName != "Unraid OS" { t.Errorf("DeviceName = %v, want 'Unraid OS'", fw.DeviceName) } if fw.Version != "7.2.0" { t.Errorf("Version = %v, want '7.2.0'", fw.Version) } } func TestParse_CPU(t *testing.T) { lscpuContent := `Architecture: x86_64 CPU op-mode(s): 32-bit, 64-bit CPU(s): 16 Model name: Intel(R) Xeon(R) CPU E5-2650 v2 @ 2.60GHz Core(s) per socket: 8 Socket(s): 1 CPU max MHz: 3400.0000 ` files := []parser.ExtractedFile{ { Path: "diagnostics/system/lscpu.txt", Content: []byte(lscpuContent), }, } p := &Parser{} result, err := p.Parse(files) if err != nil { t.Fatalf("Parse() error = %v", err) } if len(result.Hardware.CPUs) == 0 { t.Fatal("expected CPU info") } cpu := result.Hardware.CPUs[0] if cpu.Model != "Intel(R) Xeon(R) CPU E5-2650 v2 @ 2.60GHz" { t.Errorf("Model = %v", cpu.Model) } if cpu.Cores != 8 { t.Errorf("Cores = %v, want 8", cpu.Cores) } if cpu.Threads != 16 { t.Errorf("Threads = %v, want 16", cpu.Threads) } if cpu.FrequencyMHz != 3400 { t.Errorf("FrequencyMHz = %v, want 3400", cpu.FrequencyMHz) } } func TestParse_Memory(t *testing.T) { memContent := ` total used free shared buff/cache available Mem: 50Gi 11Gi 1.4Gi 565Mi 39Gi 39Gi Swap: 0B 0B 0B Total: 50Gi 11Gi 1.4Gi ` files := []parser.ExtractedFile{ { Path: "diagnostics/system/memory.txt", Content: []byte(memContent), }, } p := &Parser{} result, err := p.Parse(files) if err != nil { t.Fatalf("Parse() error = %v", err) } if len(result.Hardware.Memory) == 0 { t.Fatal("expected memory info") } mem := result.Hardware.Memory[0] expectedSizeMB := 50 * 1024 // 50 GiB in MB if mem.SizeMB != expectedSizeMB { t.Errorf("SizeMB = %v, want %v", mem.SizeMB, expectedSizeMB) } if mem.Type != "DRAM" { t.Errorf("Type = %v, want 'DRAM'", mem.Type) } } func TestParse_SMART(t *testing.T) { smartContent := `smartctl 7.5 2025-04-30 r5714 [x86_64-linux-6.12.54-Unraid] (local build) Copyright (C) 2002-25, Bruce Allen, Christian Franke, www.smartmontools.org === START OF INFORMATION SECTION === Device Model: ST4000NM000B-2TF100 Serial Number: WX103EC9 LU WWN Device Id: 5 000c50 0ed59db60 Firmware Version: TNA1 User Capacity: 4,000,787,030,016 bytes [4.00 TB] Sector Size: 512 bytes logical/physical Rotation Rate: 7200 rpm Form Factor: 3.5 inches SATA Version is: SATA 3.3, 6.0 Gb/s (current: 6.0 Gb/s) === START OF READ SMART DATA SECTION === SMART overall-health self-assessment test result: PASSED ` files := []parser.ExtractedFile{ { Path: "diagnostics/smart/ST4000NM000B-2TF100_WX103EC9-20260205-2333 disk1 (sdi).txt", Content: []byte(smartContent), }, } p := &Parser{} result, err := p.Parse(files) if err != nil { t.Fatalf("Parse() error = %v", err) } if len(result.Hardware.Storage) == 0 { t.Fatal("expected storage info") } disk := result.Hardware.Storage[0] if disk.Model != "ST4000NM000B-2TF100" { t.Errorf("Model = %v, want 'ST4000NM000B-2TF100'", disk.Model) } if disk.SerialNumber != "WX103EC9" { t.Errorf("SerialNumber = %v, want 'WX103EC9'", disk.SerialNumber) } if disk.Firmware != "TNA1" { t.Errorf("Firmware = %v, want 'TNA1'", disk.Firmware) } if disk.SizeGB != 4000 { t.Errorf("SizeGB = %v, want 4000", disk.SizeGB) } if disk.Type != "hdd" { t.Errorf("Type = %v, want 'hdd'", disk.Type) } // Check that no health warnings were generated (PASSED health) healthWarnings := 0 for _, event := range result.Events { if event.EventType == "Disk Health" && event.Severity == "warning" { healthWarnings++ } } if healthWarnings != 0 { t.Errorf("Expected no health warnings for PASSED disk, got %v", healthWarnings) } } func TestParser_Metadata(t *testing.T) { p := &Parser{} if p.Name() != "Unraid Parser" { t.Errorf("Name() = %v, want 'Unraid Parser'", p.Name()) } if p.Vendor() != "unraid" { t.Errorf("Vendor() = %v, want 'unraid'", p.Vendor()) } if p.Version() == "" { t.Error("Version() should not be empty") } } func TestParse_MemoryDIMMsFromMeminfo(t *testing.T) { memInfo := `MemTotal: 53393436 kB Handle 0x002D, DMI type 17, 34 bytes Memory Device Size: 16 GB Locator: Node0_Dimm1 Bank Locator: Node0_Bank0 Type: DDR3 Speed: 1333 MT/s Manufacturer: Samsung Serial Number: 238F7649 Part Number: M393B2G70BH0- Rank: 4 Configured Memory Speed: 1333 MT/s Handle 0x002F, DMI type 17, 34 bytes Memory Device Size: No Module Installed Locator: Node0_Dimm2 ` files := []parser.ExtractedFile{ {Path: "diagnostics/system/memory.txt", Content: []byte("Mem: 50Gi")}, {Path: "diagnostics/system/meminfo.txt", Content: []byte(memInfo)}, } p := &Parser{} result, err := p.Parse(files) if err != nil { t.Fatalf("Parse() error = %v", err) } if got := len(result.Hardware.Memory); got != 1 { t.Fatalf("expected only installed DIMM entries, got %d entries", got) } dimm := result.Hardware.Memory[0] if dimm.Slot != "Node0_Dimm1" { t.Errorf("Slot = %q, want Node0_Dimm1", dimm.Slot) } if dimm.SizeMB != 16*1024 { t.Errorf("SizeMB = %d, want %d", dimm.SizeMB, 16*1024) } if dimm.Type != "DDR3" { t.Errorf("Type = %q, want DDR3", dimm.Type) } if dimm.SerialNumber != "238F7649" { t.Errorf("SerialNumber = %q, want 238F7649", dimm.SerialNumber) } } func TestParse_NetworkAndPCIeFromLSPCIAndEthtool(t *testing.T) { lspci := `03:00.0 SCSI storage controller [0100]: Broadcom / LSI SAS2008 PCI-Express Fusion-MPT SAS-2 [Falcon] [1000:0072] (rev 03) 07:00.0 Ethernet controller [0200]: Realtek Semiconductor Co., Ltd. RTL8111/8168/8211/8411 PCI Express Gigabit Ethernet Controller [10ec:8168] (rev 06) ` ethtool := `Settings for eth0: Speed: 1000Mb/s Link detected: yes driver: r8168 firmware-version: bus-info: 0000:07:00.0 -------------------------------- ` files := []parser.ExtractedFile{ {Path: "diagnostics/system/lspci.txt", Content: []byte(lspci)}, {Path: "diagnostics/system/ethtool.txt", Content: []byte(ethtool)}, } p := &Parser{} result, err := p.Parse(files) if err != nil { t.Fatalf("Parse() error = %v", err) } if len(result.Hardware.NetworkAdapters) != 1 { t.Fatalf("expected 1 network adapter, got %d", len(result.Hardware.NetworkAdapters)) } nic := result.Hardware.NetworkAdapters[0] if nic.Location != "0000:07:00.0" { t.Errorf("Location = %q, want 0000:07:00.0", nic.Location) } if nic.Model == "" { t.Error("Model should not be empty") } if nic.Vendor == "" { t.Error("Vendor should not be empty") } if len(result.Hardware.PCIeDevices) < 2 { t.Fatalf("expected at least 2 PCIe devices, got %d", len(result.Hardware.PCIeDevices)) } } func TestParse_HostSerialFallbackFromVarsUUID(t *testing.T) { vars := ` [flashGUID] => 1... [regGUID] => 1...7 [uuid] => 2713440667722491190 ` files := []parser.ExtractedFile{ {Path: "diagnostics/system/vars.txt", Content: []byte(vars)}, } p := &Parser{} result, err := p.Parse(files) if err != nil { t.Fatalf("Parse() error = %v", err) } if result.Hardware.BoardInfo.SerialNumber != "2713440667722491190" { t.Fatalf("BoardInfo.SerialNumber = %q, want vars uuid", result.Hardware.BoardInfo.SerialNumber) } if result.Hardware.BoardInfo.UUID != "2713440667722491190" { t.Fatalf("BoardInfo.UUID = %q, want vars uuid", result.Hardware.BoardInfo.UUID) } }