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