0054d4bd69
gates / gates (push) Successful in 27s
R-634: a whole-box backup no longer stops/restarts a DEPLOYING app (the measured cause of containers running under 'not deployed'); StopStack and StartStack refuse a deploying stack for every caller. R-625: held badge 'Stopped - restore needed', no Update button. R-636: kernel oom_kill counter; 20+ in 30 min -> one app_oom_storm. R-647: held error per reader, copy_holds key, two log wordings. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
117 lines
4.3 KiB
Go
117 lines
4.3 KiB
Go
package notify
|
|
|
|
import (
|
|
"bytes"
|
|
"log"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"gitea.dooplex.hu/admin/felhom-controller/internal/settings"
|
|
)
|
|
|
|
// v0.264.0 — the two update events on the wire, and R-620.
|
|
|
|
type pushRec struct {
|
|
typ, sev, msg, cust string
|
|
details interface{}
|
|
}
|
|
|
|
func recNotifier(t *testing.T, lang string) (*Notifier, *[]pushRec) {
|
|
t.Helper()
|
|
var buf bytes.Buffer
|
|
sett, err := settings.Load(filepath.Join(t.TempDir(), "settings.json"), log.New(&buf, "", 0))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if lang != "hu" {
|
|
if err := sett.SetLanguage(lang); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
n := &Notifier{settings: sett, logger: log.New(&buf, "", 0)}
|
|
var got []pushRec
|
|
n.pushFn = func(typ, sev, msg, cust string, d interface{}) { got = append(got, pushRec{typ, sev, msg, cust, d}) }
|
|
return n, &got
|
|
}
|
|
|
|
func TestUpdateEvents_UndoneWireText(t *testing.T) {
|
|
at := time.Date(2026, 9, 23, 10, 3, 0, 0, time.UTC) // 12:03 Budapest
|
|
for _, tc := range []struct{ lang, cust string }{
|
|
{"hu", ""},
|
|
{"en", "The update of docmost at 2026-09-23 12:03 did not work. The box put back the previous version and its data automatically — nothing was lost, and there is nothing you need to do."},
|
|
} {
|
|
n, got := recNotifier(t, tc.lang)
|
|
n.NotifyAppUpdateUndone("docmost", map[string]string{"docmost": "a:1"}, map[string]string{"docmost": "a:2"}, at)
|
|
if len(*got) != 1 {
|
|
t.Fatalf("%s: %d events", tc.lang, len(*got))
|
|
}
|
|
r := (*got)[0]
|
|
if r.typ != "app_update_undone" || r.sev != "warning" {
|
|
t.Errorf("%s: type/severity = %s/%s", tc.lang, r.typ, r.sev)
|
|
}
|
|
if r.msg != "A(z) docmost frissítése 2026-09-23 12:03-kor nem sikerült. A doboz automatikusan visszaállította az előző változatot és az adatokat — semmi nem veszett el, nincs teendőd." {
|
|
t.Errorf("%s: the wire message must be the Hungarian, got %q", tc.lang, r.msg)
|
|
}
|
|
if r.cust != tc.cust {
|
|
t.Errorf("%s: household copy = %q", tc.lang, r.cust)
|
|
}
|
|
if d, ok := r.details.(AppUpdateDetails); !ok || d.StackName != "docmost" || d.App != "docmost" || d.From["docmost"] != "a:1" {
|
|
t.Errorf("%s: details must carry stack_name (the hub's per-app cooldown key) and the step, got %+v", tc.lang, r.details)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestUpdateEvents_HeldCarriesTheHoldSentenceInBothLanguages(t *testing.T) {
|
|
sentence := func(lang string) string { return "HOLD-" + lang }
|
|
n, got := recNotifier(t, "en")
|
|
n.NotifyAppUpdateHeld(AppUpdateDetails{App: "romm", StackName: "romm", CopyTier: 1}, sentence)
|
|
r := (*got)[0]
|
|
if r.typ != "app_update_held" || r.sev != "error" || r.msg != "HOLD-hu" || r.cust != "HOLD-en" {
|
|
t.Errorf("got %+v", r)
|
|
}
|
|
n2, got2 := recNotifier(t, "hu")
|
|
n2.NotifyAppUpdateHeld(AppUpdateDetails{App: "romm", StackName: "romm"}, sentence)
|
|
if r := (*got2)[0]; r.cust != "" {
|
|
t.Errorf("a Hungarian household sends no second copy (R-558), got %q", r.cust)
|
|
}
|
|
}
|
|
|
|
// R-620. COMPANION RED-PROOF (REPORT.md): delete the n.dropped call in pushEventBoth — zero WARN
|
|
// lines, and this test fails.
|
|
func TestR620_DisabledNotifierSaysWhatItDrops(t *testing.T) {
|
|
var buf bytes.Buffer
|
|
n := New("", "", "c1", nil, log.New(&buf, "", 0), false) // no hub → disabled
|
|
buf.Reset()
|
|
n.PushEvent("app_update_undone", "warning", "x", nil)
|
|
n.PushEvent("app_update_undone", "warning", "y", nil)
|
|
n.PushEvent("backup_failed", "error", "z", nil)
|
|
warns := 0
|
|
for _, l := range strings.Split(buf.String(), "\n") {
|
|
if strings.Contains(l, "[WARN]") && strings.Contains(l, "DROPPED event") {
|
|
warns++
|
|
}
|
|
}
|
|
if warns != 2 {
|
|
t.Fatalf("want exactly TWO WARN lines (one per event type), got %d:\n%s", warns, buf.String())
|
|
}
|
|
for _, want := range []string{"DROPPED event app_update_undone (severity warning)", "DROPPED event backup_failed (severity error)"} {
|
|
if !strings.Contains(buf.String(), want) {
|
|
t.Errorf("missing %q in:\n%s", want, buf.String())
|
|
}
|
|
}
|
|
}
|
|
|
|
// R-647 (3) — the disabled path names the SEVERITY a health change would be sent at, never the health
|
|
// status. COMPANION RED-PROOF (REPORT.md): pass `status` again — the line reads "severity warn".
|
|
func TestR647_DisabledHealthChangeNamesTheSeverity(t *testing.T) {
|
|
var buf bytes.Buffer
|
|
n := New("", "", "c1", nil, log.New(&buf, "", 0), false)
|
|
buf.Reset()
|
|
n.NotifyHealthChange("warn", nil, nil)
|
|
if !strings.Contains(buf.String(), "DROPPED event health_change (severity warning)") {
|
|
t.Fatalf("want the severity 'warning' named, got:\n%s", buf.String())
|
|
}
|
|
}
|