Files
felhom-controller/controller/internal/stacks/r569_stack_error_kinds_test.go
T
admin d15ad105be R-569: stack-lifecycle handlers pick their status by error KIND, not English words
stop/start/restart/update, remove and delete matched "protected", "not found", "not deployed",
"still running", "not orphaned" in err.Error(). New sentinels in internal/stacks
(stack_errors.go) carried by the producers in manager.go/delete.go via util.KindErrorf (message
bytes unchanged); api.stackOpStatusFor maps them. Tests: reworded-message table per family,
wiring check over all three handlers, producers keep kind + words.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
2026-10-05 22:24:22 +02:00

52 lines
2.4 KiB
Go

package stacks
import (
"errors"
"testing"
)
// R-569 — the stack-lifecycle refusals the API maps to 404/403/409 carry their KIND, and their
// messages are byte-for-byte what they were. Every case below refuses BEFORE anything is executed
// (no compose, no docker): unknown name, protected name, or a scanned app that is not orphaned.
//
// RED-PROOF (REPORT): put any one producer back to plain fmt.Errorf and its errors.Is row fails while
// its message row still passes — the silent state the API's old text match lived in.
func TestR569_StackProducersCarryKindAndKeepTheirWords(t *testing.T) {
m := r553Manager(t, "display_name: R569\n")
cases := []struct {
name string
call func() error
kind error
wantMsg string
}{
{"start unknown", func() error { return m.StartStack("nope") }, ErrStackNotFound, `stack "nope" not found`},
{"stop unknown", func() error { return m.StopStack("nope") }, ErrStackNotFound, `stack "nope" not found`},
{"restart unknown", func() error { return m.RestartStack("nope") }, ErrStackNotFound, `stack "nope" not found`},
{"stop protected", func() error { return m.StopStack("samba") }, ErrProtectedStack,
`stack "samba" is protected and cannot be stopped`},
{"remove unknown", func() error { _, err := m.RemoveStack("nope", false, nil); return err }, ErrStackNotFound,
`stack "nope" not found`},
{"remove protected", func() error { _, err := m.RemoveStack("samba", false, nil); return err }, ErrProtectedStack,
`stack "samba" is protected and cannot be removed`},
{"delete unknown", func() error { _, err := m.DeleteStack("nope", false); return err }, ErrStackNotFound,
`stack "nope" not found`},
{"delete protected", func() error { _, err := m.DeleteStack("samba", false); return err }, ErrProtectedStack,
`stack "samba" is protected and cannot be deleted`},
{"delete not orphaned", func() error { _, err := m.DeleteStack("r553app", false); return err }, ErrNotOrphaned,
`stack "r553app" is not orphaned — only orphaned stacks can be deleted`},
}
for _, c := range cases {
err := c.call()
if err == nil {
t.Errorf("%s: no refusal", c.name)
continue
}
if !errors.Is(err, c.kind) {
t.Errorf("%s: errors.Is(err, %v) = false — the API would answer 500 (err=%q)", c.name, c.kind, err.Error())
}
if err.Error() != c.wantMsg {
t.Errorf("%s: message moved:\n got %q\n want %q", c.name, err.Error(), c.wantMsg)
}
}
}