Files
felhom-agent/internal/storage/r330_smart_attrs_test.go
T
admin adaf86ad57
gates / gates (push) Successful in 53s
R-330: the agent sends SMART 187/188/199 (raw; omitted when unknown)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
2026-10-06 22:13:04 +02:00

65 lines
3.0 KiB
Go

package storage
import (
"encoding/json"
"strings"
"testing"
)
// R-330 (disk health Phase 2) — attributes 187, 188 and 199 ride the wire.
//
// The failing drive of 2026-08-14 carried 187 Reported_Uncorrect at raw 1001 while SMART said PASSED;
// none of the three reached the controller. The values are carried as RAW counters; an absent
// attribute is OMITTED from the JSON (unknown), never sent as 0 (S-39).
// The 2026-08-14 shape: PASSED, 187 raw 1001, plus 188/199 and the existing three.
const r330SATA = `{"smart_status":{"passed":true},"ata_smart_attributes":{"table":[
{"id":5,"raw":{"value":0}},
{"id":187,"raw":{"value":1001}},
{"id":188,"raw":{"value":4295032833}},
{"id":197,"raw":{"value":8}},
{"id":198,"raw":{"value":8}},
{"id":199,"raw":{"value":3}}]}}`
// COMPANION RED-PROOF (observed): delete the three R-330 cases in parseSMART → this fails with
// "187 Reported_Uncorrect must be carried (raw 1001); got <nil>". Restored.
func TestParseSMART_R330_CarriesTheThreeCounters(t *testing.T) {
s := parseSMART([]byte(r330SATA))
if s.ReportedUncorrect == nil || *s.ReportedUncorrect != 1001 {
t.Fatalf("187 Reported_Uncorrect must be carried (raw 1001); got %v", s.ReportedUncorrect)
}
// 188's raw value is vendor-packed on some drives (this one is 0x100010001): carried as reported, not truncated.
if s.CommandTimeout == nil || *s.CommandTimeout != 4295032833 {
t.Fatalf("188 Command_Timeout must be carried as the full raw value; got %v", s.CommandTimeout)
}
if s.UDMACRCErrors == nil || *s.UDMACRCErrors != 3 {
t.Fatalf("199 UDMA_CRC_Error_Count must be carried (raw 3); got %v", s.UDMACRCErrors)
}
if s.PendingSectors == nil || *s.PendingSectors != 8 {
t.Fatalf("the existing counters must be unchanged; pending=%v", s.PendingSectors)
}
}
// A drive (or an NVMe device) that does not report the attributes leaves them nil, and the JSON
// OMITS the keys — the receiver reads "unknown", never a measured zero.
//
// COMPANION RED-PROOF (observed): drop `,omitempty` from the three tags in hub.SmartSummary → this
// fails with "an unreported attribute must be omitted, not sent: … reported_uncorrect …". Restored.
func TestParseSMART_R330_AbsentIsOmittedNotZero(t *testing.T) {
for name, raw := range map[string]string{
"sata without the three": `{"smart_status":{"passed":true},"ata_smart_attributes":{"table":[{"id":5,"raw":{"value":0}}]}}`,
"nvme": `{"smart_status":{"passed":true},"nvme_smart_health_information_log":{"critical_warning":0,"media_errors":0,"percentage_used":3}}`,
} {
s := parseSMART([]byte(raw))
if s.ReportedUncorrect != nil || s.CommandTimeout != nil || s.UDMACRCErrors != nil {
t.Fatalf("%s: unreported attributes must stay nil; got %v %v %v", name, s.ReportedUncorrect, s.CommandTimeout, s.UDMACRCErrors)
}
b, _ := json.Marshal(s)
for _, k := range []string{"reported_uncorrect", "command_timeout", "udma_crc_errors"} {
if strings.Contains(string(b), `"`+k+`"`) {
t.Fatalf("%s: an unreported attribute must be omitted, not sent: %s", name, b)
}
}
}
}