package backup import ( "io" "log" "strings" "testing" "time" "gitea.dooplex.hu/admin/felhom-controller/internal/settings" ) // Decision 28: the stop is a hold (same store — no start path revives it), Start lifts ONLY that kind, // and a second stop within 24 h says support is informed. func TestD28_HoldTripsAndLift(t *testing.T) { sett := slice4Settings(t) m := &Manager{logger: log.New(io.Discard, "", 0), settings: sett} t0 := time.Date(2026, 9, 24, 22, 0, 0, 0, time.UTC) trip, err := m.HoldUnhealthy("gokapi", "crash_loop", t0) if err != nil || trip != 1 { t.Fatalf("trip=%d err=%v", trip, err) } held, why := m.RestoreHoldForLang("gokapi", "hu") if !held || !strings.Contains(why, "Indítás gombbal") || strings.Contains(why, "gyfélszolgálat") { t.Fatalf("first stop: held=%v %q", held, why) } if !m.UpdateHeldStacks()["gokapi"] { t.Fatal("an unhealthy stop is a product stop — it must be in the app-down suppression set") } if m.HoldKind("gokapi") != settings.HoldReasonUnhealthyStop || !m.LiftUnhealthyStop("gokapi") { t.Fatal("Start could not lift the unhealthy stop") } if held, _ := m.RestoreHoldFor("gokapi"); held { t.Fatal("still held after Start") } trip, _ = m.HoldUnhealthy("gokapi", "oom_storm", t0.Add(3*time.Hour)) _, why = m.RestoreHoldForLang("gokapi", "en") if trip != 2 || !strings.Contains(why, "support has been told") { t.Fatalf("second stop within 24 h: trip=%d %q", trip, why) } m.LiftUnhealthyStop("gokapi") if trip, _ = m.HoldUnhealthy("gokapi", "crash_loop", t0.Add(30*time.Hour)); trip != 1 { t.Fatalf("a stop 27 h after the last is a first stop again, got trip %d", trip) } // Start never lifts another kind. _ = sett.SetRestoreHold(settings.RestoreHold{Stack: "nextcloud", At: t0.Format(time.RFC3339), Reason: settings.HoldReasonUpdateFailed}) if m.LiftUnhealthyStop("nextcloud") { t.Fatal("Start lifted an UPDATE hold") } }