diff --git a/internal/alerts/quiet_hours_test.go b/internal/alerts/quiet_hours_test.go index 133acd3..e56cecb 100644 --- a/internal/alerts/quiet_hours_test.go +++ b/internal/alerts/quiet_hours_test.go @@ -1,6 +1,9 @@ package alerts -import "testing" +import ( + "testing" + "time" +) func newManagerWithQuietHoursSuppress(s QuietHoursSuppression) *Manager { m := NewManager() @@ -69,3 +72,204 @@ func TestShouldSuppressNotificationQuietHours(t *testing.T) { } }) } + +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 + }) +}