Files
felhom-controller/controller/internal/backup/r690_removed_unit_test.go
T
admin 43e99d160c
gates / gates (push) Successful in 26s
kept data: the choice at reinstall, the list, the read-only view, the load (09 decision 36); R-690 fixed
An install over an app's kept drive folder (appdata/<app> non-empty) asks the household:
"use my kept data" (a load from the newest copy of THIS drive's install, own unit or
second-drive mirror, then the template's after_load) or "start fresh" (the folder is
renamed into <drive>/kept/<app>/<date>/ with the removed app's unit; nothing deleted).
The install API answers 409 kept_data_choice until one is chosen; DeployStack refuses
too. New page Megorzott adatok / Kept data (/kept-data): Load / Look / Delete (typed
confirmation, the only deletion of kept data). FileBrowser gets a read-only source.
The drive-full warning names the kept folders. <drive>/kept is protected and outside
every backup leg.

R-690: the removed-app restore (R-487) never found a unit on a DATA drive — it asked
GetStackComposePath (true for every catalog app) and restored nextcloud with no env.
Now isStackDeployed; pinned with a production-shaped provider.

Red-proofs: audits/night-2026-09-26/E/redproofs/.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
2026-09-25 13:32:30 +02:00

63 lines
2.6 KiB
Go

package backup
import (
"log"
"os"
"path/filepath"
"testing"
"time"
"gitea.dooplex.hu/admin/felhom-controller/internal/config"
"gitea.dooplex.hu/admin/felhom-controller/internal/settings"
)
// r690Provider answers GetStackComposePath the way PRODUCTION does (main.go stackAdapter): ok for
// every stack the box knows — deployed or not, because every catalog template is a stack — while
// ListDeployedStacks names only the deployed ones. The R-487 fake answered it for deployed apps
// only, which is why its test passed while the box restored nextcloud with no env (R-690).
type r690Provider struct{ floorProvider }
func (p *r690Provider) GetStackComposePath(name string) (string, bool) {
return filepath.Join(p.dir, "stacks", name, "docker-compose.yml"), true
}
func r690Manager(t *testing.T) (*Manager, *settings.Settings, string) {
t.Helper()
sett, err := settings.Load(filepath.Join(t.TempDir(), "settings.json"), log.New(os.Stderr, "", 0))
if err != nil {
t.Fatal(err)
}
sys := t.TempDir()
cfg := &config.Config{}
cfg.Paths.SystemDataPath = sys
m := NewManager(cfg, sett, log.New(os.Stderr, "", 0))
m.SetStackProvider(&r690Provider{floorProvider{stacks: []string{"still-here"}, dir: t.TempDir()}})
drive := t.TempDir()
if err := sett.AddStoragePath(settings.StoragePath{Path: drive, Label: "HDD", Schedulable: true}); err != nil {
t.Fatal(err)
}
return m, sett, drive
}
// The consequence, not the mechanism: the unit the restore OPENS and the copy the picker OFFERS are
// the removed app's unit on the data drive, while its catalog stack still exists.
// COMPANION RED-PROOF: put GetStackComposePath back as the "removed?" test in primaryUnitDirFor →
// this fails naming the system-drive path; in ListRestorePoints → the picker offers 0 copies.
func TestR690_RemovedUnitFoundWhenTheStackStillExists(t *testing.T) {
m, _, drive := r690Manager(t)
want := writeR487Unit(t, m.namespaceRoot(drive), "nextcloud", "Nextcloud", time.Now())
if got := m.primaryUnitDirFor("nextcloud"); got != want {
t.Fatalf("the restore opens %s, want the kept unit %s on the data drive", got, want)
}
pts, found := m.ListRestorePoints("nextcloud")
if !found || len(pts) != 1 || pts[0].DriveLabel != "HDD" {
t.Fatalf("the picker offers %+v (found=%v), want the one kept copy on HDD", pts, found)
}
// Negative control: a DEPLOYED app is never redirected to a removed-app unit.
writeR487Unit(t, m.namespaceRoot(drive), "still-here", "Still", time.Now())
if got := m.primaryUnitDirFor("still-here"); got == RecoveryUnitPath(m.namespaceRoot(drive), "still-here") {
t.Fatalf("a deployed app was sent to the removed-app unit %s", got)
}
}