Traefik v3.7.17 (GHSA-xv5x-242w-7hf7); R-1347 a restore forgets the update-held flag; R-1348 a failed put-back at start-up mails; R-1339, R-1351, R-1323 truthful start-up lines and one lock over FileBrowser's compose. Commit only.
gates / gates (push) Successful in 29s

This commit is contained in:
admin committed 2026-11-02 23:14:16 +01:00
1 parent a90b62323f
commit c4b6844d34
19 files changed
+569 -9

No files matched your search

@@ -0,0 +1,53 @@
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")
}