From 9229cf86bee9fa6a0ddecb7f5908b2b4fbf8a6e2 Mon Sep 17 00:00:00 2001 From: rcourtman Date: Mon, 1 Dec 2025 16:04:25 +0000 Subject: [PATCH] test: Add comprehensive tests for safeCallResolvedCallback function MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - calls callback synchronously with alert ID - calls callback asynchronously - noop when callback is nil - recovers from panic in sync and async callbacks - safeCallResolvedCallback 90%→100% --- internal/alerts/alerts_test.go | 86 ++++++++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) diff --git a/internal/alerts/alerts_test.go b/internal/alerts/alerts_test.go index 356aaf1..94743bf 100644 --- a/internal/alerts/alerts_test.go +++ b/internal/alerts/alerts_test.go @@ -3880,3 +3880,89 @@ func TestSafeCallEscalateCallback(t *testing.T) { } }) } + +func TestSafeCallResolvedCallback(t *testing.T) { + t.Parallel() + + t.Run("calls callback with alert ID synchronously", func(t *testing.T) { + t.Parallel() + m := NewManager() + + var receivedID string + m.SetResolvedCallback(func(alertID string) { + receivedID = alertID + }) + + m.safeCallResolvedCallback("test-alert-123", false) + + if receivedID != "test-alert-123" { + t.Errorf("expected alert ID 'test-alert-123', got %q", receivedID) + } + }) + + t.Run("calls callback asynchronously", func(t *testing.T) { + t.Parallel() + m := NewManager() + + var receivedID string + done := make(chan struct{}) + + m.SetResolvedCallback(func(alertID string) { + receivedID = alertID + close(done) + }) + + m.safeCallResolvedCallback("async-alert", true) + + select { + case <-done: + if receivedID != "async-alert" { + t.Errorf("expected alert ID 'async-alert', got %q", receivedID) + } + case <-time.After(1 * time.Second): + t.Fatal("async callback not called within timeout") + } + }) + + t.Run("noop when callback is nil", func(t *testing.T) { + t.Parallel() + m := NewManager() + // No callback set + + // Should not panic + m.safeCallResolvedCallback("test-alert", false) + m.safeCallResolvedCallback("test-alert", true) + }) + + t.Run("recovers from panic in sync callback", func(t *testing.T) { + t.Parallel() + m := NewManager() + + m.SetResolvedCallback(func(alertID string) { + panic("test panic") + }) + + // Should not panic the caller + m.safeCallResolvedCallback("panic-test", false) + }) + + t.Run("recovers from panic in async callback", func(t *testing.T) { + t.Parallel() + m := NewManager() + + done := make(chan struct{}) + m.SetResolvedCallback(func(alertID string) { + defer close(done) + panic("async panic") + }) + + m.safeCallResolvedCallback("async-panic", true) + + select { + case <-done: + // Callback ran (and panicked, but recovered) + case <-time.After(1 * time.Second): + t.Fatal("async callback not called within timeout") + } + }) +}