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
142 lines
5.9 KiB
Go
142 lines
5.9 KiB
Go
package web
|
|
|
|
import (
|
|
"bytes"
|
|
"log"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"net/url"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"gitea.dooplex.hu/admin/felhom-hub/internal/store"
|
|
)
|
|
|
|
// R-604 (hub v0.135.0): a global floor raise says which boxes it did NOT move — one log line per customer whose
|
|
// own floor is LOWER, and ONE operator mail naming them. An override at or above the global is not named; a raise
|
|
// that moves everyone sends nothing.
|
|
// RED-PROOF (audits/hub-safety-2026-10-05/partD/red-proof.txt): remove the s.reportFloorHeldBack(v) call in
|
|
// handleSetGlobalFloor → TestR604_GlobalRaiseNamesHeldBackBoxes fails (no log line, no mail).
|
|
|
|
type emitted struct{ customer, typ, sev, msg string }
|
|
|
|
func r604Server(t *testing.T) (*Server, *store.Store, *bytes.Buffer, *[]emitted) {
|
|
t.Helper()
|
|
s, st := newTestServer(t)
|
|
var buf bytes.Buffer
|
|
s.logger = log.New(&buf, "", 0)
|
|
var got []emitted
|
|
s.SetEventEmitter(func(c, typ, sev, msg, _, _ string) { got = append(got, emitted{c, typ, sev, msg}) })
|
|
// vouch a golden ABOVE the floors used here, so a floor needs no declared MinAgent (R-472 is not under test)
|
|
if err := st.SetArtifactManifest(store.ArtifactManifest{GoldenVersion: "0.400.0", AgentVersion: "0.145.0"}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for _, c := range []struct{ id, floor string }{{"c-low", "0.240.0"}, {"c-high", "0.300.0"}, {"c-none", ""}} {
|
|
if err := st.SaveCustomerConfig(&store.CustomerConfig{CustomerID: c.id, CustomerName: c.id, RetrievalPassword: "x", APIKey: "k-" + c.id, ConfigJSON: "{}"}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if c.floor != "" {
|
|
if err := st.SetMinControllerVersion(c.id, c.floor); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
}
|
|
return s, st, &buf, &got
|
|
}
|
|
|
|
func setGlobal(t *testing.T, s *Server, v string) {
|
|
t.Helper()
|
|
r := httptest.NewRequest(http.MethodPost, "/configuration/global-floor", strings.NewReader(url.Values{"min_controller_version": {v}}.Encode()))
|
|
r.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
|
w := httptest.NewRecorder()
|
|
s.ServeHTTP(w, r)
|
|
if w.Code != http.StatusSeeOther || !strings.Contains(w.Header().Get("Location"), "flash=floor_set") {
|
|
t.Fatalf("global floor %s: %d %s", v, w.Code, w.Header().Get("Location"))
|
|
}
|
|
}
|
|
|
|
func TestR604_GlobalRaiseNamesHeldBackBoxes(t *testing.T) {
|
|
s, _, buf, got := r604Server(t)
|
|
setGlobal(t, s, "0.295.0")
|
|
logs := buf.String()
|
|
if !strings.Contains(logs, "does NOT move customer c-low: its own floor 0.240.0 wins") {
|
|
t.Fatalf("no log line for the held-back box:\n%s", logs)
|
|
}
|
|
if strings.Contains(logs, "customer c-high") || strings.Contains(logs, "customer c-none") {
|
|
t.Fatalf("a box that is NOT held back was named:\n%s", logs)
|
|
}
|
|
if len(*got) != 1 {
|
|
t.Fatalf("want exactly ONE operator mail, got %d: %+v", len(*got), *got)
|
|
}
|
|
e := (*got)[0]
|
|
if e.typ != "floor_raise_skipped" || e.sev != "warning" || e.customer != "" ||
|
|
!strings.Contains(e.msg, "c-low (own floor 0.240.0") || strings.Contains(e.msg, "c-high") {
|
|
t.Fatalf("the mail does not name exactly the held-back box: %+v", e)
|
|
}
|
|
if !strings.Contains(e.msg, "set 20") {
|
|
t.Fatalf("the mail must give the override's age (set time): %q", e.msg)
|
|
}
|
|
}
|
|
|
|
func TestR604_RaiseThatMovesEveryoneSendsNothing(t *testing.T) {
|
|
s, _, buf, got := r604Server(t)
|
|
setGlobal(t, s, "0.200.0") // below both overrides: nobody is held back by a LOWER own floor
|
|
if len(*got) != 0 || strings.Contains(buf.String(), "does NOT move") {
|
|
t.Fatalf("nothing held back, yet: mails %+v, log %q", *got, buf.String())
|
|
}
|
|
}
|
|
|
|
// The System page shows every per-customer floor with its age, and flags the one the global floor cannot move.
|
|
func TestR604_SystemPageListsFloorsWithAge(t *testing.T) {
|
|
s, st, _ := systemServer(t)
|
|
if err := st.SetArtifactManifest(store.ArtifactManifest{GoldenVersion: "0.400.0", AgentVersion: "0.145.0"}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := st.SetGlobalMinControllerVersion("0.295.0"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := st.SaveCustomerConfig(&store.CustomerConfig{CustomerID: "c-full", CustomerName: "Full", RetrievalPassword: "x", APIKey: "k", ConfigJSON: "{}"}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := st.SetMinControllerVersion("c-full", "0.243.0"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
b := getSystem(t, s)
|
|
for _, want := range []string{`id="version-floors"`, "Global controller floor: <strong>0.295.0", ">c-full<", "0.243.0",
|
|
time.Now().UTC().Format("2006-01-02"), "NO — its own floor is lower and wins"} {
|
|
if !strings.Contains(b, want) {
|
|
t.Errorf("System page lacks %q", want)
|
|
}
|
|
}
|
|
// the other branch of the gate: no override → the plain sentence
|
|
_ = st.SetMinControllerVersion("c-full", "")
|
|
if b := getSystem(t, s); !strings.Contains(b, "No per-customer floors") {
|
|
t.Error("no overrides: the page must say every box follows the global floor")
|
|
}
|
|
}
|
|
|
|
// R-530 on the page: the Agent cell shows box → vouched and is amber; red once the alarm's wait has passed.
|
|
func TestR530_SystemPageAgentCell(t *testing.T) {
|
|
s, st, svc := systemServer(t) // every box reports agent 0.142.0
|
|
if err := st.SetArtifactManifest(store.ArtifactManifest{AgentVersion: "0.145.0", AgentSHA256: "x"}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := svc.Alarms(); err != nil { // starts the behind-clock for every box
|
|
t.Fatal(err)
|
|
}
|
|
b := getSystem(t, s)
|
|
if !strings.Contains(b, "0.142.0 → 0.145.0") || !strings.Contains(b, `class="c-warn" title="3 minor releases behind`) {
|
|
t.Fatalf("the Agent cell does not show the box behind the vouched agent")
|
|
}
|
|
_ = st.SetAgentBehindSince("full-1", time.Now().Add(-8*24*time.Hour))
|
|
if b := getSystem(t, s); !strings.Contains(b, `class="c-bad" title="3 minor releases behind`) {
|
|
t.Fatal("past the alarm's wait the cell must be red")
|
|
}
|
|
// current branch
|
|
_ = st.SetArtifactManifest(store.ArtifactManifest{AgentVersion: "0.142.0", AgentSHA256: "x"})
|
|
if b := getSystem(t, s); strings.Contains(b, "→ 0.142.0") || !strings.Contains(b, `title="current (vouched 0.142.0)"`) {
|
|
t.Fatal("a current box must read current, not behind")
|
|
}
|
|
}
|