Files
pulse/internal/alerts/offline_toggle_test.go
T
rcourtman b75c181888 fix(alerts): clear canonical docker alerts when disable-all toggles are set
The DisableAllDockerContainers and DisableAllDockerServices config sweeps
matched only legacy docker-container-/docker-service- alert ID prefixes.
Canonical stateful alerts (image-update, state, health, replica-gap) are
stored under <resourceID>::<specID> state IDs, so existing alerts survived
the toggle and lingered indefinitely, re-notifying past cooldown.

Match on the docker: resource ID scheme as well as the legacy prefixes.
2026-07-17 12:03:00 +01:00

156 lines
5.0 KiB
Go

package alerts
import (
"testing"
"time"
"github.com/rcourtman/pulse-go-rewrite/internal/models"
)
func TestDisableAllNodesOfflinePreventsOfflineAlert(t *testing.T) {
manager := NewManager()
// Reset state to avoid interference from persisted alerts.
manager.mu.Lock()
manager.activeAlerts = make(map[string]*Alert)
manager.nodeOfflineCount = make(map[string]int)
manager.mu.Unlock()
config := manager.GetConfig()
config.DisableAllNodesOffline = true
manager.UpdateConfig(config)
node := models.Node{
ID: "node-1",
Name: "node-1",
Status: "offline",
ConnectionHealth: "error",
}
manager.CheckNode(node)
manager.mu.RLock()
_, alertExists := manager.activeAlerts["node-offline-node-1"]
_, counterExists := manager.nodeOfflineCount["node-1"]
manager.mu.RUnlock()
if alertExists {
t.Fatalf("expected no node offline alert when DisableAllNodesOffline is true")
}
if counterExists {
t.Fatalf("expected node offline counter to be cleared when DisableAllNodesOffline is true")
}
}
func TestUpdateConfigClearsExistingNodeOfflineAlerts(t *testing.T) {
manager := NewManager()
manager.mu.Lock()
manager.activeAlerts = make(map[string]*Alert)
manager.nodeOfflineCount = make(map[string]int)
manager.activeAlerts["node-offline-node-1"] = &Alert{
ID: "node-offline-node-1",
Type: "connectivity",
ResourceID: "node-1",
ResourceName: "node-1",
Node: "node-1",
StartTime: time.Now().Add(-5 * time.Minute),
LastSeen: time.Now(),
}
manager.nodeOfflineCount["node-1"] = 3
manager.mu.Unlock()
config := manager.GetConfig()
config.DisableAllNodesOffline = true
manager.UpdateConfig(config)
// Allow asynchronous resolution callbacks, if any, to run.
time.Sleep(50 * time.Millisecond)
manager.mu.RLock()
_, alertExists := manager.activeAlerts["node-offline-node-1"]
_, counterExists := manager.nodeOfflineCount["node-1"]
manager.mu.RUnlock()
if alertExists {
t.Fatalf("expected node offline alert to be cleared when DisableAllNodesOffline is enabled")
}
if counterExists {
t.Fatalf("expected node offline counter to be reset when DisableAllNodesOffline is enabled")
}
}
func TestUpdateConfigClearsDockerContainerAlertsWhenDisabled(t *testing.T) {
manager := NewManager()
containerResourceID := "docker:host-1/container-1"
containerAlertIDs := []string{
"docker-container-state-" + containerResourceID,
"docker-container-health-" + containerResourceID,
"docker-container-restart-loop-" + containerResourceID,
"docker-container-oom-" + containerResourceID,
"docker-container-memory-limit-" + containerResourceID,
}
// Canonical stateful alerts (e.g. the image-update alert) are stored under
// "<resourceID>::<specID>" state IDs rather than legacy prefixed IDs.
canonicalUpdateAlertID := buildCanonicalStateID(containerResourceID, containerResourceID+"-image-update")
containerAlertIDs = append(containerAlertIDs, canonicalUpdateAlertID)
manager.mu.Lock()
for _, id := range containerAlertIDs {
manager.activeAlerts[id] = &Alert{ID: id, ResourceID: containerResourceID}
}
manager.dockerStateConfirm[containerResourceID] = 2
manager.dockerRestartTracking[containerResourceID] = &dockerRestartRecord{}
manager.mu.Unlock()
config := manager.GetConfig()
config.DisableAllDockerContainers = true
manager.UpdateConfig(config)
time.Sleep(10 * time.Millisecond)
manager.mu.RLock()
defer manager.mu.RUnlock()
for _, id := range containerAlertIDs {
if _, exists := manager.activeAlerts[id]; exists {
t.Fatalf("expected docker container alert %s to be cleared when DisableAllDockerContainers is enabled", id)
}
}
if len(manager.dockerStateConfirm) != 0 {
t.Fatalf("expected dockerStateConfirm map to be cleared when DisableAllDockerContainers is enabled")
}
if len(manager.dockerRestartTracking) != 0 {
t.Fatalf("expected dockerRestartTracking map to be cleared when DisableAllDockerContainers is enabled")
}
}
func TestUpdateConfigClearsDockerServiceAlertsWhenDisabled(t *testing.T) {
manager := NewManager()
serviceResourceID := "docker:host-2/service/frontend"
serviceAlertID := "docker-service-health-" + serviceResourceID
canonicalServiceAlertID := buildCanonicalStateID(serviceResourceID, serviceResourceID+"-replica-gap")
manager.mu.Lock()
manager.activeAlerts[serviceAlertID] = &Alert{ID: serviceAlertID, ResourceID: serviceResourceID}
manager.activeAlerts[canonicalServiceAlertID] = &Alert{ID: canonicalServiceAlertID, ResourceID: serviceResourceID}
manager.mu.Unlock()
config := manager.GetConfig()
config.DisableAllDockerServices = true
manager.UpdateConfig(config)
time.Sleep(10 * time.Millisecond)
manager.mu.RLock()
defer manager.mu.RUnlock()
if _, exists := manager.activeAlerts[serviceAlertID]; exists {
t.Fatalf("expected docker service alert to be cleared when DisableAllDockerServices is enabled")
}
if _, exists := manager.activeAlerts[canonicalServiceAlertID]; exists {
t.Fatalf("expected canonical docker service alert to be cleared when DisableAllDockerServices is enabled")
}
}