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) } }