cf9ce01917
SQ6 over-capture FIXED for classified apps: the userdata root tar is exclude-scoped (keeps only ancestors/descendants of a SELECTED bind relpath — R1-C, the tier2Reconcile keep-rule); no selected userdata bind → no root tar (radarr state-only). New appbackup.ComputeFabBuckets (shared resolveGuardCollapse pipeline; class buckets; guards over ALL classes; no cross-bucket containment). appexport/fabplan.go: computeFabPlan + tarDirectoryExcluding + fabEstimateSplit. ExportRequest gains DeselectOptional/OptInExcluded (both start handlers — two-call-site); mandatory is a server-side floor. Manifest v1 + import UNTOUCHED; legacy apps byte-identical to v0.130.0. Estimate additive class split; export UI: locked-mandatory/optional-checkboxes/excluded-opt-in + two-number warning. All 6 §10 red-proofs verified. 6D Accept #1 now runs against this shape.
108 lines
3.6 KiB
Go
108 lines
3.6 KiB
Go
package appexport
|
|
|
|
import (
|
|
"errors"
|
|
"io"
|
|
"log"
|
|
"strings"
|
|
"testing"
|
|
|
|
"gitea.dooplex.hu/admin/felhom-controller/internal/appbackup"
|
|
)
|
|
|
|
// hddProvider is an rtProvider that reports an HDD-backed stack (estimate scenario H + the
|
|
// v0.130.0 additive-export tests in export_additive_test.go).
|
|
type hddProvider struct {
|
|
*rtProvider
|
|
mounts []string
|
|
hddPath string
|
|
binds []appbackup.ClassifiedBind
|
|
hasBinds bool
|
|
}
|
|
|
|
func (p *hddProvider) GetStackNeedsHDD(string) bool { return true }
|
|
func (p *hddProvider) GetStackHDDMounts(string) []string { return p.mounts }
|
|
func (p *hddProvider) GetStackHDDPath(string) string { return p.hddPath }
|
|
func (p *hddProvider) GetStackClassifiedBinds(string) ([]appbackup.ClassifiedBind, bool) {
|
|
return p.binds, p.hasBinds
|
|
}
|
|
|
|
func newEstimator(t *testing.T, provider ExportStackProvider) *Exporter {
|
|
t.Helper()
|
|
return NewExporter(provider, log.New(io.Discard, "", 0), "test")
|
|
}
|
|
|
|
// Scenario F (the F-A fix): a volume-only app with a >1 GiB volume reports the REAL size via the
|
|
// container-view sizer — not 0/"3.6 KB". This is the F-A red-proof anchor (revert EstimateExport to
|
|
// dockerVolumeSize → reads 0).
|
|
func TestEstimate_VolumeSize_RealNotZero(t *testing.T) {
|
|
const twoGiB = int64(2) << 30
|
|
orig := volumeSizer
|
|
volumeSizer = func(vol string) (int64, error) { return twoGiB, nil }
|
|
defer func() { volumeSizer = orig }()
|
|
|
|
e := newEstimator(t, &rtProvider{stackDir: t.TempDir(), volumes: []string{"app_data"}})
|
|
est, err := e.EstimateExport("app", t.TempDir())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if est.SizeUnknown {
|
|
t.Fatalf("size must be known when the sizer succeeds")
|
|
}
|
|
if est.DataSizeBytes != twoGiB {
|
|
t.Fatalf("DataSizeBytes = %d, want %d (WRONG would be 0 — the F-A bug)", est.DataSizeBytes, twoGiB)
|
|
}
|
|
if !strings.Contains(est.DataSizeHuman, "GB") {
|
|
t.Fatalf("DataSizeHuman = %q, want GB-scale (WRONG would be \"3.6 KB\")", est.DataSizeHuman)
|
|
}
|
|
}
|
|
|
|
// Scenario G: a failed volume read must never render as "fits". Size is marked unknown, the human
|
|
// string says so, and FitsOnDest is forced false.
|
|
func TestEstimate_VolumeSize_FailureNeverFits(t *testing.T) {
|
|
orig := volumeSizer
|
|
volumeSizer = func(vol string) (int64, error) { return 0, errors.New("docker: no such image") }
|
|
defer func() { volumeSizer = orig }()
|
|
|
|
e := newEstimator(t, &rtProvider{stackDir: t.TempDir(), volumes: []string{"app_data"}})
|
|
est, err := e.EstimateExport("app", t.TempDir())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !est.SizeUnknown {
|
|
t.Fatalf("a failed volume read must set SizeUnknown")
|
|
}
|
|
if est.FitsOnDest {
|
|
t.Fatalf("an unknown size must NEVER render as fits_on_dest:true")
|
|
}
|
|
if est.DataSizeHuman != "ismeretlen méret" {
|
|
t.Fatalf("DataSizeHuman = %q, want \"ismeretlen méret\"", est.DataSizeHuman)
|
|
}
|
|
if est.DataSizeBytes != 0 {
|
|
t.Fatalf("no successful read → DataSizeBytes should be 0, got %d", est.DataSizeBytes)
|
|
}
|
|
}
|
|
|
|
// Scenario H (regression): an HDD-backed stack must NOT touch the new volume sizer — the HDD branch
|
|
// (duBytes on the mounted /mnt path) is unchanged. Platform-independent: assert the seam is not
|
|
// invoked and SizeUnknown stays false.
|
|
func TestEstimate_HDDPath_DoesNotUseVolumeSizer(t *testing.T) {
|
|
called := false
|
|
orig := volumeSizer
|
|
volumeSizer = func(vol string) (int64, error) { called = true; return 0, nil }
|
|
defer func() { volumeSizer = orig }()
|
|
|
|
p := &hddProvider{rtProvider: &rtProvider{stackDir: t.TempDir()}, mounts: []string{t.TempDir()}}
|
|
e := newEstimator(t, p)
|
|
est, err := e.EstimateExport("app", t.TempDir())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if called {
|
|
t.Fatalf("HDD-backed stack must not call the docker volume sizer")
|
|
}
|
|
if est.SizeUnknown {
|
|
t.Fatalf("HDD branch must not set SizeUnknown")
|
|
}
|
|
}
|