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" ) // R-474 — the remove handler really deletes the whole unit, the Tier-2 mirror and the prefs. func TestR474_RemoveHandlerDeletesUnitMirrorAndPrefs(t *testing.T) { fset := token.NewFileSet() f, err := parser.ParseFile(fset, "router.go", nil, 0) if err != nil { t.Fatal(err) } var sel []string 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 { sel = append(sel, s.Sel.Name) } return true }) } joined := " " + strings.Join(sel, " ") + " " for _, want := range []string{"RecoveryUnitPath", "Tier2MirrorDirsForApp", "RemoveTier2Mirrors", "DeleteAppBackupPrefs"} { if !strings.Contains(joined, " "+want+" ") { t.Errorf("removeStack must call %s", want) } } } // R-486 — removing an app with its backups KEPT keeps its Tier-2 record. The only call that may // forget the record is DeleteAppBackupPrefs, which the R-474 branch guards on remove_backups. // // COMPANION RED-PROOF (REPORT.md): put `r.sett.SetCrossDriveConfig(name, nil)` back into // removeStack unconditionally — this fails. func TestR486_RemovalKeepsTheTier2RecordUnlessBackupsGo(t *testing.T) { fset := token.NewFileSet() f, err := parser.ParseFile(fset, "router.go", nil, 0) if err != nil { t.Fatal(err) } 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 == "SetCrossDriveConfig" { t.Errorf("removeStack calls SetCrossDriveConfig at line %d — a removal with backups kept would forget the mirror it kept (R-486)", fset.Position(s.Pos()).Line) } return true }) } } // 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()) } }