a411cde7c4
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
82 lines
4.3 KiB
Go
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")
|
|
}
|
|
}
|