Files
felhom-controller/controller/internal/web/r1256_begin_restore_test.go
T
admin 342a394bff
gates / gates (push) Successful in 31s
R-1256: four restore edges (review round 2 HUNT-1 H4/H5/H6/H8) — each test seen failing first
H4: a failed definition write in the off-site restore writes the live definition back before the start;
    a failed write-back holds the app (restore_mixed).
H5: a put-back whose stop fails pours nothing, keeps the copy and holds the app (off-site, local, power-cut);
    a failed definition write-back keeps the copy (D20); a failed 'volume rm -f' fails that volume (no merge).
H6: TryBeginRestoreOp/RestoreBusy: check every operation and record the restore in one step under m.mu;
    every web handler and the kept loads begin through it; install-time kept load asks about the mirror too.
H8: SweepUnusedRestoreCopies every 6 h: removes a copy only with no hold, no marker naming it, no restore
    running, and older than 14 days.

Commit only; rides the next controller release.
2026-10-28 22:24:16 +01:00

61 lines
1.9 KiB
Go

package web
import (
"go/ast"
"go/parser"
"go/token"
"os"
"path/filepath"
"strings"
"testing"
"gitea.dooplex.hu/admin/felhom-controller/internal/backup"
)
// R-1256 H6: every restore handler records its op through beginRestoreOp, which asks about every other operation in
// the same step (backup.TryBeginRestoreOp). A handler that calls BeginRestoreOp directly re-opens the window between
// the early refusal and the record. RED-PROOF: put one `s.backupMgr.BeginRestoreOp(...)` back → FAILS, naming the file.
func TestR1256_H6_NoHandlerBeginsARestoreUnchecked(t *testing.T) {
files, _ := filepath.Glob("*.go")
for _, f := range files {
if strings.HasSuffix(f, "_test.go") {
continue
}
src, err := os.ReadFile(f)
if err != nil {
t.Fatal(err)
}
fs := token.NewFileSet()
af, err := parser.ParseFile(fs, f, src, 0)
if err != nil {
t.Fatal(err)
}
ast.Inspect(af, func(n ast.Node) bool {
if sel, ok := n.(*ast.SelectorExpr); ok && sel.Sel.Name == "BeginRestoreOp" {
t.Errorf("%s: a direct BeginRestoreOp — begin through s.beginRestoreOp (R-1256 H6)", fs.Position(sel.Pos()))
}
return true
})
}
}
// The handler's begin refuses with the household's sentence when a second-drive copy began after the early check.
func TestR1256_H6_TheBeginRefusesWithTheCopySentence(t *testing.T) {
s, _, _ := newOffboxWebServer(t)
if _, blocked := s.restoreOpBlocked(); blocked {
t.Fatal("fixture: nothing runs")
}
done := backup.MarkTier2CopyForTest("immich")
msg, ok := s.beginRestoreOp("offbox-reconstitute", "immich")
done()
if ok || !strings.Contains(msg, "második meghajtóra") {
t.Fatalf("the begin must refuse with the copy named; ok=%v %q", ok, msg)
}
if s.backupMgr.RestoreStatus().Running {
t.Fatal("a refused begin recorded a restore")
}
if _, ok := s.beginRestoreOp("offbox-reconstitute", "immich"); !ok {
t.Fatal("after the copy the begin must work")
}
}