153 lines
4.3 KiB
Go
153 lines
4.3 KiB
Go
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")
|
|
}
|
|
}
|