controller v0.267.0: tests off DooPlex's Docker, cut-off copies refused, two pages true
gates / gates (push) Successful in 26s

R-650: internal/dockerexec — every docker exec routed through it; under
go test a real docker is refused (opt-in FELHOM_TEST_REAL_DOCKER=1; a stub
under the temp dir is allowed). api/stacks/web tests run under a silent
stub (TestMain). TestR650_NoBareDockerExec pins it repo-wide.
R-640: a dump without its engine's completion marker is refused before
the first mutation (unit + off-site restore) and again before any load.
R-499: the Tier-2 page's system-disk sentence has four true branches.
R-518: the backup button states the measured ~8 min stop.
R-626: measured on 9202, not reproduced.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
This commit is contained in:
2026-09-23 20:25:28 +02:00
parent 15e630aa02
commit 80e6ad8c47
55 changed files with 2510 additions and 125 deletions
@@ -0,0 +1,105 @@
package web
import (
"context"
"errors"
"io"
"log"
"net/http/httptest"
"os"
"path/filepath"
"strings"
"testing"
"gitea.dooplex.hu/admin/felhom-controller/internal/agentapi"
"gitea.dooplex.hu/admin/felhom-controller/internal/backup"
"gitea.dooplex.hu/admin/felhom-controller/internal/config"
"gitea.dooplex.hu/admin/felhom-controller/internal/settings"
"gitea.dooplex.hu/admin/felhom-controller/internal/stacks"
)
// R-499 — an app on the system disk was told „már szerepelnek a teljes rendszermentésben (PBS)" and
// „nincs külön teendő" on every box, including one whose only whole-system copy sat on the SAME disk
// (measured 2026-09-14). The page must now say what is true for THIS box.
// tier2PageServer builds a real Server with one deployed, driveless app ("privatebin") and the
// agent's tier/disk answers injected through the production seams, then renders the real handler.
func tier2PageServer(t *testing.T, tiers func(context.Context) (agentapi.TiersResponse, error), disks []agentapi.DiskInfo) string {
t.Helper()
lg := log.New(io.Discard, "", 0)
dir := t.TempDir()
cfg := &config.Config{}
cfg.Paths.StacksDir = filepath.Join(dir, "stacks")
cfg.Paths.DataDir = filepath.Join(dir, "data")
cfg.Paths.SystemDataPath = filepath.Join(dir, "system")
cfg.Stacks.ComposeCommand = "docker compose"
cfg.Backup.Enabled = true
app := filepath.Join(cfg.Paths.StacksDir, "privatebin")
if err := os.MkdirAll(app, 0o755); err != nil {
t.Fatal(err)
}
os.WriteFile(filepath.Join(app, "docker-compose.yml"), []byte("services:\n privatebin:\n image: privatebin/pdo:2.0.6\n"), 0o644)
os.WriteFile(filepath.Join(app, "app.yaml"), []byte("deployed: true\n"), 0o600)
sett, err := settings.Load(filepath.Join(dir, "settings.json"), lg)
if err != nil {
t.Fatal(err)
}
sm, err := stacks.NewManager(cfg, lg)
if err != nil {
t.Fatal(err)
}
if err := sm.ScanStacks(); err != nil {
t.Fatal(err)
}
bm := backup.NewManager(cfg, sett, lg)
bm.SetStackProvider(&blockProvider{hdd: ""}) // driveless: IsHDDApp false
s := &Server{cfg: cfg, settings: sett, stackMgr: sm, backupMgr: bm, logger: lg, version: "test"}
s.tiersFn = tiers
s.disksFn = func(context.Context) (agentapi.DisksResponse, error) { return agentapi.DisksResponse{Disks: disks}, nil }
s.loadTemplates()
w := httptest.NewRecorder()
s.tier2ConfigPageHandler(w, httptest.NewRequest("GET", "/stacks/privatebin/backup", nil), "privatebin")
if w.Code != 200 {
t.Fatalf("page answered %d", w.Code)
}
return w.Body.String()
}
func primaryTier(target string) func(context.Context) (agentapi.TiersResponse, error) {
return func(context.Context) (agentapi.TiersResponse, error) {
return agentapi.TiersResponse{Tiers: []agentapi.BackupTierInfo{{Target: target, Primary: true}}}, nil
}
}
// COMPANION RED-PROOF: rendering the old sentence for every branch (the pre-fix template) makes the
// same_disk case fail on `promises the backup protects this app`.
func TestR499_Tier2PageSaysWhatIsTrueForThisBox(t *testing.T) {
ownDrive := agentapi.DiskInfo{Name: "mentes", MountPath: "/mnt/mentes", GuestPath: "/mnt/felhom-drives/mentes", Role: "user-data", BackupTarget: true}
cases := []struct {
name, want string
tiers func(context.Context) (agentapi.TiersResponse, error)
disks []agentapi.DiskInfo
}{
{"same disk (the measured box)", "same_disk", primaryTier("local"), nil},
{"own drive", "protected", primaryTier("felhom-backup"), []agentapi.DiskInfo{ownDrive}},
{"configured drive gone", "absent", primaryTier("felhom-backup"), nil},
{"agent not askable", "unknown", func(context.Context) (agentapi.TiersResponse, error) { return agentapi.TiersResponse{}, errors.New("down") }, nil},
}
for _, c := range cases {
body := tier2PageServer(t, c.tiers, c.disks)
if !strings.Contains(body, `data-system-backup="`+c.want+`"`) {
t.Errorf("%s: want branch %q on the page", c.name, c.want)
}
// ASCII fragments (ui-hungarian rule): "(PBS)" and "nincs k" of „nincs külön teendő".
if strings.Contains(body, "(PBS)") {
t.Errorf("%s: the page still names PBS, which this box may not have", c.name)
}
promises := strings.Contains(body, "nincs k")
if c.want == "protected" && !promises {
t.Errorf("%s: control — the protected branch must still say there is nothing to do", c.name)
}
if c.want != "protected" && promises {
t.Errorf("%s: promises the backup protects this app (\"nothing to do\") on a box where it may not", c.name)
}
}
}