964ae7538f
gates / gates (push) Successful in 27s
compose down (volumes kept) before the record reads not deployed. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
70 lines
2.8 KiB
Go
70 lines
2.8 KiB
Go
package stacks
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"runtime"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// R-649 (v0.266.0, operator ruling 2026-09-23) — a failed install removes the containers it started.
|
|
// The stub compose binary FAILS `up` and records every call, so the test sees what the deploy ran
|
|
// across the real process boundary.
|
|
//
|
|
// COMPANION RED-PROOF (REPORT.md): delete the `down` call in runComposeDeploy's failure branch — the
|
|
// recorded calls are only [up -d] and this fails.
|
|
func TestR649_AFailedInstallRemovesWhatItStarted(t *testing.T) {
|
|
if runtime.GOOS != "linux" {
|
|
t.Skip("the stub compose binary is a shell script")
|
|
}
|
|
m, dir := newInstalledManager(t, "services:\n web:\n image: nginx:1.27\n", "deployed: true\nenv: {}\n")
|
|
bin := t.TempDir()
|
|
calls := filepath.Join(t.TempDir(), "calls")
|
|
script := "#!/bin/sh\necho \"$*\" >> " + calls + "\ncase \"$*\" in *up*) exit 1;; esac\nexit 0\n"
|
|
if err := os.WriteFile(filepath.Join(bin, "docker-compose"), []byte(script), 0o755); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
t.Setenv("PATH", bin) // ONLY the stub: no real docker can ever be reached from here (R-650)
|
|
m.composeCmd = "docker-compose"
|
|
m.execFn = func(string, ...string) (string, error) { return "", nil }
|
|
|
|
m.runComposeDeploy("bookstack", dir, map[string]string{}, &AppConfig{Deployed: true})
|
|
|
|
b, _ := os.ReadFile(calls)
|
|
got := strings.Split(strings.TrimSpace(string(b)), "\n")
|
|
if len(got) != 2 || !strings.Contains(got[0], "up -d") || !strings.HasSuffix(got[1], "down") {
|
|
t.Fatalf("a failed install must run `down` after its failed `up`; calls=%q", got)
|
|
}
|
|
if strings.Contains(got[1], "-v") || strings.Contains(got[1], "--volumes") {
|
|
t.Fatalf("the cleanup must KEEP named volumes (a reinstall finds its data); got %q", got[1])
|
|
}
|
|
if cfg := LoadAppConfig(dir); cfg != nil && cfg.Deployed {
|
|
t.Fatal("the record must read not deployed")
|
|
}
|
|
}
|
|
|
|
// A successful install never runs `down` (control: the cleanup is on the failure branch only).
|
|
func TestR649_ASuccessfulInstallIsNotTakenDown(t *testing.T) {
|
|
if runtime.GOOS != "linux" {
|
|
t.Skip("the stub compose binary is a shell script")
|
|
}
|
|
m, dir := newInstalledManager(t, "services:\n web:\n image: nginx:1.27\n", "deployed: true\nenv: {}\n")
|
|
bin := t.TempDir()
|
|
calls := filepath.Join(t.TempDir(), "calls")
|
|
script := "#!/bin/sh\necho \"$*\" >> " + calls + "\nexit 0\n"
|
|
if err := os.WriteFile(filepath.Join(bin, "docker-compose"), []byte(script), 0o755); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
t.Setenv("PATH", bin)
|
|
m.composeCmd = "docker-compose"
|
|
m.execFn = func(string, ...string) (string, error) { return "", nil }
|
|
|
|
m.runComposeDeploy("bookstack", dir, map[string]string{}, &AppConfig{Deployed: true})
|
|
|
|
b, _ := os.ReadFile(calls)
|
|
if strings.Contains(string(b), "down") {
|
|
t.Fatalf("a successful install must not be taken down; calls=%q", string(b))
|
|
}
|
|
}
|