Files
felhom.eu/hub/internal/web/system_trim_test.go
T
admin a411cde7c4 R-444: hub half — the System page shows each box's last disk trim
sysfacts reads the agent's top-level guest_disk_trim stanza (schedule + per-guest
last attempt: vmid, last_attempt_at, ok, bytes_trimmed, mounts, duration_seconds,
last_ok_at, error) into a field-by-field mirror. The System page's new 'Last disk
trim' column shows the last successful trim and the GiB it freed; amber when the
newest attempt failed (error shown) or last_ok_at is older than 14 days (judged on
the success time, never the attempt time); '—' when the agent sends no stanza.
wire_contract_gate: SUBTREE_MIRRORS checks guest_disk_trim field by field BOTH
ways against sysfacts.DiskTrim; decoys (ok renamed, last_ok_at dropped) in
test_gate_decoys.py. Decision 139.

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

82 lines
4.3 KiB
Go

package web
import (
"fmt"
"strings"
"testing"
"time"
"gitea.dooplex.hu/admin/felhom-hub/internal/store"
"gitea.dooplex.hu/admin/felhom-hub/internal/sysfacts"
)
// R-444 (`09` §3 decision 139): the System page's "Last disk trim" cell — one case per branch. Staleness is judged on
// last_ok_at, never on the attempt time ("presence is not success").
func TestTrimCell(t *testing.T) {
now := time.Date(2026, 10, 6, 12, 0, 0, 0, time.UTC)
at := func(d time.Duration) string { return now.Add(-d).Format(time.RFC3339) }
day := 24 * time.Hour
dt := func(gs ...sysfacts.GuestTrim) *sysfacts.DiskTrim {
return &sysfacts.DiskTrim{Schedule: "weekly", Guests: gs}
}
cases := []struct {
name string
dt *sysfacts.DiskTrim
text string
class string
titleHas string
}{
{"agent sends nothing", nil, "—", "", "reports no disk trim"},
{"stanza, no guest yet", dt(), "none yet", "", "has not trimmed a guest yet"},
{"recent ok", dt(sysfacts.GuestTrim{VMID: 9201, LastAttemptAt: at(2 * day), LastOKAt: at(2 * day), OK: true, BytesTrimmed: 32427003904}),
"2 days ago · 30.2 GiB", "", "schedule: weekly"},
{"ok but older than 14 days", dt(sysfacts.GuestTrim{VMID: 9201, LastAttemptAt: at(15 * day), LastOKAt: at(15 * day), OK: true, BytesTrimmed: 1 << 30}),
"15 days ago · 1.0 GiB", "warn", "older than 14 days"},
{"exactly 14 days is not stale", dt(sysfacts.GuestTrim{VMID: 9201, LastAttemptAt: at(14 * day), LastOKAt: at(14 * day), OK: true}),
"14 days ago · 0.0 GiB", "", ""},
{"newest failed, recent success before it", dt(sysfacts.GuestTrim{VMID: 9201, LastAttemptAt: at(1 * day), LastOKAt: at(8 * day), OK: false, Error: "pct fstrim: exit 255"}),
"FAILED 24 h ago: pct fstrim: exit 255 (last ok 8 days ago)", "warn", "the newest trim failed"},
{"failed and never ok", dt(sysfacts.GuestTrim{VMID: 9201, LastAttemptAt: at(1 * day), OK: false}),
"FAILED 24 h ago: no error text (never ok)", "warn", "the newest trim failed"},
{"a recent ATTEMPT does not hide a 20-day-old success", dt(sysfacts.GuestTrim{VMID: 9201, LastAttemptAt: at(1 * day), LastOKAt: at(20 * day), OK: false, Error: "busy"}),
"FAILED 24 h ago: busy (last ok 20 days ago)", "warn", "older than 14 days"},
{"two guests, one amber", dt(
sysfacts.GuestTrim{VMID: 9201, LastAttemptAt: at(2 * day), LastOKAt: at(2 * day), OK: true, BytesTrimmed: 2 << 30},
sysfacts.GuestTrim{VMID: 9202, LastAttemptAt: at(1 * day), OK: false, Error: "x"}),
"9201: 2 days ago · 2.0 GiB / 9202: FAILED 24 h ago: x (never ok)", "warn", "guest 9202"},
{"unreadable time", dt(sysfacts.GuestTrim{VMID: 9201, LastAttemptAt: "yesterday", OK: true}), "unknown", "warn", "unreadable last_attempt_at"},
}
for _, c := range cases {
got := trimCell(c.dt, now)
if got.Text != c.text || got.Class != c.class || !strings.Contains(got.Title, c.titleHas) {
t.Errorf("%s: got %+v, want text %q class %q title containing %q", c.name, got, c.text, c.class, c.titleHas)
}
}
}
// The cell reaches the rendered page, read from the stored host report (the endpoint the operator's browser calls).
func TestSystemPage_LastDiskTrim(t *testing.T) {
s, st, _ := systemServer(t)
when := time.Now().Add(-20 * 24 * time.Hour).UTC().Format(time.RFC3339)
body := fmt.Sprintf(`{"host":{"cpu_percent":1},"guest_disk_trim":{"schedule":"weekly","guests":[{"vmid":9201,"last_attempt_at":%q,"ok":true,"bytes_trimmed":32427003904,"mounts":2,"duration_seconds":24.4,"last_ok_at":%q}]}}`, when, when)
if err := st.UpsertHost(&store.Host{HostID: "trim-1", CustomerID: "c-trim", APIKey: "k-trim"}); err != nil {
t.Fatal(err)
}
if err := st.SaveHostReport("trim-1", "c-trim", []byte(body), store.HostReportDenorm{AgentVersion: "0.150.0", CloudflaredStatus: "running"}); err != nil {
t.Fatal(err)
}
b := getSystem(t, s)
if !strings.Contains(b, ">Last disk trim</th>") {
t.Error("the System page lacks the Last disk trim column")
}
if !strings.Contains(b, "20 days ago · 30.2 GiB") {
t.Errorf("the trim-1 box's trim is not on the page")
}
if !strings.Contains(b, `class="c-warn" title="guest 9201: the last successful trim is older than 14 days`) {
t.Error("a 20-day-old trim must be amber, with its reason")
}
if !strings.Contains(b, `title="the agent reports no disk trim`) {
t.Error("a box whose agent sends no trim must read — with its reason")
}
}