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)
gates / gates (push) Successful in 14s

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.
This commit is contained in:
2026-09-13 22:50:18 +02:00
parent c1f62ddae8
commit d698ce343b
22 changed files with 838 additions and 13 deletions
@@ -1,9 +1,13 @@
package api
import (
"encoding/json"
"gitea.dooplex.hu/admin/felhom-controller/internal/settings"
"go/ast"
"go/parser"
"go/token"
"net/http"
"net/http/httptest"
"strings"
"testing"
)
@@ -60,3 +64,58 @@ func TestR486_RemovalKeepsTheTier2RecordUnlessBackupsGo(t *testing.T) {
})
}
}
// R-491 — a removal clears the app's UPDATE hold (and only that kind).
//
// COMPANION RED-PROOF (REPORT.md): remove the ClearUpdateHold call from removeStack — this fails.
func TestR491_RemovalClearsTheUpdateHold(t *testing.T) {
fset := token.NewFileSet()
f, err := parser.ParseFile(fset, "router.go", nil, 0)
if err != nil {
t.Fatal(err)
}
found := false
for _, d := range f.Decls {
fn, ok := d.(*ast.FuncDecl)
if !ok || fn.Name.Name != "removeStack" || fn.Body == nil {
continue
}
ast.Inspect(fn.Body, func(n ast.Node) bool {
if s, ok := n.(*ast.SelectorExpr); ok && s.Sel.Name == "ClearUpdateHold" {
found = true
}
return true
})
}
if !found {
t.Error("removeStack must clear the app's update hold (R-491), or a reinstall starts held")
}
}
// R-490 — with a default storage path registered, system-info reports the drive (the fallback every
// other reader already had).
func TestR490_SystemInfoFallsBackToTheDefaultStoragePath(t *testing.T) {
r, sett, _, _ := newSlice4Router(t)
r.sett = sett // the fixture router carries no settings; system-info reads the default path from them
if err := sett.AddStoragePath(settings.StoragePath{Path: t.TempDir(), Label: "HDD", Schedulable: true, IsDefault: true}); err != nil {
t.Fatal(err)
}
w := httptest.NewRecorder()
r.ServeHTTP(w, httptest.NewRequest(http.MethodGet, "/api/system/info", nil))
if w.Code != http.StatusOK {
t.Fatalf("status %d: %s", w.Code, w.Body.String())
}
var resp struct {
Data struct {
System struct {
HDDConfigured bool `json:"hdd_configured"`
} `json:"system"`
} `json:"data"`
}
if err := json.Unmarshal(w.Body.Bytes(), &resp); err != nil {
t.Fatal(err)
}
if !resp.Data.System.HDDConfigured {
t.Errorf("hdd_configured must be true with a default storage path registered; body %s", w.Body.String())
}
}