package backup import ( "errors" "strings" "testing" "gitea.dooplex.hu/admin/felhom-controller/internal/settings" ) // R-1348: the restore_mixed hold written at START-UP after an interrupted put-back fails must reach the operator as the // R-1340 mail. Recover runs long before the notifier exists (main.go), so the guard keeps the name and hands it over when // SetHeldNotify is wired; a failure after that (a drive that returned at run time) goes out at once. // // RED-PROOF (2026-11-02): seen failing before the seam existed (build: SetHeldNotify); with the call dropped from // holdPutBackFailed these fail on „no mail for a held app". func TestR1348_AFailedPutBackAtStartupMailsOnceTheNotifierIsWired(t *testing.T) { r := newR1251Rig(t) r.putErr = errors.New("compose down: timeout") g := r.guard() if res := g.Recover(); res == nil || len(res.Held) != 1 { t.Fatalf("recovery must hold the app: %+v", res) } var mailed []string g.SetHeldNotify(func(stack string) { mailed = append(mailed, stack) }) if len(mailed) != 1 || mailed[0] != "immich" { t.Fatalf("no mail for a held app: the hold from Recover must be handed over when the notifier is wired, got %v", mailed) } h, ok := r.sett.GetRestoreHold("immich") if !ok || h.Reason != settings.HoldReasonRestoreMixed || h.MixedCause != settings.MixedCauseInterruptedPutBack || !strings.Contains(h.FailedStep, "compose down: timeout") { t.Fatalf("the mail reads this record, and it is wrong: %+v", h) } g.SetHeldNotify(func(stack string) { mailed = append(mailed, "again:"+stack) }) if len(mailed) != 1 { t.Fatalf("a held app is mailed once, not at every wiring: %v", mailed) } } // A clean put-back, or one still waiting for its drive, is no failure: no mail. func TestR1348_NoMailWithoutAFailedPutBack(t *testing.T) { r := newR1251Rig(t) g := r.guard() g.Recover() var mailed []string g.SetHeldNotify(func(stack string) { mailed = append(mailed, stack) }) if len(mailed) != 0 { t.Fatalf("a put-back that worked mailed: %v", mailed) } w := newR1251Rig(t) w.unbind(t) gw := w.guard() gw.Recover() gw.SetHeldNotify(func(stack string) { mailed = append(mailed, stack) }) if len(mailed) != 0 { t.Fatalf("a put-back that waits for its drive has its own hold and reminder, not this mail: %v", mailed) } } // After the wiring a failed put-back (the drive returned at run time: ResumePutBack) mails at once. func TestR1348_AFailedResumeMailsAtOnce(t *testing.T) { r := newR1251Rig(t) r.unbind(t) g := r.guard() g.Recover() // waiting for its drive var mailed []string g.SetHeldNotify(func(stack string) { mailed = append(mailed, stack) }) r.bind(t) r.putErr = errors.New("tar xf failed") g.ResumePutBack("immich") if len(mailed) != 1 || mailed[0] != "immich" { t.Fatalf("a put-back that failed on resume must mail: %v", mailed) } }