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) } }