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()) } }