ea0d3f1764
The catalog knew only 'present' or 'gone', and 'gone' orphans every customer already running the app. lifecycle: in .felhom.yml withdraws an app from new installs without touching anyone running it. Deploy gate is server-side and fail-closed, before any mutation, with the ruled Hungarian refusal - hiding a button is not a gate. Unknown values fail OPEN (available + one WARN), deliberately opposite, so a typo or a newer catalog cannot pull a working app out of every customer's list. Orphan detection never sees the field - a red-proof adds that filter and shows the abandoned app immediately reading as an orphan. Badge plumbing is generic (MetaBadge + meta_badge partial) so R-56's difficulty labels drop in with no new markup.
66 lines
2.4 KiB
Go
66 lines
2.4 KiB
Go
package stacks
|
|
|
|
import (
|
|
"io"
|
|
"log"
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
|
|
"gitea.dooplex.hu/admin/felhom-controller/internal/config"
|
|
)
|
|
|
|
// TestCatalogTemplateSlugs_IgnoresLifecycle is the ORPHAN NEGATIVE test, and it is the reason
|
|
// lifecycle is a metadata field instead of a directory move.
|
|
//
|
|
// A withdrawn app stays in the catalog tree; only what we OFFER changes. The orphan detector marks a
|
|
// deployed stack "Elavult" when its template has DISAPPEARED from the catalog — so if lifecycle ever
|
|
// leaked into template discovery (a filter in getCatalogTemplateSlugs, or a skip in the syncer's
|
|
// copyTemplates), every customer running an abandoned app would see it flagged as orphaned and be
|
|
// offered a Törlés button for a perfectly working app. That is the exact harm this design avoids.
|
|
//
|
|
// COMPANION RED-PROOF: make getCatalogTemplateSlugs skip non-available templates and this fails —
|
|
// the abandoned app drops out of the catalog set and reads as an orphan. Recorded in REPORT.md.
|
|
func TestCatalogTemplateSlugs_IgnoresLifecycle(t *testing.T) {
|
|
dataDir := t.TempDir()
|
|
tpl := filepath.Join(dataDir, "catalog-cache", "templates")
|
|
|
|
mk := func(app, lifecycle string) {
|
|
d := filepath.Join(tpl, app)
|
|
if err := os.MkdirAll(d, 0755); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := os.WriteFile(filepath.Join(d, "docker-compose.yml"), []byte("services: {}\n"), 0644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
yml := "display_name: " + app + "\n"
|
|
if lifecycle != "" {
|
|
yml += "lifecycle: " + lifecycle + "\n"
|
|
}
|
|
if err := os.WriteFile(filepath.Join(d, ".felhom.yml"), []byte(yml), 0644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
mk("bookstack", "") // available
|
|
mk("plant-it", "abandoned") // withdrawn, but STILL IN THE CATALOG TREE
|
|
mk("someapp", "hidden") // withdrawn, ditto
|
|
|
|
m := &Manager{
|
|
cfg: &config.Config{Paths: config.PathsConfig{DataDir: dataDir}},
|
|
logger: log.New(io.Discard, "", 0),
|
|
}
|
|
slugs := m.getCatalogTemplateSlugs()
|
|
if slugs == nil {
|
|
t.Fatal("catalog set is nil — orphan detection would be skipped entirely")
|
|
}
|
|
for _, app := range []string{"bookstack", "plant-it", "someapp"} {
|
|
if !slugs[app] {
|
|
t.Errorf("%q missing from the catalog set → a deployed instance would be marked ORPHANED "+
|
|
"and offered for deletion. Lifecycle must never affect template DISCOVERY.", app)
|
|
}
|
|
}
|
|
if len(slugs) != 3 {
|
|
t.Errorf("catalog set = %v, want all 3 templates regardless of lifecycle", slugs)
|
|
}
|
|
}
|