package stacks import ( "path" "reflect" "testing" "gitea.dooplex.hu/admin/felhom-controller/internal/appbackup" ) // absSet returns the sorted Abs slice of a CaptureSet's Paths (helper for exact assertions). func absSet(cs appbackup.CaptureSet) []string { out := make([]string, 0, len(cs.Paths)) for _, p := range cs.Paths { out = append(out, p.Abs) } return out } // TestCaptureSet_Wiring (Scenario G, F-S3 no-seam): a real .felhom.yml backup block + real compose, // loaded through a REAL stacks.Manager, must produce the exact per-tier capture sets end-to-end — // ClassifiedBinds → ComputeCaptureSet — with no fake between the parser, validator, classifier, and // the capture computation (the wiring is where a class-constant typo would otherwise hide). func TestCaptureSet_Wiring(t *testing.T) { felhom := `display_name: Wiretest backup: userdata: - path: media/photos class: optional ` // appdata/wiretest is UNLISTED + writable → default mandatory; media/photos is :ro but EXPLICIT // optional (explicit beats the :ro reader-default). compose := `services: wiretest: volumes: - ${HDD_PATH}/appdata/wiretest:/data - ${USERDATA_PATH}/media/photos:/external/photos:ro ` m := newClassifyManager(t, felhom, compose) cbs, has := m.ClassifiedBinds("app") if !has { t.Fatal("valid block must classify end-to-end (hasClassification=true)") } const hddPath = "/mnt/drv" wantWiretest := path.Join(hddPath, "appdata/wiretest") wantPhotos := path.Join(hddPath, "userdata", "media/photos") // Secondary: mandatory + optional, sorted by Abs. sec := appbackup.ComputeCaptureSet(cbs, has, appbackup.TierSecondary, hddPath, "") if got, want := absSet(sec), []string{wantWiretest, wantPhotos}; !reflect.DeepEqual(got, want) { t.Errorf("secondary Paths = %v, want %v", got, want) } // Offsite: mandatory only — the explicit-optional :ro library must NOT appear. off := appbackup.ComputeCaptureSet(cbs, has, appbackup.TierOffsite, hddPath, "") if got, want := absSet(off), []string{wantWiretest}; !reflect.DeepEqual(got, want) { t.Errorf("offsite Paths = %v, want %v (optional :ro must not ship offsite)", got, want) } // classes ride through end-to-end for _, p := range sec.Paths { if p.Abs == wantPhotos && p.Class != appbackup.ClassOptional { t.Errorf("media/photos class = %q, want optional (explicit beats :ro default, through the real Manager)", p.Class) } if p.Abs == wantWiretest && p.Class != appbackup.ClassMandatory { t.Errorf("appdata/wiretest class = %q, want mandatory (unlisted writable default)", p.Class) } } }