ff1db11db4
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
91 lines
2.8 KiB
Go
91 lines
2.8 KiB
Go
package osupdates
|
|
|
|
import (
|
|
"testing"
|
|
"time"
|
|
|
|
"gitea.dooplex.hu/admin/felhom-hub/internal/store"
|
|
)
|
|
|
|
func bundleReport(t *testing.T, f *fix, host, version, sha string) {
|
|
t.Helper()
|
|
body := `{"system":{"pve_version":"pve-manager/9.2.2/x","config_bundle":{"version":"` + version + `","bundle_sha256":"` + sha + `"}}}`
|
|
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(body), store.HostReportDenorm{AgentVersion: "0.143.0"}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func vouch(t *testing.T, f *fix, sha string) {
|
|
t.Helper()
|
|
if err := f.s.Store.SetArtifactManifest(store.ArtifactManifest{AgentVersion: "0.143.0", AgentSHA256: "x", BundleSHA256: sha}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func count(sent []string, typ string) int {
|
|
n := 0
|
|
for _, s := range sent {
|
|
if s == typ {
|
|
n++
|
|
}
|
|
}
|
|
return n
|
|
}
|
|
|
|
// R-840: a box behind the vouched bundle is told to the operator after 7 days — not before, once, and the clock
|
|
// restarts when the box catches up. "none" (no bundle ever) counts; "unknown" never does.
|
|
func TestBundleAlarm_AfterSevenDaysBehind(t *testing.T) {
|
|
f := newFix(t)
|
|
vouch(t, f, "new")
|
|
bundleReport(t, f, "cust1", "none", "")
|
|
sent, _ := f.s.Alarms()
|
|
if count(sent, EventBundleBehind) != 0 {
|
|
t.Fatal("alarm on the first sight")
|
|
}
|
|
f.now = f.now.Add(6 * 24 * time.Hour)
|
|
if sent, _ = f.s.Alarms(); count(sent, EventBundleBehind) != 0 {
|
|
t.Fatal("alarm before 7 days")
|
|
}
|
|
f.now = f.now.Add(25 * time.Hour)
|
|
if sent, _ = f.s.Alarms(); count(sent, EventBundleBehind) != 1 {
|
|
t.Fatalf("no alarm after 7 days: %v", sent)
|
|
}
|
|
if sent, _ = f.s.Alarms(); count(sent, EventBundleBehind) != 0 {
|
|
t.Fatal("the alarm must not repeat at once")
|
|
}
|
|
bundleReport(t, f, "cust1", "0.143.0", "new")
|
|
f.s.Alarms()
|
|
if !f.s.Store.BundleBehindSince("cust1").IsZero() {
|
|
t.Fatal("caught up: the clock must clear")
|
|
}
|
|
}
|
|
|
|
func TestBundleAlarm_UnknownAndNoVouchedBundleSayNothing(t *testing.T) {
|
|
f := newFix(t)
|
|
vouch(t, f, "new")
|
|
bundleReport(t, f, "cust1", "unknown", "")
|
|
f.s.Alarms()
|
|
f.now = f.now.Add(30 * 24 * time.Hour)
|
|
if sent, _ := f.s.Alarms(); count(sent, EventBundleBehind) != 0 || !f.s.Store.BundleBehindSince("cust1").IsZero() {
|
|
t.Fatalf("unknown is not a fact: %v", sent)
|
|
}
|
|
// control: the same box saying "none" IS behind (proves the report above was read at all)
|
|
bundleReport(t, f, "cust1", "none", "")
|
|
f.s.Alarms()
|
|
if f.s.Store.BundleBehindSince("cust1").IsZero() {
|
|
t.Fatal("control: a box saying none must start the clock")
|
|
}
|
|
g := newFix(t)
|
|
vouch(t, g, "")
|
|
bundleReport(t, g, "cust1", "0.143.0", "some-bundle")
|
|
g.s.Alarms()
|
|
g.now = g.now.Add(30 * 24 * time.Hour)
|
|
if sent, _ := g.s.Alarms(); count(sent, EventBundleBehind) != 0 {
|
|
t.Fatalf("nothing vouched, nothing behind: %v", sent)
|
|
}
|
|
}
|