0626b9fc8a
gates / gates (push) Successful in 46s
Tier-2 and the one-copy notice ask the host agent (POST /disks/same-disk, agent 0.157.0): different + proven -> made and counted; same disk -> not made (D1); unknown (VM, hidden USB serial) -> made, not counted, the row says why (D2); a NAS never counts and no longer hides the notice (R-1075). An older agent keeps the st_dev rule, logged once. D4: a data-folder or media app on a box with no drive is refused in words with a link to the drives page (R-1068's raw-error half). MinAgent 0.157.0. Claude-Session: https://claude.ai/code/session_012qRErfCoiTkvDK9N5XHbzb
77 lines
2.9 KiB
Go
77 lines
2.9 KiB
Go
package backup
|
|
|
|
import (
|
|
"io"
|
|
"log"
|
|
"path/filepath"
|
|
"testing"
|
|
|
|
"gitea.dooplex.hu/admin/felhom-controller/internal/config"
|
|
"gitea.dooplex.hu/admin/felhom-controller/internal/settings"
|
|
)
|
|
|
|
// F6: a volume-only app (no HDD_PATH → data on sys_drive) must now flow through the tier-2 run — it
|
|
// used to be skipped, leaving a single controller-level copy. COMPANION red-proof: restore the
|
|
// `GetStackHDDPath == "" { continue }` skip in RunAllTier2 → "actualbudget" is absent → this fails.
|
|
func TestRunAllTier2_IncludesVolumeOnlyApps(t *testing.T) {
|
|
cfg := &config.Config{}
|
|
cfg.Paths.SystemDataPath = filepath.Join(t.TempDir(), "sys")
|
|
fake := &volDumpFakeProvider{
|
|
stacks: []StackSummary{{Name: "nextcloud"}, {Name: "actualbudget"}},
|
|
hdd: map[string]string{"nextcloud": filepath.Join(t.TempDir(), "usb")}, // actualbudget = volume-only
|
|
}
|
|
m := &Manager{cfg: cfg, logger: log.New(io.Discard, "", 0),
|
|
systemDataPath: cfg.Paths.SystemDataPath, stackProvider: fake}
|
|
|
|
var processed []string
|
|
m.perAppTier2 = func(name string) error { processed = append(processed, name); return nil }
|
|
m.RunAllTier2()
|
|
|
|
var sawVolumeOnly bool
|
|
for _, p := range processed {
|
|
if p == "actualbudget" {
|
|
sawVolumeOnly = true
|
|
}
|
|
}
|
|
if !sawVolumeOnly {
|
|
t.Errorf("volume-only app 'actualbudget' was NOT included in the tier-2 run (F6): processed=%v", processed)
|
|
}
|
|
}
|
|
|
|
// F6: a single-drive box (no off-drive target) surfaces the honest single-copy notice; a box with an
|
|
// off-drive target does not (never a faked 3-2-1 guarantee).
|
|
func TestSingleCopyWarning_HonestOnSingleDrive(t *testing.T) {
|
|
// Single-drive: only the system drive, no schedulable off-drive paths.
|
|
m1, _ := newTestManager(t, "/srv/sys")
|
|
if got := m1.singleCopyWarning(); got != m1.note("note.backup.single_copy") {
|
|
t.Errorf("single-drive box must surface the honest notice, got %q", got)
|
|
}
|
|
|
|
// Multi-drive: an enrolled off-drive path → no warning.
|
|
m2, sett := newTestManager(t, "/srv/sys")
|
|
if err := sett.AddStoragePath(settings.StoragePath{Path: "/mnt/usb", Label: "USB", Schedulable: true}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if got := m2.singleCopyWarning(); got != "" {
|
|
t.Errorf("a box with an off-drive target must NOT show the single-copy notice, got %q", got)
|
|
}
|
|
}
|
|
|
|
// F6: a sys_drive (volume-only) app's restore point carries a clear drive label, never blank.
|
|
func TestRestorePoint_SysDriveLabelNotBlank(t *testing.T) {
|
|
cfg := &config.Config{}
|
|
sysDrive := t.TempDir()
|
|
cfg.Paths.SystemDataPath = sysDrive
|
|
fake := &volDumpFakeProvider{
|
|
stacks: []StackSummary{{Name: "actualbudget"}},
|
|
volumes: map[string][]string{"actualbudget": {"actualbudget_data"}},
|
|
}
|
|
m := &Manager{cfg: cfg, logger: log.New(io.Discard, "", 0),
|
|
systemDataPath: sysDrive, stackProvider: fake}
|
|
|
|
label := m.sysDriveLabelFor("actualbudget")
|
|
if label != systemDriveLabel {
|
|
t.Errorf("sys_drive app drive label = %q, want %q (never blank — F6)", label, systemDriveLabel)
|
|
}
|
|
}
|