controller: zero-toggle honesty — a configured+escrowed offbox with no app selected shows 'Nincs távoli mentésre jelölt alkalmazás…' on the toggle list, and a zero-toggle run reports 'Sikeres — nincs mentésre jelölt alkalmazás' instead of bare success
Claude-Session: https://claude.ai/code/session_01GzammAMzsJTgpQHqxwM2bC
This commit is contained in:
@@ -441,6 +441,12 @@ func (m *Manager) RunOffboxBackup(ctx context.Context) error {
|
|||||||
o.LastError = ""
|
o.LastError = ""
|
||||||
o.SnapshotCount = snapshots
|
o.SnapshotCount = snapshots
|
||||||
var warns []string
|
var warns []string
|
||||||
|
// Zero-toggle honesty (take-two obs.): a configured target with NOTHING selected reports
|
||||||
|
// its emptiness instead of a bare success — the customer thinks offsite runs, but nothing
|
||||||
|
// is covered until at least one app is toggled.
|
||||||
|
if len(apps) == 0 {
|
||||||
|
warns = append(warns, "Sikeres — nincs mentésre jelölt alkalmazás")
|
||||||
|
}
|
||||||
if len(missing) > 0 {
|
if len(missing) > 0 {
|
||||||
warns = append(warns, fmt.Sprintf("Figyelmeztetés: %d alkalmazásnak nincs elérhető mentése, ezek kimaradtak: %s",
|
warns = append(warns, fmt.Sprintf("Figyelmeztetés: %d alkalmazásnak nincs elérhető mentése, ezek kimaradtak: %s",
|
||||||
len(missing), strings.Join(missing, ", ")))
|
len(missing), strings.Join(missing, ", ")))
|
||||||
|
|||||||
@@ -194,6 +194,43 @@ func TestOffbox_RunFailsFastAndAlerts(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Zero-toggle honesty (take-two obs., v0.123.0): a run with a configured+escrowed target but ZERO
|
||||||
|
// toggled apps must stay status=ok yet report "Sikeres — nincs mentésre jelölt alkalmazás" instead
|
||||||
|
// of a bare success. Red-proof: drop the len(apps)==0 warns branch → the LastWarning assertion fails.
|
||||||
|
func TestOffbox_ZeroToggledRunReportsEmptiness(t *testing.T) {
|
||||||
|
m, sett := newOffboxManager(t)
|
||||||
|
m.SetOffboxRunner(func(_ context.Context, _ []string, args ...string) ([]byte, error) {
|
||||||
|
if contains(args, "cat") && contains(args, "config") {
|
||||||
|
return []byte(`{"version":2}`), nil // repo exists
|
||||||
|
}
|
||||||
|
return []byte(`[]`), nil
|
||||||
|
})
|
||||||
|
// No SetAppOffbox call — zero apps toggled.
|
||||||
|
if err := m.RunOffboxBackup(context.Background()); err != nil {
|
||||||
|
t.Fatalf("zero-toggled run must not error: %v", err)
|
||||||
|
}
|
||||||
|
st := sett.GetOffboxTarget()
|
||||||
|
if st.LastStatus != "ok" {
|
||||||
|
t.Fatalf("zero-toggled run status = %q, want ok (emptiness is honesty, not failure)", st.LastStatus)
|
||||||
|
}
|
||||||
|
if !strings.Contains(st.LastWarning, "Sikeres — nincs mentésre jelölt alkalmazás") {
|
||||||
|
t.Fatalf("zero-toggled run must report its emptiness, LastWarning = %q", st.LastWarning)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Companion negative: with one toggled app captured, the emptiness copy must NOT appear.
|
||||||
|
nsRoot := m.AppNamespaceRoot("rallly")
|
||||||
|
if err := os.MkdirAll(filepath.Join(RecoveryUnitPath(nsRoot, "rallly"), "db-dumps"), 0o755); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
_ = sett.SetAppOffbox("rallly", true)
|
||||||
|
if err := m.RunOffboxBackup(context.Background()); err != nil {
|
||||||
|
t.Fatalf("toggled run must not error: %v", err)
|
||||||
|
}
|
||||||
|
if st := sett.GetOffboxTarget(); strings.Contains(st.LastWarning, "nincs mentésre jelölt") {
|
||||||
|
t.Fatalf("a covered run must not carry the emptiness copy, LastWarning = %q", st.LastWarning)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// TestOffbox_RepoIdempotent: when the repo exists (cat config succeeds), ensure must NOT init.
|
// TestOffbox_RepoIdempotent: when the repo exists (cat config succeeds), ensure must NOT init.
|
||||||
func TestOffbox_RepoIdempotent(t *testing.T) {
|
func TestOffbox_RepoIdempotent(t *testing.T) {
|
||||||
m, sett := newOffboxManager(t)
|
m, sett := newOffboxManager(t)
|
||||||
|
|||||||
@@ -671,7 +671,17 @@ func (s *Server) backupsHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
offboxTgt := s.settings.GetOffboxTarget()
|
offboxTgt := s.settings.GetOffboxTarget()
|
||||||
data["Offbox"] = offboxTgt
|
data["Offbox"] = offboxTgt
|
||||||
data["OffboxConfigured"] = s.backupMgr.OffboxConfigured()
|
data["OffboxConfigured"] = s.backupMgr.OffboxConfigured()
|
||||||
data["OffboxApps"] = s.buildOffboxApps()
|
offboxApps := s.buildOffboxApps()
|
||||||
|
data["OffboxApps"] = offboxApps
|
||||||
|
// Zero-toggle hint (take-two obs.): configured + escrowed but no app selected — nothing is
|
||||||
|
// actually covered by the offsite leg until the customer toggles at least one.
|
||||||
|
offboxToggled := 0
|
||||||
|
for _, a := range offboxApps {
|
||||||
|
if a.Enabled {
|
||||||
|
offboxToggled++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
data["OffboxToggledCount"] = offboxToggled
|
||||||
// SLICE 4 soft-quota usage bar (rendered only when a quota is set — shared model).
|
// SLICE 4 soft-quota usage bar (rendered only when a quota is set — shared model).
|
||||||
data["OffboxQuotaPct"] = backup.OffboxQuotaPercent(offboxTgt)
|
data["OffboxQuotaPct"] = backup.OffboxQuotaPercent(offboxTgt)
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -169,6 +169,9 @@
|
|||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<h4 style="margin-top:1.25rem">Mely alkalmazások mentődnek a távoli tárolóra?</h4>
|
<h4 style="margin-top:1.25rem">Mely alkalmazások mentődnek a távoli tárolóra?</h4>
|
||||||
|
{{if and .OffboxApps (eq .Offbox.EscrowState "escrowed") (eq .OffboxToggledCount 0)}}
|
||||||
|
<p class="form-hint">Nincs távoli mentésre jelölt alkalmazás — jelölj ki legalább egyet.</p>
|
||||||
|
{{end}}
|
||||||
{{if .OffboxApps}}
|
{{if .OffboxApps}}
|
||||||
<div class="storage-paths-list">
|
<div class="storage-paths-list">
|
||||||
{{range .OffboxApps}}
|
{{range .OffboxApps}}
|
||||||
|
|||||||
Reference in New Issue
Block a user