v0.295.0: a box that was off at its backup time catches up once (R-871, decision 109); the missed-backup banner (decision 110); a late daily timer after a host suspend is skipped
gates / gates (push) Successful in 31s

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-10-05 09:27:16 +02:00
parent e730a629fd
commit 635c33d381
24 changed files with 2074 additions and 18 deletions
@@ -0,0 +1,30 @@
package quiesce
import (
"context"
"testing"
)
// R-871 (v0.295.0): a scheduled whole-guest cycle waits while a catch-up of a missed night runs (it would stop the
// apps under the catch-up's database dumps), and runs at the next poll once the catch-up has ended.
// COMPANION RED-PROOF: drop the catchUpRunning() check in runOnce → "the backup started while a catch-up ran".
func TestR871_ScheduledCycleWaitsForCatchUp(t *testing.T) {
be := &fakeBackend{due: true, dueAge: i64(20 * 3600), phases: []string{"done"}}
st := &fakeStacks{running: []string{"nextcloud"}}
l := windowLoop(t, be, st, "02:30", atBudapest(5, 0)) // inside [W+2h, W+6h)
running := true
l.SetCatchUpFn(func() bool { return running })
if err := l.runOnce(context.Background()); err != nil {
t.Fatalf("runOnce: %v", err)
}
if be.startCalls != 0 || len(st.stoppedNames()) != 0 {
t.Fatalf("the backup started while a catch-up ran: start=%d stopped=%v", be.startCalls, st.stoppedNames())
}
running = false
if err := l.runOnce(context.Background()); err != nil {
t.Fatalf("runOnce: %v", err)
}
if be.startCalls != 1 {
t.Fatalf("after the catch-up the backup must run at the next poll; start=%d", be.startCalls)
}
}