package quiesce import ( "context" "reflect" "sync" "testing" ) // householdStacks is fakeStacks plus the household's recorded intent (the stacks manager's WantsStopped). type householdStacks struct { fakeStacks mu2 sync.Mutex stopped map[string]bool } func (h *householdStacks) WantsStopped(name string) bool { h.mu2.Lock() defer h.mu2.Unlock() return h.stopped[name] } // pressStopDuringBackup is a backend that, on its first status poll (the backup is running, the apps are // down for it), lets the household press Stop on one app — the 2026-09-29 19:37:16 shape. type pressStopDuringBackup struct { *fakeBackend st *householdStacks app string once sync.Once } func (b *pressStopDuringBackup) BackupStatus(ctx context.Context) (string, error) { b.once.Do(func() { b.st.mu2.Lock(); b.st.stopped[b.app] = true; b.st.mu2.Unlock() }) return b.fakeBackend.BackupStatus(ctx) } // R-721 (v0.283.0). The CONSEQUENCE asserted: after the backup's resume, the app the household stopped during // the backup was NOT started again; the other one was. // COMPANION RED-PROOF: remove the WantsStopped skip in restartAll → this test fails with actualbudget in the // started list (the measured 2026-09-29 defect). func TestR721_AStopPressedDuringTheBackupHolds(t *testing.T) { st := &householdStacks{fakeStacks: fakeStacks{running: []string{"actualbudget", "bookstack"}}, stopped: map[string]bool{}} be := &pressStopDuringBackup{fakeBackend: &fakeBackend{due: true, phases: []string{"running", "snapshotted", "done"}}, st: st, app: "actualbudget"} l := testLoop(t, be, st) if err := l.runOnce(context.Background()); err != nil { t.Fatalf("runOnce: %v", err) } if got := st.startedNames(); !reflect.DeepEqual(got, []string{"bookstack"}) { t.Fatalf("the backup's resume started %v — the household's Stop on actualbudget must hold (R-721)", got) } } // Control: with no Stop pressed, both come back (the skip must not swallow a normal resume). func TestR721_ControlBothResumeWithoutAStop(t *testing.T) { st := &householdStacks{fakeStacks: fakeStacks{running: []string{"actualbudget", "bookstack"}}, stopped: map[string]bool{}} l := testLoop(t, &fakeBackend{due: true, phases: []string{"running", "done"}}, st) if err := l.runOnce(context.Background()); err != nil { t.Fatal(err) } if got := st.startedNames(); !reflect.DeepEqual(got, []string{"actualbudget", "bookstack"}) { t.Fatalf("got %v", got) } }