R-330: the controller decodes SMART 187/188/199 from the agent (carried only, no verdict change)
gates / gates (push) Successful in 56s
gates / gates (push) Successful in 56s
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
This commit is contained in:
@@ -0,0 +1,61 @@
|
||||
package agentapi
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// R-330 (disk health Phase 2, the wire only). The agent (v0.150.0+) sends three more SATA raw
|
||||
// counters; the controller must DECODE them (a field the struct lacks is silently dropped by
|
||||
// encoding/json) and must not yet ACT on them.
|
||||
|
||||
// The 2026-08-14 failing drive as the agent now reports it (values from
|
||||
// felhom.eu/documentation/audits/fixtures/smart-ST3000VX010-failing-2026-08-14.json).
|
||||
const r330AgentJSON = `{"health":"PASSED","temperature_c":40,"power_on_hours":null,
|
||||
"reallocated_sectors":0,"pending_sectors":352,"offline_uncorrectable":352,
|
||||
"reported_uncorrect":1001,"command_timeout":0,"udma_crc_errors":0,
|
||||
"critical_warning":null,"media_errors":null,"percentage_used":null}`
|
||||
|
||||
// COMPANION RED-PROOF (observed): rename the controller tag `reported_uncorrect` → `reported_uncorrectable`
|
||||
// → this fails with "187 must decode from the agent's JSON (raw 1001); got <nil>". Restored.
|
||||
func TestR330_DecodesTheThreeCounters(t *testing.T) {
|
||||
var s SmartSummary
|
||||
if err := json.Unmarshal([]byte(r330AgentJSON), &s); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if s.ReportedUncorrect == nil || *s.ReportedUncorrect != 1001 {
|
||||
t.Fatalf("187 must decode from the agent's JSON (raw 1001); got %v", s.ReportedUncorrect)
|
||||
}
|
||||
// A measured ZERO stays a zero (not nil): 188 and 199 were reported as 0 by this drive.
|
||||
if s.CommandTimeout == nil || *s.CommandTimeout != 0 || s.UDMACRCErrors == nil || *s.UDMACRCErrors != 0 {
|
||||
t.Fatalf("188/199 reported as 0 must decode as a measured 0; got %v %v", s.CommandTimeout, s.UDMACRCErrors)
|
||||
}
|
||||
|
||||
// An older agent omits the keys → nil (unknown), never 0.
|
||||
var old SmartSummary
|
||||
if err := json.Unmarshal([]byte(`{"health":"PASSED","pending_sectors":0}`), &old); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if old.ReportedUncorrect != nil || old.CommandTimeout != nil || old.UDMACRCErrors != nil {
|
||||
t.Fatalf("an older agent's report must leave the counters nil (unknown); got %v %v %v",
|
||||
old.ReportedUncorrect, old.CommandTimeout, old.UDMACRCErrors)
|
||||
}
|
||||
}
|
||||
|
||||
// CARRIED ONLY: tonight the counters change no verdict, label or degraded-attribute list — reading
|
||||
// them is a change to what a household is told and is not taken here (R-330's next slice).
|
||||
func TestR330_CountersChangeNoVerdictYet(t *testing.T) {
|
||||
with := realDrive()
|
||||
big := int64(5000)
|
||||
with.ReportedUncorrect, with.CommandTimeout, with.UDMACRCErrors = &big, &big, &big
|
||||
without := realDrive()
|
||||
for _, prior := range []DiskPrior{{}, {SawUncorrectable: true}} {
|
||||
if a, b := DiskVerdictFor(with, prior), DiskVerdictFor(without, prior); a != b {
|
||||
t.Fatalf("prior %+v: the R-330 counters changed the verdict (%v vs %v) — that is a household-facing change", prior, a, b)
|
||||
}
|
||||
}
|
||||
if a, b := DegradedAttributes(with), DegradedAttributes(without); !reflect.DeepEqual(a, b) {
|
||||
t.Fatalf("the R-330 counters changed the degraded-attribute list: %v vs %v", a, b)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user