Files
felhom-controller/controller/internal/monitor/r516_resource_msgs_test.go
T
admin fcd9f09927 health check: the last six producers carry their dashboard sentence (R-79 option A, D3)
Docker unreachable, protected container down, and the four storage-path entries now carry a
bundle key beside their wire text; the wire text is unchanged byte for byte. Hungarian values of
the four storage keys equal the frozen formats (i18n_go_parity). Red-proven: a zero MsgRef on the
unavailable-path warning fails TestR79_RemainingProducersCarryTheirDashboardSentence and
TestR79_HealthBannersFollowTheHousehold (producer-driven, English household sees Hungarian).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
2026-10-08 14:49:27 +02:00

146 lines
6.4 KiB
Go

package monitor
import (
"fmt"
"os"
"path/filepath"
"strings"
"testing"
"gitea.dooplex.hu/admin/felhom-controller/internal/config"
"gitea.dooplex.hu/admin/felhom-controller/internal/settings"
"gitea.dooplex.hu/admin/felhom-controller/internal/system"
)
// R-516 item 10 — the resource warnings („SSD disk usage high: 90%") reached the household's banner in
// English on every page. checkResources now attaches the dashboard's own sentence (a bundle key) beside
// each one, and the wire text is byte-for-byte what it was.
//
// COMPANION RED-PROOF: give the SSD warning a zero MsgRef (addWarning) — the "warn" case fails here
// and the banner test in internal/web shows the English wire text again.
func TestR516_ResourceWarningsCarryTheirDashboardSentence(t *testing.T) {
cfg := config.Default()
cases := []struct {
name string
info system.SystemInfo
wantIssue, wantKey []string
wantWire []string
}{
{"warn", system.SystemInfo{DiskPercent: 85, HDDConfigured: true, HDDPercent: 85, MemPercent: 90, CPUPercent: 95,
TemperatureCelsius: 80, TemperatureSource: "coretemp"}, nil,
[]string{"health.system_disk_high", "health.data_disk_high", "health.memory_high", "health.cpu_high", "health.temperature_high"},
[]string{"SSD disk usage high: 85%", "HDD disk usage high: 85%", "Memory usage high: 90%", "CPU usage high: 95%", "Temperature high: 80°C (coretemp)"}},
{"critical", system.SystemInfo{DiskPercent: 95, HDDConfigured: true, HDDPercent: 96},
[]string{"health.system_disk_critical", "health.data_disk_critical"}, nil,
[]string{"SSD disk usage critical: 95%", "HDD disk usage critical: 96%"}},
}
for _, c := range cases {
r := &HealthReport{}
checkResources(r, c.info, cfg, nil, false)
texts, keys := r.Warnings, []string{}
for i := range r.Warnings {
keys = append(keys, r.WarningMsgAt(i).Key)
}
if c.wantIssue != nil {
texts, keys = r.Issues, nil
for i := range r.Issues {
keys = append(keys, r.IssueMsgAt(i).Key)
}
}
want := c.wantKey
if c.wantIssue != nil {
want = c.wantIssue
}
if len(texts) != len(c.wantWire) {
t.Fatalf("%s: %d entries %q, want %d — the cases below would be compared against nothing", c.name, len(texts), texts, len(c.wantWire))
}
for i := range texts {
if texts[i] != c.wantWire[i] {
t.Errorf("%s: the wire text moved: %q, want %q", c.name, texts[i], c.wantWire[i])
}
if keys[i] != want[i] {
t.Errorf("%s: %q carries dashboard key %q, want %q", c.name, texts[i], keys[i], want[i])
}
}
if len(r.Warnings) != len(r.WarningKinds) || len(r.Warnings) != len(r.WarningMsgs) || len(r.Issues) != len(r.IssueMsgs) {
t.Errorf("%s: the parallel slices drifted apart", c.name)
}
}
}
// R-79 option A (operator ruling D3, 2026-10-08) — the six producers R-516 item 10 left verbatim:
// Docker unreachable, a protected container down, and the four storage-path entries. Each now
// carries its dashboard sentence; the wire text is byte for byte what it was (it is ON THE WIRE,
// report health.*; the frozen storage formats are also pinned by TestStorageWarningFormatsAreFrozen).
//
// COMPANION RED-PROOF: give any one of them a zero MsgRef (e.g. the unavailable-path warning back to
// addWarning) — its case fails here, and TestR79_StorageBannerFollowsTheHousehold in internal/web
// shows the Hungarian wire text to an English household again.
func TestR79_RemainingProducersCarryTheirDashboardSentence(t *testing.T) {
type entry struct{ wire, key string }
check := func(name string, texts []string, msgs func(int) MsgRef, want []entry) {
t.Helper()
if len(texts) != len(want) {
t.Fatalf("%s: %d entries %q, want %d — the cases below would be compared against nothing", name, len(texts), texts, len(want))
}
for i := range texts {
if texts[i] != want[i].wire {
t.Errorf("%s: the wire text moved: %q, want %q", name, texts[i], want[i].wire)
}
if k := msgs(i).Key; k != want[i].key {
t.Errorf("%s: %q carries dashboard key %q, want %q", name, texts[i], k, want[i].key)
}
}
}
// Docker and protected containers: driven through the same adders RunHealthCheck calls.
r := &HealthReport{}
addDockerIssue(r, fmt.Errorf("docker not reachable: exit status 1"))
addProtectedIssues(r, []string{"cloudflared", "traefik"})
check("docker+protected", r.Issues, r.IssueMsgAt, []entry{
{"Docker: docker not reachable: exit status 1", "health.docker_unreachable"},
{"Protected container not running: cloudflared", "health.protected_container_down"},
{"Protected container not running: traefik", "health.protected_container_down"},
})
if a := r.IssueMsgAt(1).Args; len(a) != 1 || a[0] != "cloudflared" {
t.Errorf("protected-container sentence args = %v, want [cloudflared]", a)
}
// Storage paths: a missing path, a path that is not a mount point at 92 %, one at 97 %.
oldMount, oldUsage := isMountPointFn, diskUsageFn
defer func() { isMountPointFn, diskUsageFn = oldMount, oldUsage }()
isMountPointFn = func(p string) bool { return !strings.HasSuffix(p, "/notsep") }
diskUsageFn = func(p string) *system.DiskUsageInfo {
switch {
case strings.HasSuffix(p, "/notsep"):
return &system.DiskUsageInfo{UsedPercent: 92}
case strings.HasSuffix(p, "/full"):
return &system.DiskUsageInfo{UsedPercent: 97}
}
return nil
}
dir := t.TempDir()
for _, sub := range []string{"notsep", "full"} {
if err := os.Mkdir(filepath.Join(dir, sub), 0o755); err != nil {
t.Fatal(err)
}
}
missing := filepath.Join(dir, "missing")
r = &HealthReport{}
addStorage(r, []settings.StoragePath{{Path: missing}, {Path: dir + "/notsep"}, {Path: dir + "/full"}})
check("storage warnings", r.Warnings, r.WarningMsgAt, []entry{
{"Adattároló nem elérhető: " + missing, "health.storage_unavailable"},
{"Az adattároló (" + dir + "/notsep) nem külön meghajtón van — az adatok a rendszermeghajtóra íródnak", "health.storage_not_separate"},
{"Adattároló használat magas: " + dir + "/notsep (92%)", "health.storage_usage_high"},
})
check("storage issues", r.Issues, r.IssueMsgAt, []entry{
{"Adattároló majdnem megtelt: " + dir + "/full (97%)", "health.storage_almost_full"},
})
if len(r.Warnings) != len(r.WarningKinds) || len(r.Warnings) != len(r.WarningMsgs) || len(r.Issues) != len(r.IssueMsgs) {
t.Errorf("storage: the parallel slices drifted apart")
}
if got := r.WarningKindAt(1); got != WarnKindStorageNotSeparate {
t.Errorf("not-separate warning lost its kind: %q", got)
}
}