controller v0.72.0: FileBrowser converges on boot-recreate

processGuestBootChange recreated the drive-backed app stacks but never re-synced
FileBrowser (base-infra, no HDD_PATH), so its drive mounts went stale after a
reboot. Now, AFTER pollLiveBinds confirms the live binds and the apps are
recreated, trigger go s.SyncFileBrowserMounts() so FileBrowser converges against
the now-live drives. Refactored into pure recreateDriveBackedApps(stacks, present,
recreate, syncFB). Tests: FB sync runs once after recreate (red-proofed companion);
runs even when nothing recreated.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-16 17:37:12 +02:00
parent 14f200c9a1
commit 6ea25388d7
3 changed files with 92 additions and 11 deletions
@@ -139,6 +139,43 @@ func TestPollLiveBinds_TimeoutLeavesAbsent(t *testing.T) {
}
}
// TestRecreateDriveBackedApps_SyncsFileBrowserAfterRecreate (Task B): FileBrowser must be re-synced
// AFTER the drive-backed apps are recreated (so it syncs against live binds). COMPANION: the pre-fix
// boot-recreate path never synced FileBrowser — red-proofed by dropping the syncFB() call.
func TestRecreateDriveBackedApps_SyncsFileBrowserAfterRecreate(t *testing.T) {
flash := "/mnt/felhom-drives/felhom-flash"
present := map[string]bool{flash: true}
stacks := []bootStack{
{name: "romm", deployed: true, hdd: flash}, // drive-backed, live → recreate
{name: "actualbudget", deployed: true, hdd: "/mnt/sys_drive/felhom-data"}, // SSD → not recreated
{name: "stranded", deployed: true, hdd: "/mnt/felhom-drives/felhom-usb"}, // drive-backed, bind NOT live → skipped
}
var seq []string
recreate := func(bs bootStack) { seq = append(seq, "recreate:"+bs.name) }
syncFB := func() { seq = append(seq, "syncFB") }
recreated, skipped := recreateDriveBackedApps(stacks, present, recreate, syncFB)
if recreated != 1 || skipped != 1 {
t.Fatalf("recreated=%d skipped=%d, want 1/1", recreated, skipped)
}
// FileBrowser sync MUST be invoked, and AFTER every recreate.
if len(seq) != 2 || seq[0] != "recreate:romm" || seq[len(seq)-1] != "syncFB" {
t.Fatalf("FileBrowser sync must run once, AFTER the recreate; seq=%v", seq)
}
}
// TestRecreateDriveBackedApps_SyncsEvenWithNoRecreate: FileBrowser is re-synced to reflect the live
// binds even when no app needed recreating (so its mounts never go stale).
func TestRecreateDriveBackedApps_SyncsEvenWithNoRecreate(t *testing.T) {
stacks := []bootStack{{name: "x", deployed: true, hdd: "/mnt/sys_drive/felhom-data"}} // SSD only
synced := false
recreateDriveBackedApps(stacks, map[string]bool{}, func(bootStack) { t.Fatal("should not recreate") }, func() { synced = true })
if !synced {
t.Fatal("FileBrowser sync must run even when nothing was recreated")
}
}
func TestStablePathForName(t *testing.T) {
if got := stablePathForName("felhom-usb"); got != "/mnt/felhom-drives/felhom-usb" {
t.Errorf("stablePathForName = %q", got)