mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-04-28 19:41:17 +00:00
- Fix deadlock and race conditions in internal/alerts - Add comprehensive error path tests for internal/config - Fix 401 handling in internal/api - Fix Docker Swarm task filtering test logic
275 lines
7.5 KiB
Go
275 lines
7.5 KiB
Go
package alerts
|
|
|
|
import (
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func newManagerWithQuietHoursSuppress(s QuietHoursSuppression) *Manager {
|
|
m := NewManager()
|
|
m.config.Schedule.QuietHours = QuietHours{
|
|
Enabled: true,
|
|
Start: "00:00",
|
|
End: "23:59",
|
|
Timezone: "UTC",
|
|
Days: map[string]bool{
|
|
"monday": true,
|
|
"tuesday": true,
|
|
"wednesday": true,
|
|
"thursday": true,
|
|
"friday": true,
|
|
"saturday": true,
|
|
"sunday": true,
|
|
},
|
|
Suppress: s,
|
|
}
|
|
return m
|
|
}
|
|
|
|
func TestShouldSuppressNotificationQuietHours(t *testing.T) {
|
|
t.Run("non-critical alerts suppressed by default", func(t *testing.T) {
|
|
m := newManagerWithQuietHoursSuppress(QuietHoursSuppression{})
|
|
alert := &Alert{ID: "warn", Type: "cpu", Level: AlertLevelWarning}
|
|
suppressed, reason := m.shouldSuppressNotification(alert)
|
|
if !suppressed || reason != "non-critical" {
|
|
t.Fatalf("expected non-critical alert to be suppressed, got suppressed=%t reason=%q", suppressed, reason)
|
|
}
|
|
})
|
|
|
|
t.Run("critical offline alerts suppressed when configured", func(t *testing.T) {
|
|
m := newManagerWithQuietHoursSuppress(QuietHoursSuppression{Offline: true})
|
|
alert := &Alert{ID: "offline", Type: "connectivity", Level: AlertLevelCritical}
|
|
suppressed, reason := m.shouldSuppressNotification(alert)
|
|
if !suppressed || reason != "offline" {
|
|
t.Fatalf("expected offline alert suppression, got suppressed=%t reason=%q", suppressed, reason)
|
|
}
|
|
})
|
|
|
|
t.Run("critical performance alerts require opt-in", func(t *testing.T) {
|
|
m := newManagerWithQuietHoursSuppress(QuietHoursSuppression{})
|
|
alert := &Alert{ID: "perf", Type: "cpu", Level: AlertLevelCritical}
|
|
suppressed, reason := m.shouldSuppressNotification(alert)
|
|
if suppressed {
|
|
t.Fatalf("expected performance alert not to be suppressed, got reason=%q", reason)
|
|
}
|
|
})
|
|
|
|
t.Run("critical performance alerts suppressed when enabled", func(t *testing.T) {
|
|
m := newManagerWithQuietHoursSuppress(QuietHoursSuppression{Performance: true})
|
|
alert := &Alert{ID: "perf-enabled", Type: "cpu", Level: AlertLevelCritical}
|
|
suppressed, reason := m.shouldSuppressNotification(alert)
|
|
if !suppressed || reason != "performance" {
|
|
t.Fatalf("expected performance alert suppression, got suppressed=%t reason=%q", suppressed, reason)
|
|
}
|
|
})
|
|
|
|
t.Run("critical storage alerts suppressed when enabled", func(t *testing.T) {
|
|
m := newManagerWithQuietHoursSuppress(QuietHoursSuppression{Storage: true})
|
|
alert := &Alert{ID: "storage", Type: "usage", Level: AlertLevelCritical}
|
|
suppressed, reason := m.shouldSuppressNotification(alert)
|
|
if !suppressed || reason != "storage" {
|
|
t.Fatalf("expected storage alert suppression, got suppressed=%t reason=%q", suppressed, reason)
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestIsInQuietHours(t *testing.T) {
|
|
// t.Parallel()
|
|
|
|
t.Run("disabled returns false", func(t *testing.T) {
|
|
// t.Parallel()
|
|
m := NewManager()
|
|
m.mu.Lock()
|
|
m.config.Schedule.QuietHours.Enabled = false
|
|
m.mu.Unlock()
|
|
|
|
m.mu.RLock()
|
|
result := m.isInQuietHours()
|
|
m.mu.RUnlock()
|
|
|
|
if result {
|
|
t.Errorf("isInQuietHours() = true, want false when disabled")
|
|
}
|
|
})
|
|
|
|
t.Run("invalid timezone falls back to local", func(t *testing.T) {
|
|
// t.Parallel()
|
|
m := NewManager()
|
|
m.mu.Lock()
|
|
m.config.Schedule.QuietHours = QuietHours{
|
|
Enabled: true,
|
|
Start: "00:00",
|
|
End: "23:59",
|
|
Timezone: "Invalid/Timezone",
|
|
Days: map[string]bool{
|
|
"monday": true, "tuesday": true, "wednesday": true,
|
|
"thursday": true, "friday": true, "saturday": true, "sunday": true,
|
|
},
|
|
}
|
|
m.mu.Unlock()
|
|
|
|
// Should not panic, should fall back to local time
|
|
m.mu.RLock()
|
|
result := m.isInQuietHours()
|
|
m.mu.RUnlock()
|
|
|
|
// With all days enabled and 00:00-23:59, it should be true
|
|
if !result {
|
|
t.Errorf("isInQuietHours() = false, want true (invalid timezone should fall back to local)")
|
|
}
|
|
})
|
|
|
|
t.Run("day not enabled returns false", func(t *testing.T) {
|
|
// t.Parallel()
|
|
m := NewManager()
|
|
now := time.Now()
|
|
currentDay := now.Format("Monday")
|
|
|
|
m.mu.Lock()
|
|
m.config.Schedule.QuietHours = QuietHours{
|
|
Enabled: true,
|
|
Start: "00:00",
|
|
End: "23:59",
|
|
Timezone: "UTC",
|
|
Days: map[string]bool{}, // No days enabled
|
|
}
|
|
m.mu.Unlock()
|
|
|
|
m.mu.RLock()
|
|
result := m.isInQuietHours()
|
|
m.mu.RUnlock()
|
|
|
|
if result {
|
|
t.Errorf("isInQuietHours() = true, want false (day %s not enabled)", currentDay)
|
|
}
|
|
})
|
|
|
|
t.Run("invalid start time returns false", func(t *testing.T) {
|
|
// t.Parallel()
|
|
m := NewManager()
|
|
|
|
m.mu.Lock()
|
|
m.config.Schedule.QuietHours = QuietHours{
|
|
Enabled: true,
|
|
Start: "invalid",
|
|
End: "23:59",
|
|
Timezone: "UTC",
|
|
Days: map[string]bool{
|
|
"monday": true, "tuesday": true, "wednesday": true,
|
|
"thursday": true, "friday": true, "saturday": true, "sunday": true,
|
|
},
|
|
}
|
|
m.mu.Unlock()
|
|
|
|
m.mu.RLock()
|
|
result := m.isInQuietHours()
|
|
m.mu.RUnlock()
|
|
|
|
if result {
|
|
t.Errorf("isInQuietHours() = true, want false (invalid start time)")
|
|
}
|
|
})
|
|
|
|
t.Run("invalid end time returns false", func(t *testing.T) {
|
|
// t.Parallel()
|
|
m := NewManager()
|
|
|
|
m.mu.Lock()
|
|
m.config.Schedule.QuietHours = QuietHours{
|
|
Enabled: true,
|
|
Start: "00:00",
|
|
End: "invalid",
|
|
Timezone: "UTC",
|
|
Days: map[string]bool{
|
|
"monday": true, "tuesday": true, "wednesday": true,
|
|
"thursday": true, "friday": true, "saturday": true, "sunday": true,
|
|
},
|
|
}
|
|
m.mu.Unlock()
|
|
|
|
m.mu.RLock()
|
|
result := m.isInQuietHours()
|
|
m.mu.RUnlock()
|
|
|
|
if result {
|
|
t.Errorf("isInQuietHours() = true, want false (invalid end time)")
|
|
}
|
|
})
|
|
|
|
t.Run("overnight quiet hours spanning midnight", func(t *testing.T) {
|
|
// t.Parallel()
|
|
m := NewManager()
|
|
|
|
// Set up overnight quiet hours (22:00 to 06:00)
|
|
m.mu.Lock()
|
|
m.config.Schedule.QuietHours = QuietHours{
|
|
Enabled: true,
|
|
Start: "22:00",
|
|
End: "06:00", // End before start = overnight
|
|
Timezone: "UTC",
|
|
Days: map[string]bool{
|
|
"monday": true, "tuesday": true, "wednesday": true,
|
|
"thursday": true, "friday": true, "saturday": true, "sunday": true,
|
|
},
|
|
}
|
|
m.mu.Unlock()
|
|
|
|
// This tests the overnight branch - exact result depends on current time
|
|
// but should not panic
|
|
m.mu.RLock()
|
|
_ = m.isInQuietHours()
|
|
m.mu.RUnlock()
|
|
})
|
|
|
|
t.Run("normal daytime quiet hours", func(t *testing.T) {
|
|
// t.Parallel()
|
|
m := NewManager()
|
|
|
|
// Set up daytime quiet hours (09:00 to 17:00)
|
|
m.mu.Lock()
|
|
m.config.Schedule.QuietHours = QuietHours{
|
|
Enabled: true,
|
|
Start: "09:00",
|
|
End: "17:00",
|
|
Timezone: "UTC",
|
|
Days: map[string]bool{
|
|
"monday": true, "tuesday": true, "wednesday": true,
|
|
"thursday": true, "friday": true, "saturday": true, "sunday": true,
|
|
},
|
|
}
|
|
m.mu.Unlock()
|
|
|
|
// Should not panic, result depends on current time
|
|
m.mu.RLock()
|
|
_ = m.isInQuietHours()
|
|
m.mu.RUnlock()
|
|
})
|
|
|
|
t.Run("outside quiet hours window", func(t *testing.T) {
|
|
// t.Parallel()
|
|
m := NewManager()
|
|
|
|
// Use a time window that's definitely not now (narrow window in far past/future time)
|
|
// This creates a window from 03:00-03:01 which is unlikely to match current time
|
|
m.mu.Lock()
|
|
m.config.Schedule.QuietHours = QuietHours{
|
|
Enabled: true,
|
|
Start: "03:00",
|
|
End: "03:01", // 1 minute window
|
|
Timezone: "UTC",
|
|
Days: map[string]bool{
|
|
"monday": true, "tuesday": true, "wednesday": true,
|
|
"thursday": true, "friday": true, "saturday": true, "sunday": true,
|
|
},
|
|
}
|
|
m.mu.Unlock()
|
|
|
|
m.mu.RLock()
|
|
result := m.isInQuietHours()
|
|
m.mu.RUnlock()
|
|
|
|
// Very narrow window, likely false but depends on exact test execution time
|
|
// Main purpose is to exercise the "outside window" return false path
|
|
_ = result
|
|
})
|
|
}
|