package api import ( "context" "net/http" "net/http/httptest" "os" "path/filepath" "strings" "testing" "time" "gitea.dooplex.hu/admin/felhom-controller/internal/stacks" ) // v0.269.0 (`09` §3 decision 27, R-666) — while a held app's page says support is informed, the remove // API allows only "remove the app, keep my data". Through the production wiring (NewManager + ScanStacks, // the hold read through UpdateGuards as main.go wires it). type heldGuards struct{ noWhole bool } func (g heldGuards) HoldFor(string) (bool, string) { return true, "HELD" } func (g heldGuards) HoldNoWholeCopy(string) bool { return g.noWhole } func (g heldGuards) Busy(string) (bool, string) { return false, "" } func (g heldGuards) CanBackUp(string) (bool, string) { return true, "" } func (g heldGuards) BackupNow(context.Context, string) error { return nil } func (g heldGuards) SafetyDump(context.Context, string) ([]string, error) { return nil, nil } func (g heldGuards) RestorePoints(context.Context, string, func(stacks.UpdateRestorePoint) bool) (stacks.UpdateRestorePoint, bool, []stacks.UpdateRestorePoint) { return stacks.UpdateRestorePoint{}, false, nil } func (g heldGuards) HoldAfterFailedUpdate(string, time.Time, stacks.UpdateRestorePoint, string) error { return nil } // COMPANION RED-PROOF (REPORT): delete the decision-27 block in removeStack — the data-deleting remove // then reaches RemoveStack and this test fails at "was not refused". func TestR666_HeldWithNoWholeCopyRemovesOnlyKeepingData(t *testing.T) { for _, body := range []string{`{"remove_hdd_data":true,"remove_backups":false}`, `{"remove_hdd_data":false,"remove_backups":true}`} { r, dir := newR442Router(t, "deployed: true\nenv:\n HDD_PATH: /mnt/felhom-drives/x\n") r.stackMgr.SetUpdateGuards(heldGuards{noWhole: true}) code, resp := postRemove(t, r, body) if code != http.StatusConflict || resp.OK { t.Fatalf("%s: HTTP %d ok=%v — a data-deleting remove of a stranded app was not refused", body, code, resp.OK) } if !strings.Contains(resp.Error, "ügyfélszolgálat") { t.Fatalf("error = %q, want the support sentence", resp.Error) } if _, err := os.Stat(filepath.Join(dir, "app.yaml")); err != nil { t.Fatal("app.yaml gone — something was removed by a refused request") } } // The dialog is told to offer keep-data only … r, _ := newR442Router(t, "deployed: true\nenv:\n HDD_PATH: /mnt/felhom-drives/x\n") r.stackMgr.SetUpdateGuards(heldGuards{noWhole: true}) w := httptest.NewRecorder() r.getStackHDDData(w, httptest.NewRequest(http.MethodGet, "/api/stacks/app/hdd-data", nil), "app") if !strings.Contains(w.Body.String(), `"keep_data_only":true`) { t.Fatalf("hdd-data does not tell the dialog: %s", w.Body.String()) } // … and a hold that NAMES a whole copy keeps the full dialog (positive control). r2, _ := newR442Router(t, "deployed: true\nenv:\n HDD_PATH: /mnt/felhom-drives/x\n") r2.stackMgr.SetUpdateGuards(heldGuards{noWhole: false}) w2 := httptest.NewRecorder() r2.getStackHDDData(w2, httptest.NewRequest(http.MethodGet, "/api/stacks/app/hdd-data", nil), "app") if strings.Contains(w2.Body.String(), "keep_data_only") { t.Fatalf("a hold with a whole copy must keep the full dialog: %s", w2.Body.String()) } }