Files
felhom-controller/controller/internal/api/r552_remove_clears_notice_test.go
T

90 lines
3.0 KiB
Go

package api
import (
"encoding/json"
"go/ast"
"go/parser"
"go/token"
"io"
"log"
"os"
"path/filepath"
"testing"
"gitea.dooplex.hu/admin/felhom-controller/internal/backup"
"gitea.dooplex.hu/admin/felhom-controller/internal/config"
"gitea.dooplex.hu/admin/felhom-controller/internal/settings"
)
// R-552: an interrupted-restore notice was cleared only by a NEW restore of the same app, so a
// household that answered it by REMOVING the app kept a „Megszakadt visszaállítás" card about an app
// that no longer exists — for ever, because the notice is persisted. The consequence asserted: after
// the removal path's clear, the notice is gone from the page's list AND from the persisted record (a
// fresh Manager loading the file sees none), while another app's notice stays.
// COMPANION RED-PROOF: drop the clearInterruptedRestoreNotice call from removeStack → the AST check
// fails; make ClearInterruptedRestore a no-op → the notice is still listed.
func TestR552_RemoveClearsTheInterruptedRestoreNotice(t *testing.T) {
dir := t.TempDir()
cfg := &config.Config{}
cfg.Paths.DataDir = dir
lg := log.New(io.Discard, "", 0)
sett, err := settings.Load(filepath.Join(dir, "settings.json"), lg)
if err != nil {
t.Fatal(err)
}
recPath := filepath.Join(dir, "restore-status.json")
// The record two stops left behind: "otherapp" already carries a notice, "homebox" was running.
seed := map[string]any{
"running": true, "op": "restore", "stack": "homebox",
"interrupted": map[string]any{"otherapp": map[string]any{"op": "restore", "stack": "otherapp", "interrupted": true}},
}
b, _ := json.Marshal(seed)
if err := os.WriteFile(recPath, b, 0o600); err != nil {
t.Fatal(err)
}
load := func() *backup.Manager {
bm := backup.NewManager(cfg, sett, lg)
bm.SetRestoreRecordPath(recPath)
bm.LoadRestoreRecord()
return bm
}
bm := load()
if _, ok := bm.InterruptedRestore("homebox"); !ok {
t.Fatal("setup: no interrupted notice for homebox")
}
r := &Router{backupMgr: bm, logger: lg}
r.clearInterruptedRestoreNotice("homebox")
if _, ok := bm.InterruptedRestore("homebox"); ok {
t.Errorf("R-552: the removed app's interrupted-restore notice is still listed")
}
if _, ok := bm.InterruptedRestore("otherapp"); !ok {
t.Errorf("another app's notice was cleared by this app's removal")
}
again := load()
if _, ok := again.InterruptedRestore("homebox"); ok {
t.Errorf("R-552: the notice comes back after a restart — the clear was not persisted")
}
fset := token.NewFileSet()
f, err := parser.ParseFile(fset, "router.go", nil, 0)
if err != nil {
t.Fatal(err)
}
called := false
for _, d := range f.Decls {
if fn, ok := d.(*ast.FuncDecl); ok && fn.Name.Name == "removeStack" && fn.Body != nil {
ast.Inspect(fn.Body, func(n ast.Node) bool {
if s, ok := n.(*ast.SelectorExpr); ok && s.Sel.Name == "clearInterruptedRestoreNotice" {
called = true
}
return true
})
}
}
if !called {
t.Errorf("R-552: removeStack does not call clearInterruptedRestoreNotice")
}
}