Introduce canonical hardware.devices repository and align UI/Reanimator exports
This commit is contained in:
152
internal/server/device_repository_test.go
Normal file
152
internal/server/device_repository_test.go
Normal file
@@ -0,0 +1,152 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"git.mchus.pro/mchus/logpile/internal/models"
|
||||
)
|
||||
|
||||
func TestBuildHardwareDevices_DedupSerialThenBDF(t *testing.T) {
|
||||
hw := &models.HardwareConfig{
|
||||
PCIeDevices: []models.PCIeDevice{
|
||||
{Slot: "A1", SerialNumber: "SER-1", BDF: "0000:01:00.0", DeviceClass: "NetworkController"},
|
||||
{Slot: "A2", SerialNumber: "SER-1", BDF: "0000:02:00.0", DeviceClass: "NetworkController"},
|
||||
{Slot: "B1", SerialNumber: "", BDF: "0000:03:00.0", DeviceClass: "NetworkController"},
|
||||
{Slot: "B2", SerialNumber: "", BDF: "0000:03:00.0", DeviceClass: "NetworkController"},
|
||||
{Slot: "C1", SerialNumber: "", BDF: "", DeviceClass: "NetworkController"},
|
||||
{Slot: "C2", SerialNumber: "", BDF: "", DeviceClass: "NetworkController"},
|
||||
},
|
||||
}
|
||||
|
||||
devices := BuildHardwareDevices(hw)
|
||||
// 1 board + (SER-1 dedup -> 1) + (BDF 03 dedup -> 1) + (C1,C2 keep both) = 5
|
||||
if len(devices) != 5 {
|
||||
t.Fatalf("expected 5 devices after dedupe, got %d", len(devices))
|
||||
}
|
||||
|
||||
bySlot := map[string]bool{}
|
||||
for _, d := range devices {
|
||||
bySlot[d.Slot] = true
|
||||
}
|
||||
if !bySlot["A1"] && !bySlot["A2"] {
|
||||
t.Fatalf("expected one serial-deduped A* device")
|
||||
}
|
||||
if bySlot["B1"] && bySlot["B2"] {
|
||||
t.Fatalf("expected B1/B2 to dedupe by bdf")
|
||||
}
|
||||
if !bySlot["C1"] || !bySlot["C2"] {
|
||||
t.Fatalf("expected C1 and C2 to remain without serial/bdf")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildHardwareDevices_SkipsEmptyMemorySlots(t *testing.T) {
|
||||
hw := &models.HardwareConfig{
|
||||
Memory: []models.MemoryDIMM{
|
||||
{Slot: "A1", Present: true, SizeMB: 32768, SerialNumber: "DIMM-1"},
|
||||
{Slot: "A2", Present: false, SizeMB: 0, SerialNumber: "DIMM-2"},
|
||||
},
|
||||
}
|
||||
|
||||
devices := BuildHardwareDevices(hw)
|
||||
memoryCount := 0
|
||||
for _, d := range devices {
|
||||
if d.Kind == models.DeviceKindMemory {
|
||||
memoryCount++
|
||||
if d.Slot == "A2" {
|
||||
t.Fatalf("empty memory slot should not be included")
|
||||
}
|
||||
}
|
||||
}
|
||||
if memoryCount != 1 {
|
||||
t.Fatalf("expected 1 installed memory record, got %d", memoryCount)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildHardwareDevices_DedupCrossKindByBDF(t *testing.T) {
|
||||
hw := &models.HardwareConfig{
|
||||
PCIeDevices: []models.PCIeDevice{
|
||||
{
|
||||
Slot: "SL0CP0_001",
|
||||
BDF: "02:00.0",
|
||||
DeviceClass: "DisplayController",
|
||||
VendorID: 0x1a03,
|
||||
DeviceID: 0x2000,
|
||||
PartNumber: "ASPEED Graphics Family",
|
||||
Manufacturer: "ASPEED Technology, Inc.",
|
||||
},
|
||||
},
|
||||
GPUs: []models.GPU{
|
||||
{
|
||||
Slot: "SL0CP0_001",
|
||||
BDF: "02:00.0",
|
||||
Model: "ASPEED Graphics Family",
|
||||
Manufacturer: "ASPEED Technology, Inc.",
|
||||
VendorID: 0x1a03,
|
||||
DeviceID: 0x2000,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
devices := BuildHardwareDevices(hw)
|
||||
count := 0
|
||||
for _, d := range devices {
|
||||
if d.BDF == "02:00.0" {
|
||||
count++
|
||||
}
|
||||
}
|
||||
if count != 1 {
|
||||
t.Fatalf("expected 1 canonical device for bdf 02:00.0, got %d", count)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildHardwareDevices_SkipsFirmwareOnlyNumericSlots(t *testing.T) {
|
||||
hw := &models.HardwareConfig{
|
||||
PCIeDevices: []models.PCIeDevice{
|
||||
{Slot: "0", DeviceClass: "Unknown", Manufacturer: "-", PartNumber: "-", Description: "-"},
|
||||
{Slot: "1", DeviceClass: "Other", Manufacturer: "unknown", PartNumber: "N/A", Description: "NULL"},
|
||||
},
|
||||
}
|
||||
|
||||
devices := BuildHardwareDevices(hw)
|
||||
for _, d := range devices {
|
||||
if d.Kind == models.DeviceKindPCIe && (d.Slot == "0" || d.Slot == "1") {
|
||||
t.Fatalf("firmware-only numeric-slot pcie record must be filtered, got slot %q", d.Slot)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandleGetConfig_ReturnsCanonicalHardware(t *testing.T) {
|
||||
srv := &Server{}
|
||||
srv.SetResult(&models.AnalysisResult{
|
||||
Hardware: &models.HardwareConfig{
|
||||
BoardInfo: models.BoardInfo{ProductName: "X", SerialNumber: "SN-1"},
|
||||
CPUs: []models.CPU{{Socket: 0, Model: "CPU"}},
|
||||
},
|
||||
})
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/config", nil)
|
||||
w := httptest.NewRecorder()
|
||||
srv.handleGetConfig(w, req)
|
||||
|
||||
if w.Code != http.StatusOK {
|
||||
t.Fatalf("expected 200, got %d", w.Code)
|
||||
}
|
||||
|
||||
var payload map[string]any
|
||||
if err := json.NewDecoder(w.Body).Decode(&payload); err != nil {
|
||||
t.Fatalf("decode response: %v", err)
|
||||
}
|
||||
hardware, ok := payload["hardware"].(map[string]any)
|
||||
if !ok {
|
||||
t.Fatalf("expected hardware object")
|
||||
}
|
||||
if _, ok := hardware["devices"]; !ok {
|
||||
t.Fatalf("expected hardware.devices in config response")
|
||||
}
|
||||
if _, ok := hardware["cpus"]; ok {
|
||||
t.Fatalf("did not expect legacy hardware.cpus in config response")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user