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
@@ -0,0 +1,38 @@
package stacks
import (
"encoding/json"
"strings"
"testing"
)
// R-489 — the removal reports the volumes it removed, and `[]` when none — never `null`.
//
// COMPANION RED-PROOF (REPORT.md): make removedVolumes return nil for an empty result — the JSON
// reads null and the second assertion fails.
func TestR489_RemovedVolumesIsADifferenceAndNeverNull(t *testing.T) {
got := removedVolumes([]string{"app_data", "app_db", "app_cache"}, []string{"app_cache"})
if strings.Join(got, ",") != "app_data,app_db" {
t.Errorf("before minus after = %v", got)
}
b, _ := json.Marshal(RemoveResponse{VolumesRemoved: removedVolumes(nil, nil)})
if !strings.Contains(string(b), `"volumes_removed":[]`) {
t.Errorf("no volumes must read as [] — got %s", b)
}
}
func TestR489_ProjectVolumesReadsTheDockerListing(t *testing.T) {
m := &Manager{}
var seen []string
m.execFn = func(name string, args ...string) (string, error) {
seen = append([]string{name}, args...)
return "app_data\napp_db\n\n", nil
}
got := m.projectVolumes("app")
if strings.Join(got, ",") != "app_data,app_db" {
t.Errorf("volumes = %v", got)
}
if !strings.Contains(strings.Join(seen, " "), "label=com.docker.compose.project=app") {
t.Errorf("must filter by the compose project label; ran %v", seen)
}
}