123 lines
4.5 KiB
Go
123 lines
4.5 KiB
Go
package backup
|
|
|
|
import (
|
|
"errors"
|
|
"io"
|
|
"log"
|
|
"path/filepath"
|
|
"sync/atomic"
|
|
"testing"
|
|
"time"
|
|
|
|
"gitea.dooplex.hu/admin/felhom-controller/internal/settings"
|
|
)
|
|
|
|
// r1342Drive registers the rig's app drive, as every drive is registered on a box, so the list and the delete see it.
|
|
func r1342Drive(t *testing.T, m *Manager) {
|
|
t.Helper()
|
|
if m.settings == nil {
|
|
sett, err := settings.Load(filepath.Join(t.TempDir(), "settings.json"), log.New(io.Discard, "", 0))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
m.settings = sett
|
|
}
|
|
if err := m.settings.AddStoragePath(settings.StoragePath{Path: m.GetAppDrivePath("app"), Label: "rig"}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
// R-1342 (found live on Tester 1, 2026-10-29 10:39 CET): (1) while the box still watches whether a restored app runs
|
|
// stably, its restore copy is the way back — „Törlés most" must not take it (D62: never while an operation runs on the
|
|
// app); (2) an app REMOVED during that watch is not „did not start" — no operator alarm, no „the copy is kept".
|
|
|
|
// removableProvider is the stable-watch rig's provider that can answer StackRemoved.
|
|
type removableProvider struct {
|
|
*sampledProvider
|
|
removed atomic.Bool
|
|
}
|
|
|
|
func (p *removableProvider) StackRemoved(string) bool { return p.removed.Load() }
|
|
|
|
// RED-PROOF: drop the watching check from restoreCopyNeededFor / DeleteRestoreCopy → the copy is offered and deleted
|
|
// mid-watch → FAILS.
|
|
func TestR1342_ACopyUnderTheStableWatchIsNotDeletable(t *testing.T) {
|
|
withStable(t, 400*time.Millisecond, 3*time.Second)
|
|
m, rig, alarms := fixARig(t, func(int) bool { return true },
|
|
func(int) []ContainerSample { return []ContainerSample{healthy("c1", 0)} })
|
|
r1342Drive(t, m)
|
|
m.BeginRestoreOp("restore", "app")
|
|
if _, err := m.RestoreFromRecoveryUnit("app"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
m.EndRestoreOp(true, "ok")
|
|
copies := m.RestoreCopies()
|
|
if len(copies) != 1 || copies[0].Needed != RestoreCopyNeededChecking || !copies[0].DeleteOn.IsZero() {
|
|
t.Fatalf("mid-watch the copy must read „checking\", got %+v", copies)
|
|
}
|
|
if _, err := m.DeleteRestoreCopy("app", copies[0].Stamp); !errors.Is(err, ErrRestoreCopyNeeded) {
|
|
t.Fatalf("a delete mid-watch: %v, want ErrRestoreCopyNeeded", err)
|
|
}
|
|
if left := undoCopies(t, rig.undoRoot); len(left) != 1 {
|
|
t.Fatalf("the copy must stay through the watch, found %v", left)
|
|
}
|
|
if left := waitNoCopies(t, rig.undoRoot); len(left) != 0 {
|
|
t.Fatalf("after the stable window the watch releases it, found %v", left)
|
|
}
|
|
if len(*alarms) != 0 {
|
|
t.Fatalf("no alarm: %v", *alarms)
|
|
}
|
|
if m.watchingCopy(filepath.Join(rig.undoRoot, copies[0].Stamp)) {
|
|
t.Fatal("the watch mark must go when the watch ends")
|
|
}
|
|
}
|
|
|
|
// RED-PROOF: drop the stableRemoved case → the removed app „did not run stably" → one alarm → FAILS.
|
|
func TestR1342_AnAppRemovedDuringTheWatchRaisesNoAlarm(t *testing.T) {
|
|
withStable(t, 2*time.Second, 400*time.Millisecond)
|
|
var up atomic.Bool
|
|
up.Store(true)
|
|
m, rig, alarms := fixARig(t, func(int) bool { return up.Load() },
|
|
func(int) []ContainerSample { return []ContainerSample{healthy("c1", 0)} })
|
|
rp := &removableProvider{sampledProvider: m.stackProvider.(*sampledProvider)}
|
|
m.stackProvider = rp
|
|
r1342Drive(t, m)
|
|
m.BeginRestoreOp("restore", "app")
|
|
if _, err := m.RestoreFromRecoveryUnit("app"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
m.EndRestoreOp(true, "ok")
|
|
// the household removes the app inside the watch
|
|
up.Store(false)
|
|
rp.removed.Store(true)
|
|
time.Sleep(700 * time.Millisecond)
|
|
if len(*alarms) != 0 {
|
|
t.Fatalf("an app removed during the watch must not alarm „did not start\": %v", *alarms)
|
|
}
|
|
copies := m.RestoreCopies()
|
|
if len(copies) != 1 || copies[0].Needed != "" {
|
|
t.Fatalf("after the watch ended the copy is an unused one (sweep / Törlés most): %+v", copies)
|
|
}
|
|
_ = rig
|
|
}
|
|
|
|
// Control: an app that stays installed and never runs stably still alarms once (the R-1270 rule is unchanged).
|
|
func TestR1342_AnInstalledUnstableAppStillAlarms(t *testing.T) {
|
|
withStable(t, 2*time.Second, 400*time.Millisecond)
|
|
var up atomic.Bool
|
|
up.Store(true)
|
|
m, _, alarms := fixARig(t, func(int) bool { return up.Load() },
|
|
func(int) []ContainerSample { return []ContainerSample{healthy("c1", 0)} })
|
|
m.stackProvider = &removableProvider{sampledProvider: m.stackProvider.(*sampledProvider)}
|
|
m.BeginRestoreOp("restore", "app")
|
|
if _, err := m.RestoreFromRecoveryUnit("app"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
m.EndRestoreOp(true, "ok")
|
|
up.Store(false)
|
|
time.Sleep(700 * time.Millisecond)
|
|
if len(*alarms) != 1 {
|
|
t.Fatalf("an installed app that never runs stably alarms once, got %v", *alarms)
|
|
}
|
|
}
|