206b0357d1
gates / gates (push) Successful in 27s
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
86 lines
3.3 KiB
Go
86 lines
3.3 KiB
Go
package stacks
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"reflect"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// R-651 (v0.268.0) — a removed app left `applied-compose.yml` and `applied-meta/` in its stack dir.
|
|
// The CONSEQUENCE asserted: after remove + reinstall, the probe the undo would judge the old version
|
|
// by (savePreUpdateMeta) is the REINSTALL's .felhom.yml — not the removed install's record. (A
|
|
// reinstall whose pin step fails writes no record of its own, which is when the stale one was read.)
|
|
//
|
|
// COMPANION RED-PROOF (REPORT.md): delete the R-651 block in RemoveStack. This test then fails at
|
|
// "the undo would probe with the REMOVED install's .felhom.yml".
|
|
func TestR651_RemoveDeletesTheAppliedRecord(t *testing.T) {
|
|
drive := t.TempDir()
|
|
m, dir, _ := newR442Manager(t, "app", ssdCompose, driveAppYAML(drive), drive)
|
|
mustWrite(t, AppliedComposePath(dir), ssdCompose)
|
|
if err := storeAppliedMeta(dir, []byte("# the REMOVED install's probe\n")); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := m.RemoveStack("app", false, nil); err != nil {
|
|
t.Fatalf("RemoveStack: %v", err)
|
|
}
|
|
for _, p := range []string{AppliedComposePath(dir), filepath.Join(dir, appliedMetaDir)} {
|
|
if _, err := os.Stat(p); err == nil {
|
|
t.Errorf("%s survived the remove (R-651)", p)
|
|
}
|
|
}
|
|
// The catalog mirror stays: it is the template, not the install.
|
|
if _, err := os.Stat(filepath.Join(dir, "docker-compose.yml")); err != nil {
|
|
t.Fatalf("the catalog mirror's compose file must stay: %v", err)
|
|
}
|
|
// Reinstall (its pin step wrote no record) → the undo's probe source.
|
|
mustWrite(t, filepath.Join(dir, ".felhom.yml"), "# the REINSTALL's probe\n")
|
|
md, _ := savePreUpdateMeta(dir)
|
|
b, _ := os.ReadFile(filepath.Join(md, ".felhom.yml"))
|
|
if !strings.Contains(string(b), "REINSTALL") {
|
|
t.Fatalf("the undo would probe with the REMOVED install's .felhom.yml: %q", b)
|
|
}
|
|
}
|
|
|
|
// R-658 — the removal's report counts a declared volume that carries no label (a restored app).
|
|
// Measured at the 2026-09-23 night's teardown: `volumes_removed: []` over volumes that did go.
|
|
//
|
|
// COMPANION RED-PROOF (REPORT.md): make appVolumeSet return projectVolumes alone — the report reads
|
|
// `[]` again and this test fails.
|
|
func TestR658_RemoveReportsUnlabelledVolumes(t *testing.T) {
|
|
drive := t.TempDir()
|
|
m, _, _ := newR442Manager(t, "app", ssdCompose, driveAppYAML(drive), drive)
|
|
downDone := false
|
|
m.execFn = func(name string, args ...string) (string, error) {
|
|
a := strings.Join(args, " ")
|
|
switch {
|
|
case strings.Contains(a, "label=com.docker.compose.project="):
|
|
return "", nil // restored: no label
|
|
case a == "volume ls -q":
|
|
if downDone {
|
|
return "other_vol\n", nil
|
|
}
|
|
return "app_app_cfg\nother_vol\n", nil
|
|
}
|
|
return "", nil
|
|
}
|
|
// the stub compose (newR442Manager) is the `down`; flip the listing after the first before-read.
|
|
calls := 0
|
|
inner := m.execFn
|
|
m.execFn = func(name string, args ...string) (string, error) {
|
|
if strings.Join(args, " ") == "volume ls -q" {
|
|
calls++
|
|
downDone = calls > 1
|
|
}
|
|
return inner(name, args...)
|
|
}
|
|
resp, err := m.RemoveStack("app", false, nil)
|
|
if err != nil {
|
|
t.Fatalf("RemoveStack: %v", err)
|
|
}
|
|
if !reflect.DeepEqual(resp.VolumesRemoved, []string{"app_app_cfg"}) {
|
|
t.Fatalf("volumes_removed = %v, want [app_app_cfg] — an unlabelled volume the remove did delete", resp.VolumesRemoved)
|
|
}
|
|
}
|