3603d1fc7f
tier2_capture.go: classified apps get TierSecondary per-bind legs (paperless copy shrinks — export
drops); legacy apps keep the byte-identical resolver set. v2 relpath-mirroring layout
(backups/secondary/<stack>/{marker LAST, recovery-unit/, hdd/<rel>/, userdata/<rel>/}); N>1 native
(errTier2MultiDir/tier2AppDataName deleted). Migration=delete-and-rebuild + reconcile; all RemoveAll
via tier2SafeRemove (refuses outside backups/secondary/). SSD=state-only tier. selectTier2Target
never picks network storage (pinned+auto, F-6C-1). Restore reads v2 behind a marker gate.
Part 0: offbox_enlarge_blocked is a persisted one-time Load seed (opt-out sticks), not a getter
append. Part 0.5: offsite restore scratch prefers a local (non-network) path.
Full v2 test suite + all 10 §10 red-proofs verified. Destructive writes bounded to backups/secondary/.
67 lines
2.3 KiB
Go
67 lines
2.3 KiB
Go
package backup
|
|
|
|
import (
|
|
"bytes"
|
|
"log"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
|
|
"gitea.dooplex.hu/admin/felhom-controller/internal/settings"
|
|
)
|
|
|
|
// Part 0.5 (F-3afix-1): the offsite restore scratch must prefer a LOCAL path — a network (squashed)
|
|
// scratch would feed PlaceOffsiteRestore wrong-owner files. A network app-drive is skipped when a
|
|
// local candidate exists; a network-only environment is a last resort WITH a loud WARN.
|
|
func TestOffboxScratchDir_PrefersLocal(t *testing.T) {
|
|
m, sett := newOffboxManager(t)
|
|
prov := &offbox3aProvider{hdd: map[string]string{}, binds: map[string][]ClassifiedBind{}, has: map[string]bool{}}
|
|
m.SetStackProvider(prov)
|
|
|
|
nas := "/mnt/nas"
|
|
local := t.TempDir()
|
|
if err := sett.AddStoragePath(settings.StoragePath{Path: nas, Label: "nas", Schedulable: true, Kind: settings.StorageKindNetwork, Protocol: "nfs"}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := sett.AddStoragePath(settings.StoragePath{Path: local, Label: "local", Schedulable: true}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
prov.hdd["immich"] = nas // the app lives ON the NAS
|
|
|
|
_, nsRoot, err := m.offboxRestoreScratchDir("immich")
|
|
if err != nil {
|
|
t.Fatalf("scratchDir: %v", err)
|
|
}
|
|
if strings.Contains(nsRoot, "nas") {
|
|
t.Errorf("network app-drive must not be the scratch — got %q, want the local drive", nsRoot)
|
|
}
|
|
if !strings.HasPrefix(nsRoot, filepath.Clean(local)) {
|
|
t.Errorf("scratch nsRoot = %q, want under the local drive %q", nsRoot, local)
|
|
}
|
|
}
|
|
|
|
func TestOffboxScratchDir_NetworkOnlyLastResortWarns(t *testing.T) {
|
|
var buf bytes.Buffer
|
|
m, sett := newOffboxManager(t)
|
|
m.logger = log.New(&buf, "", 0)
|
|
prov := &offbox3aProvider{hdd: map[string]string{}, binds: map[string][]ClassifiedBind{}, has: map[string]bool{}}
|
|
m.SetStackProvider(prov)
|
|
|
|
nas := "/mnt/nas"
|
|
if err := sett.AddStoragePath(settings.StoragePath{Path: nas, Label: "nas", Schedulable: true, Kind: settings.StorageKindNetwork, Protocol: "nfs"}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
prov.hdd["immich"] = nas
|
|
|
|
_, nsRoot, err := m.offboxRestoreScratchDir("immich")
|
|
if err != nil {
|
|
t.Fatalf("scratchDir: %v", err)
|
|
}
|
|
if !strings.Contains(nsRoot, "nas") {
|
|
t.Errorf("network-only → the NAS is the last resort, got %q", nsRoot)
|
|
}
|
|
if !strings.Contains(buf.String(), "ownership fidelity not guaranteed") {
|
|
t.Errorf("last-resort network scratch must log a loud WARN, got: %s", buf.String())
|
|
}
|
|
}
|