Files
felhom-controller/controller/internal/stacks/d64_forget_update_held_test.go
T
admin 5b3b541602
gates / gates (push) Successful in 31s
clear_hold (D64 = B) + the box command through the running controller (R-1343), R-1340, R-1341 — tests red first; commit only
- clear_hold: a fifth verb in the closed operator-action list; lifts a hold of any reason in the RUNNING
  controller (memory + settings.json together), forgets the update's held flag, drops a waiting put-back
  from the app-stop marker (copy kept). Refused on a bad name or no hold. No new event type.
- --clear-restore-hold asks the running controller over <data dir>/ctl/hold.sock (unix, 0600, peer uid
  checked, no TCP); edits settings.json only when no controller answers, and says so (in-guest restart).
- R-1340: the operator's HELD STOPPED mail is built from the hold record (restore kind, mixed cause,
  failed put-back step, database replay or not, kept copy) — never the option-C database story by default.
- R-1341: the OOM WARN line once per new kill (container, start time, kill count); event unchanged.
2026-10-29 12:49:46 +01:00

47 lines
1.9 KiB
Go

package stacks
import (
"io"
"log"
"testing"
"gitea.dooplex.hu/admin/felhom-controller/internal/config"
)
// D64 = B / R-1343: when the operator clears a hold, the ONE held flag kept outside the hold store — „the last update
// ended held" — goes too. Left set, the crash-loop observer and the nightly update leg kept skipping an app whose hold
// was gone, until the controller restarted.
// RED-PROOF (2026-10-29): with ForgetUpdateHeld's body reduced to `return false`, this fails on „a held update must be forgotten".
func TestD64_ForgetUpdateHeld(t *testing.T) {
m := &Manager{cfg: &config.Config{}, logger: log.New(io.Discard, "", 0), stacks: map[string]*Stack{
"kimai": {Name: "kimai", Deployed: true},
"running": {Name: "running", Deployed: true, Updating: true},
}}
m.finishUpdateKey("kimai", UpdatePhaseFailed, UpdateErrorKeyHeld, "a kimai leállítva marad")
m.markUpdateHeld("kimai")
m.markUpdateHeld("running")
if !m.ForgetUpdateHeld("kimai") {
t.Fatal("a held update must be forgotten")
}
st, _ := m.GetStack("kimai")
if st.updateHeld || st.UpdateErrorKey != "" || st.UpdateError != "" || st.UpdatePhase != "" {
t.Fatalf("held state left: held=%v key=%q err=%q phase=%q", st.updateHeld, st.UpdateErrorKey, st.UpdateError, st.UpdatePhase)
}
// The two readers of the flag (ObserveUnhealthy's skip, unhealthy.go; legCandidate's LegSkipHeld, unattended.go)
// read the stored stack — not called here, because ObserveUnhealthy reaches docker (R-650).
m.mu.RLock()
skipped := m.stacks["kimai"].updateHeld || m.stacks["kimai"].UpdateErrorKey == UpdateErrorKeyHeld
m.mu.RUnlock()
if skipped {
t.Fatal("the observer and the update leg still skip kimai")
}
// A running update is never touched; an app with nothing held reports false.
if m.ForgetUpdateHeld("running") {
t.Fatal("a running update must not be touched")
}
if m.ForgetUpdateHeld("kimai") || m.ForgetUpdateHeld("nosuch") {
t.Fatal("nothing held → false")
}
}