mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-09-10 02:25:56 +00:00
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:
@@ -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"
|
||||
]
|
||||
}
|
||||
|
||||
+1
-1
@@ -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
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user