54 lines
2.3 KiB
Go
54 lines
2.3 KiB
Go
package backup
|
|
|
|
import (
|
|
"io"
|
|
"log"
|
|
"testing"
|
|
"time"
|
|
|
|
"gitea.dooplex.hu/admin/felhom-controller/internal/settings"
|
|
)
|
|
|
|
// R-1347: a restore that lifts an UPDATE hold must also forget the stack manager's in-memory „last update ended held"
|
|
// flag. Left set, the crash-loop watcher does not watch the app and the night leg skips it until the controller restarts
|
|
// (D64's sibling: the operator's clear_hold already forgets it, R-1343; the restore did not).
|
|
//
|
|
// The consequence on the real flag — ObserveUnhealthy and legCandidate — is TestR1347_RestoreLiftsTheWatchersBlindSpot
|
|
// (cmd/controller), and the seam's wiring TestR1347_UpdateHeldForgetterIsWiredAtStartup.
|
|
//
|
|
// RED-PROOF (2026-11-02): seen failing before the seam existed — the build failed on SetUpdateHeldForgetter; with the
|
|
// call dropped from clearUpdateHoldAfterRestore it fails on „the flag was not forgotten".
|
|
func TestR1347_ARestoreThatLiftsAnUpdateHoldForgetsTheHeldFlag(t *testing.T) {
|
|
sett := slice4Settings(t)
|
|
m := &Manager{logger: log.New(io.Discard, "", 0), settings: sett}
|
|
forgot := map[string]int{}
|
|
m.SetUpdateHeldForgetter(func(s string) bool { forgot[s]++; return true })
|
|
|
|
_ = m.HoldAfterFailedUpdate("nextcloud", time.Now(), time.Now(), UpdateTierLocal)
|
|
m.clearUpdateHoldAfterRestore("nextcloud")
|
|
if _, held := sett.GetRestoreHold("nextcloud"); held {
|
|
t.Fatal("the update hold must be lifted")
|
|
}
|
|
if forgot["nextcloud"] != 1 {
|
|
t.Fatalf("the restore lifted the update hold, but the flag was not forgotten (calls: %v)", forgot)
|
|
}
|
|
|
|
// A RESTORE hold (R-379) stays, so nothing about it is forgotten.
|
|
_ = sett.SetRestoreHold(settings.RestoreHold{Stack: "rst", At: "2026-08-22T14:00:00Z"})
|
|
m.clearUpdateHoldAfterRestore("rst")
|
|
if _, held := sett.GetRestoreHold("rst"); !held || forgot["rst"] != 0 {
|
|
t.Fatalf("a restore hold is not lifted by a restore (held %v, forget calls %d)", held, forgot["rst"])
|
|
}
|
|
|
|
// No hold at all: nothing to forget (a plain restore of a healthy app).
|
|
m.clearUpdateHoldAfterRestore("plain")
|
|
if forgot["plain"] != 0 {
|
|
t.Fatalf("no hold was lifted, yet the flag was touched %d time(s)", forgot["plain"])
|
|
}
|
|
|
|
// nil seam: no crash (the pre-R-1347 behaviour).
|
|
m2 := &Manager{logger: log.New(io.Discard, "", 0), settings: sett}
|
|
_ = m2.HoldAfterFailedUpdate("other", time.Now(), time.Now(), UpdateTierLocal)
|
|
m2.clearUpdateHoldAfterRestore("other")
|
|
}
|