655f5f66f8
gates / gates (push) Successful in 6m0s
- S4 / R-242: GoldenVouchChecker - a published golden unvouched for 2 h raises golden_not_vouched (operator only) - S6 / R-244: the delete cascade de-references app_log_issues; one-time sweep flag -sweep-app-log-residue=count|apply - S10 / R-266: read_failed storage entry shows "read failed", not 0 % - S29 / R-250: host-key scan IPv4 first, ~2 min ladder, "safe to press Save again" error - S30 / R-246: stale_at retired (reader, setter, badge) - S32 / R-521: storage_reconnected ends the storage_disconnected cooldown on both channels - S33 / R-402: host page "Last full check (off-site)" line; two wire-gate allowlist entries removed Each with a test seen failing first. Not built or deployed yet (operator-attended deploy). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
62 lines
2.0 KiB
Go
62 lines
2.0 KiB
Go
package monitor
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"io"
|
|
"log"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
// R-242 second half (S4, decision 199). The CONSEQUENCE: a published-but-unvouched golden raises one
|
|
// operator warning after the grace; a vouch clears it; Gitea down says nothing.
|
|
// RED-PROOF: make Check return before onEvent (the pre-v0.147.0 hub had no such checker) → "want 1 alarm".
|
|
func TestGoldenVouchChecker(t *testing.T) {
|
|
now := time.Date(2026, 10, 12, 12, 0, 0, 0, time.UTC)
|
|
published := []string{"0.308.0", "0.301.0"}
|
|
var listErr error
|
|
vouched := "0.301.0"
|
|
var events []string
|
|
gc := NewGoldenVouchChecker(
|
|
func(context.Context) ([]string, error) { return published, listErr },
|
|
func() string { return vouched },
|
|
func(cust, typ, sev, msg, det, src string) { events = append(events, typ+"/"+sev) },
|
|
log.New(io.Discard, "", 0))
|
|
gc.now = func() time.Time { return now }
|
|
step := func(d time.Duration) { now = now.Add(d); gc.Check() }
|
|
|
|
step(0) // first sight of the gap — no alarm yet
|
|
step(time.Hour)
|
|
if len(events) != 0 {
|
|
t.Fatalf("alarm inside the grace: %v", events)
|
|
}
|
|
step(61 * time.Minute) // past 2 h
|
|
if len(events) != 1 || events[0] != "golden_not_vouched/warning" {
|
|
t.Fatalf("want 1 alarm golden_not_vouched/warning, got %v", events)
|
|
}
|
|
step(time.Hour)
|
|
if len(events) != 1 {
|
|
t.Fatalf("the same version alarmed twice: %v", events)
|
|
}
|
|
vouched = "0.308.0" // the operator vouches
|
|
step(time.Hour)
|
|
published = []string{"0.309.0", "0.308.0"}
|
|
listErr = errors.New("gitea down")
|
|
step(5 * time.Hour)
|
|
if len(events) != 1 {
|
|
t.Fatalf("an unreachable Gitea must not alarm: %v", events)
|
|
}
|
|
// Throttle: two calls within 15 min list once.
|
|
calls := 0
|
|
gc2 := NewGoldenVouchChecker(func(context.Context) ([]string, error) { calls++; return published, nil },
|
|
func() string { return "0.309.0" }, func(_, _, _, _, _, _ string) {}, log.New(io.Discard, "", 0))
|
|
t0 := now
|
|
gc2.now = func() time.Time { return t0 }
|
|
gc2.Check()
|
|
gc2.Check()
|
|
if calls != 1 {
|
|
t.Fatalf("throttle: %d listings in one window, want 1", calls)
|
|
}
|
|
}
|