Files
felhom-controller/controller/internal/backup/r479_tier_order_test.go
T
admin 3e813307cc
gates / gates (push) Successful in 13s
controller v0.241.0: a bind-data app leans on off-site before its own unit; the hold names what the copy holds (R-479)
Operator ruling 2026-09-13. An app with classified binds walks second
drive -> off-site -> own unit (its unit holds no files); volume apps keep
2 -> 1 -> 3. RestoreHold.CopyHolds records what the chosen copy holds and
the sentence ends with it; older holds keep their tier-only sentence.
Tests on both halves; red-proof: a layout-blind order fails the bind case.
2026-09-13 21:47:33 +02:00

97 lines
3.7 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package backup
import (
"context"
"fmt"
"gitea.dooplex.hu/admin/felhom-controller/internal/appbackup"
"io"
"log"
"strings"
"testing"
"time"
)
// R-479 (operator ruling 2026-09-13) — for an app whose data is bind-mounted files, the tiers are
// walked second drive → off-site → own unit, and the hold sentence says what the chosen copy holds.
func r479Manager(t *testing.T, bindApp bool) *Manager {
t.Helper()
m, _ := newOffboxManager(t)
prov := &offbox3aProvider{hdd: map[string]string{}, binds: map[string][]ClassifiedBind{}, has: map[string]bool{}, deployed: map[string]bool{"app": true}}
if bindApp {
prov.hdd["app"] = t.TempDir()
prov.binds["app"] = []ClassifiedBind{{ComposeBind: appbackup.ComposeBind{RelPath: "appdata/app"}, Class: ClassMandatory}}
prov.has["app"] = true
}
m.SetStackProvider(prov)
return m
}
// COMPANION RED-PROOF (REPORT.md): make UpdateTierOrderFor always return updateTierOrder — the
// bind-app case then picks the own unit ahead of off-site and this fails.
func TestR479_BindDataAppWalksSecondDriveOffsiteThenOwnUnit(t *testing.T) {
for _, tc := range []struct {
bind bool
want string
}{{true, "[2 3 1]"}, {false, "[2 1 3]"}} {
m := r479Manager(t, tc.bind)
if got := fmt.Sprint(m.UpdateTierOrderFor("app")); got != tc.want {
t.Errorf("bind=%v: order %s, want %s", tc.bind, got, tc.want)
}
if m.DataOutsideUnit("app") != tc.bind {
t.Errorf("bind=%v: DataOutsideUnit must follow the classified binds", tc.bind)
}
}
// The consequence, not only the mechanism: with Tier 2 absent and BOTH the unit and off-site
// holding a copy, a bind-data app leans on OFF-SITE; a unit app on its own unit.
for _, tc := range []struct {
bind bool
want int
}{{true, UpdateTierOffsite}, {false, UpdateTierLocal}} {
m := r479Manager(t, tc.bind)
m.updateTier2PointFn = noTier2
m.updateTier1PointsFn = tier1At(r475T0.Add(-time.Hour))
m.updateOffsiteTimesFn = func(context.Context) (map[string]time.Time, error) {
return map[string]time.Time{"app": r475T0.Add(-2 * time.Hour)}, nil
}
p, ok, _ := m.UpdateRestorePoints(context.Background(), "app", nil)
if !ok || p.Tier != tc.want {
t.Errorf("bind=%v: chose tier %d, want %d", tc.bind, p.Tier, tc.want)
}
}
}
func TestR479_HoldSentenceNamesWhatTheCopyHolds(t *testing.T) {
at := time.Date(2026, 9, 13, 8, 0, 0, 0, time.UTC)
copyAt := time.Date(2026, 9, 13, 1, 30, 0, 0, time.UTC)
m := r479Manager(t, true)
m.logger = log.New(io.Discard, "", 0)
holds := m.UpdateCopyHolds("app", UpdateTierLocal)
if !strings.HasPrefix(holds, "csak a beállításokat") || !strings.HasSuffix(holds, "a fájlokat nem") {
t.Fatalf("a bind-data app's own unit holds settings and the database only, got %q", holds)
}
if h := m.UpdateCopyHolds("app", UpdateTierOffsite); !strings.Contains(h, "a fájlokat") || strings.Contains(h, "csak") {
t.Errorf("off-site holds the files too, got %q", h)
}
if err := m.HoldAfterFailedUpdateHolding("app", at, copyAt, UpdateTierLocal, holds); err != nil {
t.Fatal(err)
}
_, why := m.RestoreHoldFor("app")
want := fmt.Sprintf(UpdateHoldFmt, "app", "2026-09-13 10:00", "saját meghajtó", "2026-09-13 03:30", holds)
if why != want {
t.Errorf("hold text =\n%q\nwant\n%q", why, want)
}
if !strings.HasSuffix(why, "a fájlokat nem.") {
t.Errorf("the sentence must END with what the copy does not hold, got %q", why)
}
// A hold recorded WITHOUT the phrase (v0.239.0–v0.240.0) keeps the tier-only sentence.
if err := m.HoldAfterFailedUpdate("app2", at, copyAt, UpdateTierSecondDrive); err != nil {
t.Fatal(err)
}
_, why2 := m.RestoreHoldFor("app2")
if why2 != fmt.Sprintf(UpdateHoldTierFmt, "app2", "2026-09-13 10:00", "második meghajtó", "2026-09-13 03:30") {
t.Errorf("tier-only hold text = %q", why2)
}
}