Files
felhom-controller/controller/internal/backup/driveready_test.go
T
2026-10-07 18:45:01 +02:00

79 lines
3.2 KiB
Go

package backup
import (
"testing"
"time"
)
// drivePathProvider puts every app on /mnt/felhom-drives/hdd_1 (the measured R-897 case).
type drivePathProvider struct{ unitFailProvider }
func (p *drivePathProvider) GetStackHDDPath(string) string { return "/mnt/felhom-drives/hdd_1" }
// R-897: right after a boot, a capture for an app on a drive waits until the drive's bind is LIVE. The drive reads
// not-bound, then bound → the capture (here a capture that fails, so it is visible as a unit event) starts only after.
// COMPANION RED-PROOF (observed): remove the driveReady call in captureAllRecoveryUnits → "captured while the drive
// was not bound".
func TestDriveReady_RefreshWaitsForTheBindThenCaptures(t *testing.T) {
m, got := newUnitNotifyManager(t, []string{"calibre-web"}, map[string]bool{"calibre-web": true})
base := m.stackProvider.(*unitFailProvider)
m.stackProvider = &drivePathProvider{*base}
now := time.Date(2026, 10, 7, 15, 16, 28, 0, time.UTC)
m.startedAt, m.nowFn = now.Add(-20*time.Second), func() time.Time { return now }
bound := false
var asked []string
m.driveLive = func(root string) bool { asked = append(asked, root); return bound }
m.captureAllRecoveryUnits(false)
if len(*got) != 0 {
t.Fatalf("captured while the drive was not bound: %+v", *got)
}
if len(asked) == 0 || asked[0] != "/mnt/felhom-drives/hdd_1" {
t.Fatalf("the drive root asked = %v", asked)
}
bound = true
m.captureAllRecoveryUnits(false)
if len(*got) != 1 {
t.Fatalf("after the bind the capture must run: %+v", *got)
}
}
// After the window the capture runs whatever the bind says (and says so).
func TestDriveReady_AfterTheWindowItRunsAnyway(t *testing.T) {
m, got := newUnitNotifyManager(t, []string{"calibre-web"}, map[string]bool{"calibre-web": true})
base := m.stackProvider.(*unitFailProvider)
m.stackProvider = &drivePathProvider{*base}
now := time.Date(2026, 10, 7, 15, 30, 0, 0, time.UTC)
m.startedAt, m.nowFn = now.Add(-DriveWaitWindow), func() time.Time { return now }
m.driveLive = func(string) bool { return false }
m.captureAllRecoveryUnits(false)
if len(*got) != 1 {
t.Fatalf("after %s the capture must run anyway: %+v", DriveWaitWindow, *got)
}
}
// A data run WAITS (polls) instead of skipping; a non-drive path and a Manager without a start time never wait.
func TestDriveReady_DataRunPollsAndOthersNeverWait(t *testing.T) {
m, _ := newUnitNotifyManager(t, nil, nil)
now := time.Date(2026, 10, 7, 15, 16, 28, 0, time.UTC)
m.startedAt, m.nowFn = now, func() time.Time { return now }
n := 0
m.driveLive = func(string) bool { n++; return n >= 3 }
polls := 0
m.driveWaitPoll = func(d time.Duration) { polls++; now = now.Add(d) }
if !m.driveReady("/mnt/felhom-drives/hdd_1/x", true) || polls != 2 {
t.Fatalf("data run: polls=%d", polls)
}
if !m.driveReady("/var/lib/felhom/system-data", false) {
t.Fatal("the system SSD never waits")
}
m2, _ := newUnitNotifyManager(t, nil, nil)
m2.driveLive = func(string) bool { return false }
if !m2.driveReady("/mnt/felhom-drives/hdd_1", false) {
t.Fatal("no start time → no wait")
}
if driveRootOf("/mnt/felhom-drives/") != "" || driveRootOf("/mnt/felhom-drives/hdd_2/a/b") != "/mnt/felhom-drives/hdd_2" {
t.Fatal("driveRootOf")
}
}