Files
pulse/internal/monitoring/monitor_runtime_settings_test.go
T
courtmanr@gmail.com df72955144 Cover pbsPollingInterval in the live polling-cadence setters
The live-setter fix for #1619 pushed backup and PMG cadences into
running monitors but left pbsPollingInterval out entirely: it was
neither applied to the handler's base config nor pushed to live
monitors, so a saved PBS interval still waited for an unrelated
restart. Add the matching override, setter and scheduler wiring, apply
the value to the base config, and fan it out from the settings handler
like the other cadence settings.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-26 12:42:22 +01:00

133 lines
4.6 KiB
Go

package monitoring
import (
"testing"
"time"
"github.com/rcourtman/pulse-go-rewrite/internal/config"
)
// Regression test for #1619: monitors poll against a detached DeepCopy of the
// base config, so mutating the base config after a settings save must not be
// relied on — the live setters have to reach the running monitor directly.
func TestSetBackupPollingIntervalReachesLiveMonitor(t *testing.T) {
now := time.Now()
base := &config.Config{
EnableBackupPolling: true,
BackupPollingInterval: time.Hour,
}
m := &Monitor{
config: base.DeepCopy(),
lastPVEBackupPoll: map[string]time.Time{"pve1": now.Add(-30 * time.Minute)},
lastPBSBackupPoll: map[string]time.Time{"pbs1": now.Add(-30 * time.Minute)},
}
last := m.lastPVEBackupPoll["pve1"]
if should, _, _ := m.shouldRunBackupPoll(last, now); should {
t.Fatal("expected no poll 30m into a 1h interval")
}
// Mutating the base config (what the settings handler used to do) never
// reaches the monitor's detached copy.
base.BackupPollingInterval = 10 * time.Minute
if should, _, _ := m.shouldRunBackupPoll(last, now); should {
t.Fatal("base config mutation unexpectedly reached the detached monitor config")
}
// The live setter must take effect without a monitor reload.
m.SetBackupPollingInterval(10 * time.Minute)
if len(m.lastPVEBackupPoll) != 0 || len(m.lastPBSBackupPoll) != 0 {
t.Fatal("lowering the interval should clear last-poll timestamps for an immediate catch-up poll")
}
last = m.lastPVEBackupPoll["pve1"] // zero after reset
if should, _, _ := m.shouldRunBackupPoll(last, now); !should {
t.Fatal("expected immediate catch-up poll after lowering the interval")
}
}
func TestSetBackupPollingIntervalRaisingKeepsTimestamps(t *testing.T) {
now := time.Now()
last := now.Add(-5 * time.Minute)
m := &Monitor{
config: &config.Config{
EnableBackupPolling: true,
BackupPollingInterval: 10 * time.Minute,
},
lastPVEBackupPoll: map[string]time.Time{"pve1": last},
lastPBSBackupPoll: map[string]time.Time{},
}
m.SetBackupPollingInterval(time.Hour)
if got := m.lastPVEBackupPoll["pve1"]; !got.Equal(last) {
t.Fatal("raising the interval should keep last-poll timestamps")
}
if should, _, _ := m.shouldRunBackupPoll(last, now); should {
t.Fatal("expected no poll 5m into the raised 1h interval")
}
}
func TestSetBackupPollingEnabledReachesLiveMonitor(t *testing.T) {
now := time.Now()
m := &Monitor{
config: &config.Config{
EnableBackupPolling: true,
BackupPollingInterval: time.Hour,
},
lastPVEBackupPoll: map[string]time.Time{"pve1": now.Add(-30 * time.Minute)},
lastPBSBackupPoll: map[string]time.Time{},
}
m.SetBackupPollingEnabled(false)
if should, reason, _ := m.shouldRunBackupPoll(time.Time{}, now); should || reason != "backup polling globally disabled" {
t.Fatalf("expected backup polling disabled, got should=%v reason=%q", should, reason)
}
m.SetBackupPollingEnabled(true)
if len(m.lastPVEBackupPoll) != 0 {
t.Fatal("re-enabling backup polling should clear last-poll timestamps")
}
if should, _, _ := m.shouldRunBackupPoll(m.lastPVEBackupPoll["pve1"], now); !should {
t.Fatal("expected immediate poll after re-enabling backup polling")
}
}
func TestSetPBSPollingIntervalReachesLiveMonitor(t *testing.T) {
base := &config.Config{PBSPollingInterval: 2 * time.Minute}
m := &Monitor{config: base.DeepCopy()}
if got := m.baseIntervalForInstanceType(InstanceTypePBS); got != 2*time.Minute {
t.Fatalf("expected 2m from config, got %v", got)
}
m.SetPBSPollingInterval(5 * time.Minute)
if got := m.baseIntervalForInstanceType(InstanceTypePBS); got != 5*time.Minute {
t.Fatalf("expected 5m after live update, got %v", got)
}
// Clamping still applies to live updates.
m.SetPBSPollingInterval(2 * time.Hour)
if got := m.baseIntervalForInstanceType(InstanceTypePBS); got != time.Hour {
t.Fatalf("expected live update clamped to 1h, got %v", got)
}
}
func TestSetPMGPollingIntervalReachesLiveMonitor(t *testing.T) {
base := &config.Config{PMGPollingInterval: 2 * time.Minute}
m := &Monitor{config: base.DeepCopy()}
if got := m.baseIntervalForInstanceType(InstanceTypePMG); got != 2*time.Minute {
t.Fatalf("expected 2m from config, got %v", got)
}
m.SetPMGPollingInterval(5 * time.Minute)
if got := m.baseIntervalForInstanceType(InstanceTypePMG); got != 5*time.Minute {
t.Fatalf("expected 5m after live update, got %v", got)
}
// Clamping still applies to live updates.
m.SetPMGPollingInterval(2 * time.Hour)
if got := m.baseIntervalForInstanceType(InstanceTypePMG); got != time.Hour {
t.Fatalf("expected live update clamped to 1h, got %v", got)
}
}