fix(exporter): populate Present on exported Memory/PSU so reanimator re-import keeps them

ReanimatorMemory.Present and ReanimatorPSU.Present were already declared as
*bool fields (matching ReanimatorStorage.Present), but convertMemoryFromDevices
and convertPSUsFromDevices never set them, unlike convertStorageFromDevices.
Exported JSON therefore had "present" for storage but not for memory/PSU
items. Re-importing a previously exported reanimator.json via
parseUploadedSnapshot (a direct json.Unmarshal into models.AnalysisResult)
left Present=false on those two categories, and the existing
IsInstalledInventory()/present-required filters then dropped them on the next
/chart/current render — reproduced live: fresh TSR upload showed Memory and
Power Supplies correctly, re-uploading the exported reanimator.json for the
same result did not.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Mikhail Chusavitin
2026-08-11 11:11:39 +03:00
co-authored by Claude Sonnet 5
parent f599215760
commit eb6cc207ce
4 changed files with 150 additions and 42 deletions
@@ -2013,3 +2013,67 @@ func TestIsDeviceBoundFirmwareFQDD(t *testing.T) {
}
}
}
// TestConvertToReanimator_MemoryAndPSURoundTripSurvivesReimport is a regression guard
// for a bug where re-uploading a previously exported reanimator.json (the "Reanimator"
// round-trip: export, then re-import the same file) silently dropped Memory and Power
// Supplies from the chart view. convertMemoryFromDevices/convertPSUsFromDevices never
// set the already-declared ReanimatorMemory.Present / ReanimatorPSU.Present output
// field, so the exported JSON omitted "present" for those two sections (unlike
// Storage, which did set it). Re-importing that JSON via parseUploadedSnapshot
// (a direct json.Unmarshal into models.AnalysisResult) left Present=false on the
// round-tripped items, and IsInstalledInventory()/the PSU present check then dropped
// them on the next ConvertToReanimator call. (2026-08-11)
func TestConvertToReanimator_MemoryAndPSURoundTripSurvivesReimport(t *testing.T) {
original := &models.AnalysisResult{
Filename: "test.zip",
CollectedAt: time.Date(2026, 7, 22, 4, 16, 0, 0, time.UTC),
Hardware: &models.HardwareConfig{
BoardInfo: models.BoardInfo{
Manufacturer: "Dell Inc.",
ProductName: "PowerEdge R7715",
SerialNumber: "1TVFYL4",
},
Memory: []models.MemoryDIMM{
{Slot: "A1", Present: true, SizeMB: 32768, SerialNumber: "37CC7641", Status: "OK"},
},
PowerSupply: []models.PSU{
{Slot: "PSU.Slot.1", Present: true, Model: "PWR SPLY,1500W", SerialNumber: "CNDED0064K2RKW", Status: "OK"},
},
},
}
exported, err := ConvertToReanimator(original)
if err != nil {
t.Fatalf("ConvertToReanimator() failed: %v", err)
}
if len(exported.Hardware.Memory) != 1 || exported.Hardware.Memory[0].Present == nil || !*exported.Hardware.Memory[0].Present {
t.Fatalf("expected exported memory to carry present=true, got %+v", exported.Hardware.Memory)
}
if len(exported.Hardware.PowerSupplies) != 1 || exported.Hardware.PowerSupplies[0].Present == nil || !*exported.Hardware.PowerSupplies[0].Present {
t.Fatalf("expected exported PSU to carry present=true, got %+v", exported.Hardware.PowerSupplies)
}
// Simulate the round trip: marshal the exported reanimator JSON, then unmarshal
// it straight into models.AnalysisResult the way parseUploadedSnapshot does when
// a user re-uploads a previously downloaded reanimator.json.
raw, err := json.Marshal(exported)
if err != nil {
t.Fatalf("marshal exported: %v", err)
}
var reimported models.AnalysisResult
if err := json.Unmarshal(raw, &reimported); err != nil {
t.Fatalf("unmarshal into AnalysisResult: %v", err)
}
reconverted, err := ConvertToReanimator(&reimported)
if err != nil {
t.Fatalf("ConvertToReanimator() on reimported result failed: %v", err)
}
if len(reconverted.Hardware.Memory) != 1 {
t.Fatalf("memory did not survive reanimator round trip, got %+v", reconverted.Hardware.Memory)
}
if len(reconverted.Hardware.PowerSupplies) != 1 {
t.Fatalf("power supplies did not survive reanimator round trip, got %+v", reconverted.Hardware.PowerSupplies)
}
}