Fix mock disk health presentation

Contract-Neutral: The storage wearout sentinel contract is unchanged; mock fixtures now encode unreported endurance as the existing canonical -1 value and carry canonical mock proof.
This commit is contained in:
rcourtman
2026-08-25 21:34:41 +01:00
parent 485376a5ec
commit e497ff987c
5 changed files with 129 additions and 60 deletions
+13 -25
View File
@@ -1,29 +1,21 @@
{
"version": 1,
"base_sha": "51d49474e461eaca83a75065ba370f3092d6ba34",
"verified_at": "2026-08-25T20:20:32Z",
"base_sha": "485376a5ecb3da171387dc72c08b20383b68ff9a",
"verified_at": "2026-08-25T20:32:21Z",
"result": "passed",
"changed_paths": [
"frontend-modern/src/components/GitHubStarBanner.tsx",
"frontend-modern/src/components/Settings/UpdatesSettingsPanel.tsx",
"frontend-modern/src/components/WhatsNewCard.tsx",
"frontend-modern/src/utils/localStorage.ts"
"frontend-modern/src/features/storageBackups/diskPresentation.ts"
],
"content_sha256": {
"frontend-modern/src/components/GitHubStarBanner.tsx": "7c5b51cb927fa778bd6bb201793a20c084d4e3f6bfab39feb4573fe2759f8c85",
"frontend-modern/src/components/Settings/UpdatesSettingsPanel.tsx": "b0cb063a25f0c9a966522b740afa962163d87a50e241030ac289f1a5679063ad",
"frontend-modern/src/components/WhatsNewCard.tsx": "5e5d7314e46b8aa496f73a42e1abc37a294a3f3ebff3c3c424c33f8d615961d0",
"frontend-modern/src/utils/localStorage.ts": "452dbaa0e4014e0af635b24502ffbe53c738a449670ac6b4c381b3033c53ee0a"
"frontend-modern/src/features/storageBackups/diskPresentation.ts": "ff16016cbd5d1edbe77f8b4e02dd2f2634abfe7885376b83820c00ce640909e2"
},
"routes": [
"/prompt-verification.html",
"/proxmox/overview",
"/settings/system-updates"
"/proxmox/storage?tab=disks"
],
"viewports": [
{
"width": 1440,
"height": 900
"width": 1280,
"height": 800
},
{
"width": 390,
@@ -31,17 +23,13 @@
}
],
"states": [
"compact post-update notice over an active infrastructure page without a blocking backdrop",
"opt-in categorized release changelog dialog at desktop and phone widths",
"one-time GitHub star prompt at desktop and phone widths",
"dismissed release notice, changelog dialog, and star prompt states",
"System Updates current-version panel in the signed-in development runtime"
"loaded physical-disk table with critical, warning, and healthy health states",
"healthy status text using the emerald success tone while unknown evidence remains neutral"
],
"interactions": [
"opened the changelog from See what's new and closed it with both Escape and Got it",
"dismissed the compact update notice without opening the changelog",
"closed the GitHub star prompt and confirmed it left the page cleanly",
"navigated the signed-in System Updates page and confirmed development builds do not claim published release notes",
"restored the browser viewport and closed all verification tabs"
"loaded the Physical Disks tab from a phone-width viewport to avoid unrelated desktop background hydration",
"confirmed the first Healthy status resolved to the emerald class and rgb(110, 231, 183) in the active dark theme",
"resized the loaded table to desktop width and confirmed health rows remained readable without clipping",
"restored the browser viewport override"
]
}
@@ -556,7 +556,7 @@ describe('getPhysicalDiskHealthStatus branch coverage', () => {
expect(getPhysicalDiskHealthStatus(disk)).toEqual({
label: 'Healthy',
summary: 'No active disk-health issues.',
tone: 'text-base-content',
tone: 'text-emerald-700 dark:text-emerald-300',
});
});
@@ -646,12 +646,11 @@ export function getPhysicalDiskHealthStatus(
};
}
const isHealthy = PHYSICAL_DISK_HEALTHY_STATES.has(normalizedHealth);
return {
label: PHYSICAL_DISK_HEALTHY_STATES.has(normalizedHealth) ? 'Healthy' : 'Unknown',
summary: PHYSICAL_DISK_HEALTHY_STATES.has(normalizedHealth)
? 'No active disk-health issues.'
: 'Health state is not reported.',
tone: 'text-base-content',
label: isHealthy ? 'Healthy' : 'Unknown',
summary: isHealthy ? 'No active disk-health issues.' : 'Health state is not reported.',
tone: isHealthy ? 'text-emerald-700 dark:text-emerald-300' : 'text-base-content',
};
}
+36 -29
View File
@@ -6897,48 +6897,55 @@ func generateDisksForNode(node models.Node) []models.PhysicalDisk {
diskCount := 1 + mockStableChoice(3, strings.TrimSpace(node.ID), strings.TrimSpace(node.Instance), strings.TrimSpace(node.Name), "physical-disk-count")
diskModels := []struct {
model string
diskType string
size int64
model string
diskType string
size int64
reportsWearout bool
}{
{"Samsung SSD 970 EVO Plus 1TB", "nvme", 1000204886016},
{"WD Blue SN570 500GB", "nvme", 500107862016},
{"Crucial MX500 2TB", "sata", 2000398934016},
{"Seagate BarraCuda 4TB", "sata", 4000787030016},
{"Kingston NV2 250GB", "nvme", 250059350016},
{"WD Red Pro 8TB", "sata", 8001563222016},
{"Samsung 980 PRO 2TB", "nvme", 2000398934016},
{"Intel SSD 660p 1TB", "nvme", 1000204886016},
{"Toshiba X300 6TB", "sata", 6001175126016},
{"Samsung SSD 970 EVO Plus 1TB", "nvme", 1000204886016, true},
{"WD Blue SN570 500GB", "nvme", 500107862016, true},
{"Crucial MX500 2TB", "sata", 2000398934016, true},
{"Seagate BarraCuda 4TB", "sata", 4000787030016, false},
{"Kingston NV2 250GB", "nvme", 250059350016, true},
{"WD Red Pro 8TB", "sata", 8001563222016, false},
{"Samsung 980 PRO 2TB", "nvme", 2000398934016, true},
{"Intel SSD 660p 1TB", "nvme", 1000204886016, true},
{"Toshiba X300 6TB", "sata", 6001175126016, false},
}
for i := 0; i < diskCount; i++ {
diskModel := diskModels[mockStableChoice(len(diskModels), strings.TrimSpace(node.ID), strings.TrimSpace(node.Instance), strings.TrimSpace(node.Name), fmt.Sprintf("%d", i), "physical-disk-model")]
// Generate health status - most are healthy
seedParts := []string{
strings.TrimSpace(node.ID),
strings.TrimSpace(node.Instance),
strings.TrimSpace(node.Name),
fmt.Sprintf("%d", i),
}
// Keep alert-producing health evidence stable across mock restarts.
// Most disks are healthy, while a small deterministic cohort exercises
// unknown and failed states.
health := "PASSED"
if rand.Float64() < 0.05 { // 5% chance of failure
healthRoll := mockStableChoice(100, append(seedParts, "physical-disk-health")...)
if healthRoll < 5 {
health = "FAILED"
} else if rand.Float64() < 0.1 { // 10% chance of unknown
} else if healthRoll < 15 {
health = "UNKNOWN"
}
// Generate wearout for SSDs (percentage life remaining; lower numbers mean heavy wear)
wearout := 0
if diskModel.diskType == "nvme" || diskModel.diskType == "sata" {
if rand.Float64() < 0.7 { // 70% chance it's an SSD with wearout data
wearout = rand.Intn(50) + 50 // 50-100% life remaining
if rand.Float64() < 0.1 { // 10% chance of low life
wearout = rand.Intn(15) + 5 // 5-20% life remaining
}
// Wearout is percentage life remaining. -1 is the canonical unreported
// sentinel; 0 is real evidence that an SSD/NVMe has no life remaining
// and would correctly render as "Replace Now". Rotational disks never
// receive synthetic endurance data.
wearout := -1
if diskModel.reportsWearout &&
mockStableChoice(100, append(seedParts, "physical-disk-wearout-reported")...) < 70 {
wearout = 50 + mockStableChoice(51, append(seedParts, "physical-disk-wearout-normal")...)
if mockStableChoice(100, append(seedParts, "physical-disk-wearout-low-cohort")...) < 10 {
wearout = 5 + mockStableChoice(16, append(seedParts, "physical-disk-wearout-low")...)
}
}
if wearout < 0 {
wearout = 0
}
if wearout > 100 {
wearout = 100
}
devPath := fmt.Sprintf("/dev/%s%d", []string{"nvme", "sd"}[i%2], i)
diskID := fmt.Sprintf("%s-%s-%s", node.Instance, node.Name, devPath)
+75
View File
@@ -9,6 +9,7 @@ import (
"testing"
"time"
"github.com/rcourtman/pulse-go-rewrite/internal/models"
"github.com/rcourtman/pulse-go-rewrite/internal/operationaltrust"
"github.com/rcourtman/pulse-go-rewrite/internal/truenas"
"github.com/rcourtman/pulse-go-rewrite/internal/unifiedresources"
@@ -74,6 +75,80 @@ func TestUnifiedResourceSnapshotIncludesPlatformFixtures(t *testing.T) {
}
}
func TestGenerateDisksForNodeUsesStableRealisticHealthEvidence(t *testing.T) {
totals := map[string]int{}
reportedWearout := 0
unreportedWearout := 0
lowWearout := 0
rotationalModels := map[string]struct{}{
"Seagate BarraCuda 4TB": {},
"WD Red Pro 8TB": {},
"Toshiba X300 6TB": {},
}
for i := 0; i < 300; i++ {
node := models.Node{
ID: fmt.Sprintf("disk-health-node-%d", i),
Name: fmt.Sprintf("pve-%d", i),
Instance: "mock-pve",
}
first := generateDisksForNode(node)
second := generateDisksForNode(node)
if len(first) != len(second) {
t.Fatalf("disk count changed for stable node %q: %d then %d", node.ID, len(first), len(second))
}
for diskIndex := range first {
disk := first[diskIndex]
repeated := second[diskIndex]
if disk.Health != repeated.Health || disk.Wearout != repeated.Wearout {
t.Fatalf(
"health evidence changed for %q disk %d: health=%q wearout=%d, then health=%q wearout=%d",
node.ID,
diskIndex,
disk.Health,
disk.Wearout,
repeated.Health,
repeated.Wearout,
)
}
totals[disk.Health]++
if disk.Wearout == 0 {
t.Fatalf("mock disk %q encoded unreported endurance as spent 0%% life", disk.ID)
}
if _, rotational := rotationalModels[disk.Model]; rotational && disk.Wearout != -1 {
t.Fatalf("rotational mock disk %q reports SSD life %d%%", disk.Model, disk.Wearout)
}
if disk.Wearout < 0 {
unreportedWearout++
} else {
reportedWearout++
if disk.Wearout <= 9 {
lowWearout++
}
}
}
}
if totals["PASSED"] <= totals["FAILED"]+totals["UNKNOWN"] {
t.Fatalf("mock disk health is not predominantly healthy: %+v", totals)
}
for _, health := range []string{"PASSED", "UNKNOWN", "FAILED"} {
if totals[health] == 0 {
t.Fatalf("mock disk health does not exercise %q: %+v", health, totals)
}
}
if reportedWearout == 0 || unreportedWearout == 0 || lowWearout == 0 {
t.Fatalf(
"mock wearout mix missing a required state: reported=%d unreported=%d low=%d",
reportedWearout,
unreportedWearout,
lowWearout,
)
}
}
func TestUnifiedResourceSnapshotIncludesRuntimeNativeTabFixtures(t *testing.T) {
previous := IsMockEnabled()
mustSetEnabled(t, true)