Files
felhom-controller/controller/internal/backup/f5_stale_primary_test.go
T

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