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

78 lines
3.5 KiB
Go

package backup
import (
"io/fs"
"os"
"path/filepath"
"strings"
"syscall"
"testing"
"gitea.dooplex.hu/admin/felhom-controller/internal/settings"
"gitea.dooplex.hu/admin/felhom-controller/internal/util"
)
// R-362: a data drive unbound 4 s into a scratch restore was reported to the household as
// „restore dir: mkdir /mnt/felhom-drives/hdd_1/backups: permission denied" — a correct refusal that
// misdescribed why. The consequence asserted: what the household reads (util.ErrText, the renderer
// the restore notice uses) names the DRIVE, in both languages, and no longer says "permission
// denied" — while a genuine permission problem on a drive that IS mounted keeps its own wording.
func TestR362_DetachedDriveIsNamed(t *testing.T) {
m, sett := newOffboxManager(t)
managed := filepath.Join(settings.NetworkMountRoot, "hdd_1")
legacy := "/mnt/hdd_legacy"
for _, sp := range []settings.StoragePath{
{Path: managed, Label: "Kulso HDD"},
{Path: legacy, Label: "Regi HDD", Disconnected: true},
} {
if err := sett.AddStoragePath(sp); err != nil {
t.Fatal(err)
}
}
mounted := false
m.driveMountedFn = func(string) bool { return mounted }
eacces := func(p string) error { return &fs.PathError{Op: "mkdir", Path: p, Err: syscall.EACCES} }
scratch := filepath.Join(managed, "backups", "offsite-restore", "app")
// 1. The managed drive is no longer a mount point (the flag has not caught up yet).
err := m.restoreDirError(scratch, eacces(filepath.Join(managed, "backups")))
hu, en := util.ErrText("hu", err), util.ErrText("en", err)
if strings.Contains(hu, "permission denied") || !strings.Contains(hu, "Kulso HDD") || !strings.Contains(hu, "nem érhető el") {
t.Errorf("R-362: the Hungarian refusal does not name the missing drive: %q", hu)
}
if strings.Contains(en, "permission denied") || !strings.Contains(en, "Kulso HDD") || !strings.Contains(en, "cannot be reached") {
t.Errorf("R-362: the English refusal does not name the missing drive: %q", en)
}
// 2. A legacy path outside the managed parent, flagged disconnected by the drive gate.
if got := util.ErrText("en", m.restoreDirError(filepath.Join(legacy, "x"), eacces(legacy))); !strings.Contains(got, "Regi HDD") {
t.Errorf("R-362: a drive the registry marks disconnected is not named: %q", got)
}
// 3. Negative control: the drive IS mounted, so a permission error is a real permission error.
mounted = true
if got := util.ErrText("en", m.restoreDirError(scratch, eacces(scratch))); !strings.Contains(got, "permission denied") {
t.Errorf("a genuine permission error on a mounted drive must keep its wording, got %q", got)
}
// 4. Negative control: a different failure class is never re-labelled.
mounted = false
if got := util.ErrText("en", m.restoreDirError(scratch, &fs.PathError{Op: "mkdir", Path: scratch, Err: syscall.EIO})); strings.Contains(got, "cannot be reached") {
t.Errorf("an I/O error was re-labelled as a missing drive: %q", got)
}
// Wiring: every restore that creates its directory goes through restoreDirError — no bare
// "restore dir: %w" is left at a call site.
for _, f := range []string{"offbox_restore.go", "offbox.go", "shares_restore.go"} {
src, rerr := os.ReadFile(f)
if rerr != nil {
t.Fatal(rerr)
}
if strings.Contains(string(src), `fmt.Errorf("restore dir: %w", err)`) {
t.Errorf("R-362: %s still returns the raw restore-dir error", f)
}
if !strings.Contains(string(src), "m.restoreDirError(") {
t.Errorf("R-362: %s does not call restoreDirError", f)
}
}
}