package backup import ( "context" "os" "path/filepath" "strings" "testing" "time" "gitea.dooplex.hu/admin/felhom-controller/internal/settings" ) // R-477 — the update's Tier-3 lookup is ONE snapshots call. The control proves the fake runner does // see stats calls when something makes them (the inventory page), so a zero is a measurement. // // COMPANION RED-PROOF (REPORT.md): make OffsiteSnapshotTimes read OffsiteInventoryList — this fails. func TestR477_TheUpdateOffsiteLookupRunsNoStats(t *testing.T) { m, _ := newOffboxManager(t) var calls []string m.SetOffboxRunner(func(_ context.Context, _ []string, args ...string) ([]byte, error) { calls = append(calls, strings.Join(args, " ")) if contains(args, "snapshots") { return []byte(`[{"short_id":"a1","time":"2026-09-13T01:00:00Z","tags":["gokapi"]},{"short_id":"b2","time":"2026-09-13T02:00:00Z","tags":["kimai"]}]`), nil } return []byte(`{"total_size":1024,"total_file_count":1}`), nil }) m.updateTier2PointFn, m.updateTier1PointsFn = noTier2, noTier1 count := func(word string) int { n := 0 for _, c := range calls { if strings.Contains(" "+c+" ", " "+word+" ") { n++ } } return n } p, ok, _ := m.UpdateRestorePoints(context.Background(), "gokapi", nil) if !ok || p.Tier != UpdateTierOffsite || !p.At.Equal(time.Date(2026, 9, 13, 1, 0, 0, 0, time.UTC)) { t.Fatalf("want gokapi's off-site snapshot; got %+v ok=%v", p, ok) } if count("snapshots") != 1 || count("stats") != 0 { t.Errorf("the update lookup must be one snapshots call and no stats; calls=%v", calls) } calls = nil if _, err := m.OffsiteInventoryList(context.Background()); err != nil { t.Fatal(err) } if count("stats") == 0 { t.Fatalf("control: the inventory DOES run stats, so the fake must see them; calls=%v", calls) } } // R-474 — only the app's own Tier-2 mirror is found and removed. func TestR474_OnlyTheAppsOwnMirrorIsRemoved(t *testing.T) { m, sett := newOffboxManager(t) drive := t.TempDir() if err := sett.AddStoragePath(settings.StoragePath{Path: drive, Label: "HDD", Schedulable: true}); err != nil { t.Fatal(err) } root := NamespaceRootFor(drive, m.systemDataPath) sec := filepath.Join(root, "backups", "secondary") mine, other, shares := filepath.Join(sec, "gokapi"), filepath.Join(sec, "kimai"), filepath.Join(sec, SharesPseudoStack) for _, d := range []string{mine, other, shares} { if err := os.MkdirAll(filepath.Join(d, "recovery-unit"), 0o755); err != nil { t.Fatal(err) } if err := os.WriteFile(filepath.Join(d, "recovery-unit", "manifest.json"), []byte("{}"), 0o644); err != nil { t.Fatal(err) } } dirs := m.Tier2MirrorDirsForApp("gokapi") if len(dirs) != 1 || dirs[0] != mine { t.Fatalf("mirror dirs for gokapi = %v, want [%s]", dirs, mine) } removed := m.RemoveTier2Mirrors("gokapi", append(dirs, other, shares, filepath.Join(sec, "gokapi", "..", "kimai"))) if len(removed) != 1 { t.Errorf("exactly the app's own mirror must be removed, got %v", removed) } if _, err := os.Stat(mine); !os.IsNotExist(err) { t.Error("gokapi's mirror must be gone") } for _, keep := range []string{other, shares} { if _, err := os.Stat(keep); err != nil { t.Errorf("%s must survive: %v", keep, err) } } if m.Tier2MirrorDirsForApp("../kimai") != nil || m.RemoveTier2Mirrors(SharesPseudoStack, []string{shares}) != nil { t.Error("an unsafe or shares name must find and remove nothing") } } func TestR474_DeleteAppBackupPrefsForgetsOnlyThatApp(t *testing.T) { _, sett := newOffboxManager(t) _ = sett.SetAppOffbox("gokapi", true) _ = sett.SetCrossDriveConfig("gokapi", &settings.CrossDriveBackup{DestinationPath: "/mnt/x"}) _ = sett.SetAppOffbox("kimai", true) if err := sett.DeleteAppBackupPrefs("gokapi"); err != nil { t.Fatal(err) } if sett.IsAppOffbox("gokapi") || sett.GetCrossDriveConfig("gokapi") != nil { t.Error("gokapi's prefs must be forgotten") } if !sett.IsAppOffbox("kimai") { t.Error("another app's prefs must stay") } if err := sett.DeleteAppBackupPrefs("never-there"); err != nil { t.Errorf("deleting absent prefs is a no-op, got %v", err) } }