package quiesce import ( "context" "sync" "testing" "time" ) // R-518 (cheap half) — a tier whose storage the agent reports ABSENT is not attempted, and no app is // stopped for it. BIGNIGHT: the apps stayed stopped through a felhom-pbs tier whose storage did not // exist, then that tier failed. type skipRecorder struct { mu sync.Mutex skipped []string } func (r *skipRecorder) BackupFailed(string, string, string) {} func (r *skipRecorder) BackupRecovered(string, string) {} func (r *skipRecorder) BackupTierSkipped(tier, _ string) { r.mu.Lock() defer r.mu.Unlock() r.skipped = append(r.skipped, tier) } // RED-PROOF (run 2026-09-15, recorded in REPORT.md): with skipAbsentTiers returning its input // unchanged, the manual run started [local felhom-pbs] and this failed at "absent tier was attempted". func TestManualRun_AbsentTierSkipped(t *testing.T) { be := newTierBackend() be.tiers = []BackupTier{{Target: "local", Primary: true}, {Target: "felhom-pbs", StorageAbsent: true}} be.phases["local"] = []string{phaseDone} st := &fakeStacks{running: []string{"immich"}} l := newTierLoop(t, be, st, nil) rec := &skipRecorder{} l.SetTierNotifier(rec) if err := l.quiesceAndPollTiers(context.Background(), l.allTiersForManualRun(context.Background())); err != nil { t.Fatalf("manual cycle: %v", err) } if got := be.startedTargets(); len(got) != 1 || got[0] != "local" { t.Fatalf("absent tier was attempted: started=%v", got) } if stops, starts := len(st.stoppedNames()), len(st.startedNames()); stops != 1 || starts != 1 { t.Fatalf("want one stop/start for the local tier, got %d/%d", stops, starts) } // A second resolution in the same absence does not notify again. _ = l.allTiersForManualRun(context.Background()) if len(rec.skipped) != 1 || rec.skipped[0] != "felhom-pbs" { t.Fatalf("want exactly one skip notice for felhom-pbs, got %v", rec.skipped) } } // Only the absent tier is due → no quiesce at all. func TestScheduled_OnlyAbsentTierDue_NoQuiesce(t *testing.T) { be := newTierBackend() be.tiers = []BackupTier{{Target: "local", Primary: true}, {Target: "felhom-pbs", StorageAbsent: true}} be.dueSet["felhom-pbs"] = true st := &fakeStacks{running: []string{"immich"}} l := newTierLoop(t, be, st, nil) l.now = func() time.Time { return time.Date(2026, 9, 15, 1, 0, 0, 0, time.UTC) } _ = l.runOnce(context.Background()) if stops := len(st.stoppedNames()); stops != 0 { t.Fatalf("an app was stopped for a tier whose storage does not exist (stops=%d)", stops) } if got := be.startedTargets(); len(got) != 0 { t.Fatalf("absent tier started: %v", got) } } // Unknown/legacy storage (StorageAbsent=false) is never skipped — fail toward backing up. func TestManualRun_UnknownStorageNotSkipped(t *testing.T) { be := newTierBackend() be.tiers = []BackupTier{{Target: "local", Primary: true}, {Target: "felhom-pbs"}} be.phases["local"] = []string{phaseDone} be.phases["felhom-pbs"] = []string{phaseDone} l := newTierLoop(t, be, &fakeStacks{running: []string{"immich"}}, nil) if got := l.allTiersForManualRun(context.Background()); len(got) != 2 { t.Fatalf("a tier without an absent verdict was dropped: %v", got) } }