test: Add tests for alert utility functions

SetMetricHooks (0%→100%):
- Sets all four hooks correctly
- Nil hooks are safely handled
- Properly saves/restores state to avoid test pollution

NotifyExistingAlert (0%→100%):
- Non-existent alert is no-op
- Existing alert dispatches async notification

GetResolvedAlert (0%→100%):
- Returns nil for non-existent alert
- Returns nil for nil resolved entry
- Returns nil when Alert is nil
- Returns cloned alert with resolved time

GetAlertHistory/GetAlertHistorySince (0%→100%):
- Returns history from history manager
- Respects limit parameter
- Zero time returns all history
- Filters by time correctly

ClearAlertHistory (0%→100%):
- Clears all history entries

Coverage: 56.9% → 57.4%
This commit is contained in:
rcourtman 2025-12-01 16:43:04 +00:00
parent 5c584b9eee
commit b9e49a26c4

View file

@ -5691,3 +5691,264 @@ func TestHandleDockerHostOffline(t *testing.T) {
}
})
}
func TestSetMetricHooks(t *testing.T) {
// NOT parallel - modifies package-level state
// Save existing state and restore after test
oldFired := recordAlertFired
oldResolved := recordAlertResolved
oldSuppressed := recordAlertSuppressed
oldAcknowledged := recordAlertAcknowledged
defer func() {
recordAlertFired = oldFired
recordAlertResolved = oldResolved
recordAlertSuppressed = oldSuppressed
recordAlertAcknowledged = oldAcknowledged
}()
t.Run("sets all hooks", func(t *testing.T) {
var firedCalled, resolvedCalled, suppressedCalled, acknowledgedCalled bool
SetMetricHooks(
func(a *Alert) { firedCalled = true },
func(a *Alert) { resolvedCalled = true },
func(s string) { suppressedCalled = true },
func() { acknowledgedCalled = true },
)
// Verify hooks are set by calling them (if they were nil, this would panic)
if recordAlertFired != nil {
recordAlertFired(&Alert{})
}
if recordAlertResolved != nil {
recordAlertResolved(&Alert{})
}
if recordAlertSuppressed != nil {
recordAlertSuppressed("test")
}
if recordAlertAcknowledged != nil {
recordAlertAcknowledged()
}
if !firedCalled {
t.Error("expected fired hook to be called")
}
if !resolvedCalled {
t.Error("expected resolved hook to be called")
}
if !suppressedCalled {
t.Error("expected suppressed hook to be called")
}
if !acknowledgedCalled {
t.Error("expected acknowledged hook to be called")
}
})
t.Run("nil hooks are safe", func(t *testing.T) {
SetMetricHooks(nil, nil, nil, nil)
// Should not panic
if recordAlertFired != nil {
t.Error("expected fired hook to be nil")
}
if recordAlertResolved != nil {
t.Error("expected resolved hook to be nil")
}
})
}
func TestNotifyExistingAlert(t *testing.T) {
t.Parallel()
t.Run("non-existent alert is no-op", func(t *testing.T) {
t.Parallel()
m := NewManager()
// Should not panic
m.NotifyExistingAlert("non-existent-alert")
})
t.Run("existing alert dispatches notification", func(t *testing.T) {
t.Parallel()
m := NewManager()
dispatchedCh := make(chan bool, 1)
m.SetAlertCallback(func(a *Alert) {
dispatchedCh <- true
})
m.mu.Lock()
m.config.Enabled = true
m.config.ActivationState = ActivationActive // Must be active to dispatch
m.activeAlerts["test-alert"] = &Alert{
ID: "test-alert",
Type: "test",
Level: AlertLevelWarning,
}
m.mu.Unlock()
m.NotifyExistingAlert("test-alert")
// Wait for async dispatch with timeout
select {
case <-dispatchedCh:
// Success
case <-time.After(1 * time.Second):
t.Error("expected alert callback to be called (timeout)")
}
})
}
func TestGetResolvedAlert(t *testing.T) {
t.Parallel()
t.Run("returns nil for non-existent alert", func(t *testing.T) {
t.Parallel()
m := NewManager()
result := m.GetResolvedAlert("non-existent")
if result != nil {
t.Error("expected nil for non-existent alert")
}
})
t.Run("returns nil for nil resolved entry", func(t *testing.T) {
t.Parallel()
m := NewManager()
m.resolvedMutex.Lock()
m.recentlyResolved["test"] = nil
m.resolvedMutex.Unlock()
result := m.GetResolvedAlert("test")
if result != nil {
t.Error("expected nil for nil resolved entry")
}
})
t.Run("returns nil when Alert is nil", func(t *testing.T) {
t.Parallel()
m := NewManager()
m.resolvedMutex.Lock()
m.recentlyResolved["test"] = &ResolvedAlert{Alert: nil}
m.resolvedMutex.Unlock()
result := m.GetResolvedAlert("test")
if result != nil {
t.Error("expected nil when Alert is nil")
}
})
t.Run("returns cloned alert", func(t *testing.T) {
t.Parallel()
m := NewManager()
resolvedTime := time.Now()
m.resolvedMutex.Lock()
m.recentlyResolved["test"] = &ResolvedAlert{
Alert: &Alert{
ID: "test",
Type: "cpu",
Level: AlertLevelWarning,
ResourceID: "res1",
ResourceName: "Resource 1",
},
ResolvedTime: resolvedTime,
}
m.resolvedMutex.Unlock()
result := m.GetResolvedAlert("test")
if result == nil {
t.Fatal("expected non-nil result")
}
if result.Alert.ID != "test" {
t.Errorf("expected ID test, got %s", result.Alert.ID)
}
if result.ResolvedTime != resolvedTime {
t.Error("expected resolved time to match")
}
})
}
func TestGetAlertHistory(t *testing.T) {
t.Parallel()
t.Run("returns history from history manager", func(t *testing.T) {
t.Parallel()
m := NewManager()
// Add some alerts to history
m.historyManager.AddAlert(Alert{ID: "alert1", Type: "cpu"})
m.historyManager.AddAlert(Alert{ID: "alert2", Type: "memory"})
history := m.GetAlertHistory(10)
if len(history) < 2 {
t.Errorf("expected at least 2 history entries, got %d", len(history))
}
})
t.Run("respects limit", func(t *testing.T) {
t.Parallel()
m := NewManager()
// Add alerts
for i := 0; i < 5; i++ {
m.historyManager.AddAlert(Alert{ID: fmt.Sprintf("alert%d", i), Type: "test"})
}
history := m.GetAlertHistory(2)
if len(history) > 2 {
t.Errorf("expected max 2 entries, got %d", len(history))
}
})
}
func TestGetAlertHistorySince(t *testing.T) {
t.Parallel()
t.Run("zero time returns all history", func(t *testing.T) {
t.Parallel()
m := NewManager()
m.historyManager.AddAlert(Alert{ID: "alert1", Type: "cpu"})
history := m.GetAlertHistorySince(time.Time{}, 10)
if len(history) == 0 {
t.Error("expected history entries for zero time")
}
})
t.Run("filters by time", func(t *testing.T) {
t.Parallel()
m := NewManager()
// Add an alert
m.historyManager.AddAlert(Alert{ID: "alert1", Type: "cpu", StartTime: time.Now()})
// Query for alerts after now + 1 hour (should return none)
future := time.Now().Add(1 * time.Hour)
history := m.GetAlertHistorySince(future, 10)
if len(history) != 0 {
t.Errorf("expected 0 entries for future time, got %d", len(history))
}
})
}
func TestClearAlertHistory(t *testing.T) {
t.Parallel()
t.Run("clears all history", func(t *testing.T) {
t.Parallel()
m := NewManager()
// Add some alerts
m.historyManager.AddAlert(Alert{ID: "alert1", Type: "cpu"})
m.historyManager.AddAlert(Alert{ID: "alert2", Type: "memory"})
err := m.ClearAlertHistory()
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
history := m.GetAlertHistory(10)
if len(history) != 0 {
t.Errorf("expected 0 entries after clear, got %d", len(history))
}
})
}