Files
admin d698ce343b
gates / gates (push) Successful in 14s
controller v0.242.0: a removed app is listed with its kept backup; five small ones (R-487 R-491 R-490 R-489 R-476 R-456)
R-487: the local backup lists are keyed on the drives, not on what is
deployed — a removed app whose unit was kept is listed with the restore
that reinstalls it, the picker answers for it, and the restore opens the
unit where it sits. R-491: a removal clears the app's update hold.
R-490: /api/system/info reaches the API router and reads the default
storage path. R-489: volumes_removed is the real before/after difference,
[] when none. R-476: a Tier-2 copy is dated by its data, not its manifest.
R-456: the boot-orphan rule is pinned. Every fix red-proofed.
2026-09-13 22:50:18 +02:00

34 lines
1.6 KiB
Go

package bootrecon
import (
"testing"
"gitea.dooplex.hu/admin/felhom-controller/internal/stacks"
)
// R-456 — written down in 02-controller-module-map.md and pinned here: a stack with one member
// REMOVED (absent, not dead) and the rest running reads as running, so the boot sweep does not repair
// it; only a present-but-dead supervised member makes it degraded. Measured 2026-09-02 on demo-hp
// (`docker rm -f bookstack` with bookstack-db running: not selected; both gone: repaired in 6.3 s).
func TestR456_AbsentMemberIsNotABootOrphan(t *testing.T) {
present := []stacks.ContainerInfo{{Name: "bookstack-db", State: "running"}}
state := stacks.AggregateStateForTest(present)
if state != stacks.StateRunning {
t.Fatalf("one running member, one absent: state %q, want running (an absent member is not a dead one)", state)
}
s := stacks.Stack{Name: "bookstack", Deployed: true, State: state, Containers: present,
AppConfig: &stacks.AppConfig{Deployed: true, DesiredState: stacks.DesiredStateRunning}}
if isBootOrphan(s) {
t.Error("a partly-dead stack must NOT be a boot orphan — repairing half a stack beside its live database is not obviously safe (R-456)")
}
// Control: the same member present but DEAD is degraded, and degraded IS an orphan.
dead := []stacks.ContainerInfo{{Name: "bookstack-db", State: "running"}, {Name: "bookstack", State: "exited"}}
if st := stacks.AggregateStateForTest(dead); st != stacks.StateDegraded {
t.Fatalf("control: a present dead member must read degraded, got %q", st)
}
s.State, s.Containers = stacks.StateDegraded, dead
if !isBootOrphan(s) {
t.Error("control: a degraded stack IS a boot orphan")
}
}