3d7a2761fc
gates / gates (push) Successful in 29s
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
102 lines
3.4 KiB
Go
102 lines
3.4 KiB
Go
package osupdates
|
|
|
|
import (
|
|
"testing"
|
|
"time"
|
|
|
|
"gitea.dooplex.hu/admin/felhom-hub/internal/store"
|
|
)
|
|
|
|
// R-530 (hub v0.135.0): a box running an agent OLDER than the vouched one is told to the operator after 7 days —
|
|
// not before, once, and the clock clears when the box catches up. An unreadable version is never a fact.
|
|
// RED-PROOF (audits/hub-safety-2026-10-05/partD/red-proof.txt): delete alarm block 6 in Alarms() →
|
|
// TestAgentAlarm_AfterSevenDaysBehind fails ("no alarm after 7 days").
|
|
|
|
func agentReport(t *testing.T, f *fix, host, agent string) {
|
|
t.Helper()
|
|
h, err := f.s.Store.GetHost(host)
|
|
if err != nil || h == nil {
|
|
t.Fatalf("no host %s", host)
|
|
}
|
|
if err := f.s.Store.SaveHostReport(host, h.CustomerID, []byte(`{"host":{"cpu_percent":1}}`), store.HostReportDenorm{AgentVersion: agent}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func vouchAgent(t *testing.T, f *fix, v string) {
|
|
t.Helper()
|
|
if err := f.s.Store.SetArtifactManifest(store.ArtifactManifest{AgentVersion: v, AgentSHA256: "x"}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func TestAgentAlarm_AfterSevenDaysBehind(t *testing.T) {
|
|
f := newFix(t)
|
|
vouchAgent(t, f, "0.145.0")
|
|
agentReport(t, f, "cust1", "0.142.0")
|
|
agentReport(t, f, "hp", "0.145.0") // current: never alarms
|
|
sent, _ := f.s.Alarms()
|
|
if count(sent, EventAgentBehind) != 0 {
|
|
t.Fatal("alarm on the first sight")
|
|
}
|
|
if f.s.Store.AgentBehindSince("cust1").IsZero() || !f.s.Store.AgentBehindSince("hp").IsZero() {
|
|
t.Fatal("the clock must start for the behind box only")
|
|
}
|
|
f.now = f.now.Add(6 * 24 * time.Hour)
|
|
if sent, _ = f.s.Alarms(); count(sent, EventAgentBehind) != 0 {
|
|
t.Fatal("alarm before 7 days")
|
|
}
|
|
f.now = f.now.Add(25 * time.Hour)
|
|
if sent, _ = f.s.Alarms(); count(sent, EventAgentBehind) != 1 {
|
|
t.Fatalf("no alarm after 7 days: %v", sent)
|
|
}
|
|
if sent, _ = f.s.Alarms(); count(sent, EventAgentBehind) != 0 {
|
|
t.Fatal("the alarm must not repeat at once")
|
|
}
|
|
agentReport(t, f, "cust1", "0.145.0")
|
|
f.s.Alarms()
|
|
if !f.s.Store.AgentBehindSince("cust1").IsZero() {
|
|
t.Fatal("caught up: the clock must clear")
|
|
}
|
|
}
|
|
|
|
func TestAgentAlarm_UnknownAndNothingVouchedSayNothing(t *testing.T) {
|
|
f := newFix(t)
|
|
vouchAgent(t, f, "0.145.0")
|
|
agentReport(t, f, "cust1", "") // an agent too old to say, or a report without the field
|
|
f.s.Alarms()
|
|
f.now = f.now.Add(30 * 24 * time.Hour)
|
|
if sent, _ := f.s.Alarms(); count(sent, EventAgentBehind) != 0 || !f.s.Store.AgentBehindSince("cust1").IsZero() {
|
|
t.Fatalf("an unreadable version is not a fact: %v", sent)
|
|
}
|
|
// control: the same box naming an old version IS behind (proves the report was read at all)
|
|
agentReport(t, f, "cust1", "0.130.0")
|
|
f.s.Alarms()
|
|
if f.s.Store.AgentBehindSince("cust1").IsZero() {
|
|
t.Fatal("control: a box on 0.130.0 must start the clock")
|
|
}
|
|
g := newFix(t)
|
|
agentReport(t, g, "cust1", "0.130.0")
|
|
g.s.Alarms()
|
|
g.now = g.now.Add(30 * 24 * time.Hour)
|
|
if sent, _ := g.s.Alarms(); count(sent, EventAgentBehind) != 0 {
|
|
t.Fatalf("nothing vouched, nothing behind: %v", sent)
|
|
}
|
|
}
|
|
|
|
func TestReleasesBehind(t *testing.T) {
|
|
for _, c := range []struct{ a, b, want string }{
|
|
{"0.142.0", "0.145.0", "3 minor releases behind"},
|
|
{"0.144.0", "0.145.0", "1 minor release behind"},
|
|
{"0.145.0", "0.145.2", "2 patch releases behind"},
|
|
{"0.145.0", "1.0.0", "a major release behind"},
|
|
{"0.145.0", "0.145.0", ""},
|
|
{"0.146.0", "0.145.0", ""},
|
|
{"", "0.145.0", ""},
|
|
} {
|
|
if got := ReleasesBehind(c.a, c.b); got != c.want {
|
|
t.Errorf("ReleasesBehind(%q, %q) = %q, want %q", c.a, c.b, got, c.want)
|
|
}
|
|
}
|
|
}
|