1453cfc69b
gates / gates (push) Failing after 50s
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
90 lines
3.0 KiB
Go
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")
|
|
}
|
|
}
|