68b3a3932e
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017CDMFpFx84pfviCTVuGGhf
108 lines
3.9 KiB
Go
108 lines
3.9 KiB
Go
package backup
|
|
|
|
import (
|
|
"io"
|
|
"log"
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
|
|
"gitea.dooplex.hu/admin/felhom-controller/internal/config"
|
|
"gitea.dooplex.hu/admin/felhom-controller/internal/settings"
|
|
)
|
|
|
|
// f5Manager wires a Manager with a real settings store holding the given enrolled drives, plus a
|
|
// sys drive, and a fake provider mapping each deployed app to its CURRENT HDD path.
|
|
func f5Manager(t *testing.T, sysDrive string, enrolled []string, deployed map[string]string) *Manager {
|
|
t.Helper()
|
|
sett, err := settings.Load(filepath.Join(t.TempDir(), "settings.json"), log.New(io.Discard, "", 0))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for _, d := range enrolled {
|
|
if err := sett.AddStoragePath(settings.StoragePath{Path: d, Label: filepath.Base(d), Schedulable: true}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
var stacks []StackSummary
|
|
for name := range deployed {
|
|
stacks = append(stacks, StackSummary{Name: name})
|
|
}
|
|
cfg := &config.Config{}
|
|
cfg.Paths.SystemDataPath = sysDrive
|
|
fake := &volDumpFakeProvider{stacks: stacks, hdd: deployed}
|
|
return &Manager{cfg: cfg, settings: sett, logger: log.New(io.Discard, "", 0),
|
|
systemDataPath: sysDrive, stackProvider: fake}
|
|
}
|
|
|
|
// seedPrimaryDir creates a backups/primary/<app> dir (with a marker file) under a drive's namespace.
|
|
func seedPrimaryDir(t *testing.T, driveNsRoot, app string) string {
|
|
t.Helper()
|
|
dir := RecoveryUnitPath(driveNsRoot, app)
|
|
if err := os.MkdirAll(filepath.Join(dir, "volume-dumps"), 0755); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := os.WriteFile(filepath.Join(dir, "manifest.json"), []byte("{}"), 0644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return dir
|
|
}
|
|
|
|
// F5: an app redeployed from drive A to drive B → its stale primary dir on A is removed, its live
|
|
// dir on B is kept. Guard red-proof lives in the two "kept" assertions.
|
|
func TestPruneStalePrimaryDirs_RemovesRedeployedResidue(t *testing.T) {
|
|
driveA := t.TempDir()
|
|
driveB := t.TempDir()
|
|
sys := filepath.Join(t.TempDir(), "sys")
|
|
// nextcloud currently on B; a stale unit remains on A.
|
|
m := f5Manager(t, sys, []string{driveA, driveB}, map[string]string{"nextcloud": driveB})
|
|
|
|
nsA := NamespaceRoot(driveA, true)
|
|
nsB := NamespaceRoot(driveB, true)
|
|
staleA := seedPrimaryDir(t, nsA, "nextcloud")
|
|
liveB := seedPrimaryDir(t, nsB, "nextcloud")
|
|
|
|
m.pruneStalePrimaryDirs()
|
|
|
|
if _, err := os.Stat(staleA); !os.IsNotExist(err) {
|
|
t.Errorf("stale primary dir on the OLD drive was not removed (F5)")
|
|
}
|
|
if _, err := os.Stat(liveB); err != nil {
|
|
t.Errorf("the LIVE primary dir on the current drive was wrongly removed: %v", err)
|
|
}
|
|
}
|
|
|
|
// GUARD: an UNDEPLOYED app's primary dir is a restore point — it must be KEPT (companion: drop the
|
|
// `!deployed { continue }` guard → this dir is deleted → fail).
|
|
func TestPruneStalePrimaryDirs_KeepsUndeployedRestorePoint(t *testing.T) {
|
|
driveA := t.TempDir()
|
|
sys := filepath.Join(t.TempDir(), "sys")
|
|
// Only "nextcloud" is deployed (on A); "removed-app" has a leftover unit but is NOT deployed.
|
|
m := f5Manager(t, sys, []string{driveA}, map[string]string{"nextcloud": driveA})
|
|
nsA := NamespaceRoot(driveA, true)
|
|
seedPrimaryDir(t, nsA, "nextcloud")
|
|
orphan := seedPrimaryDir(t, nsA, "removed-app")
|
|
|
|
m.pruneStalePrimaryDirs()
|
|
|
|
if _, err := os.Stat(orphan); err != nil {
|
|
t.Errorf("an UNDEPLOYED app's restore point was wrongly deleted (guard failure): %v", err)
|
|
}
|
|
}
|
|
|
|
// GUARD: the dir on an app's CURRENT drive is never touched (already covered above, but assert it
|
|
// directly with a single-drive app so no cross-drive move is involved).
|
|
func TestPruneStalePrimaryDirs_KeepsCurrentDriveDir(t *testing.T) {
|
|
driveA := t.TempDir()
|
|
sys := filepath.Join(t.TempDir(), "sys")
|
|
m := f5Manager(t, sys, []string{driveA}, map[string]string{"nextcloud": driveA})
|
|
nsA := NamespaceRoot(driveA, true)
|
|
live := seedPrimaryDir(t, nsA, "nextcloud")
|
|
|
|
m.pruneStalePrimaryDirs()
|
|
|
|
if _, err := os.Stat(live); err != nil {
|
|
t.Errorf("the current-drive primary dir was wrongly removed: %v", err)
|
|
}
|
|
}
|