2026-10-18 17:40:41 +02:00
|
|
|
package queue
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"testing"
|
|
|
|
|
"time"
|
|
|
|
|
)
|
|
|
|
|
|
2026-10-24 11:15:35 +02:00
|
|
|
// local builds a local test time (hour and minute suffice; quiet windows
|
|
|
|
|
// work at minute resolution).
|
|
|
|
|
func local(hour, minute int) time.Time {
|
2026-10-18 17:40:41 +02:00
|
|
|
return time.Date(2026, 10, 18, hour, minute, 0, 0, time.Local)
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-24 11:15:35 +02:00
|
|
|
// TestParseQuietHours: format HH:MM-HH:MM (may span midnight), clear errors
|
|
|
|
|
// for garbage.
|
2026-10-18 17:40:41 +02:00
|
|
|
func TestParseQuietHours(t *testing.T) {
|
|
|
|
|
cases := []struct {
|
|
|
|
|
in string
|
|
|
|
|
start clockTime
|
|
|
|
|
end clockTime
|
|
|
|
|
bad bool
|
|
|
|
|
}{
|
|
|
|
|
{in: "18:00-08:00", start: clockTime{18, 0}, end: clockTime{8, 0}},
|
|
|
|
|
{in: "9:30-17:45", start: clockTime{9, 30}, end: clockTime{17, 45}},
|
|
|
|
|
{in: "22:00-06:00", start: clockTime{22, 0}, end: clockTime{6, 0}},
|
2026-10-24 11:15:35 +02:00
|
|
|
{in: "08:00-08:00", start: clockTime{8, 0}, end: clockTime{8, 0}}, // disabled but valid
|
|
|
|
|
{in: ""}, // empty = disabled, no error
|
2026-10-18 17:40:41 +02:00
|
|
|
{in: "18:00", bad: true},
|
|
|
|
|
{in: "18:00-", bad: true},
|
|
|
|
|
{in: "-08:00", bad: true},
|
|
|
|
|
{in: "24:00-08:00", bad: true},
|
|
|
|
|
{in: "18:60-08:00", bad: true},
|
|
|
|
|
{in: "1800-0800", bad: true},
|
|
|
|
|
{in: "ab:00-08:00", bad: true},
|
|
|
|
|
}
|
|
|
|
|
for _, tc := range cases {
|
|
|
|
|
q, err := ParseQuietHours(tc.in)
|
|
|
|
|
if tc.bad {
|
|
|
|
|
if err == nil {
|
2026-10-24 11:15:35 +02:00
|
|
|
t.Errorf("ParseQuietHours(%q) = %+v, want error", tc.in, q)
|
2026-10-18 17:40:41 +02:00
|
|
|
}
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
if err != nil {
|
2026-10-24 11:15:35 +02:00
|
|
|
t.Errorf("ParseQuietHours(%q): unexpected error %v", tc.in, err)
|
2026-10-18 17:40:41 +02:00
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
if q.Start != tc.start || q.End != tc.end {
|
|
|
|
|
t.Errorf("ParseQuietHours(%q) = %v-%v, want %v-%v", tc.in, q.Start, q.End, tc.start, tc.end)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-24 11:15:35 +02:00
|
|
|
// TestQuietHoursActive: day window, night window across midnight and the
|
|
|
|
|
// disabled zero value across all boundaries (start inclusive, end
|
|
|
|
|
// exclusive).
|
2026-10-18 17:40:41 +02:00
|
|
|
func TestQuietHoursActive(t *testing.T) {
|
|
|
|
|
cases := []struct {
|
2026-10-24 11:15:35 +02:00
|
|
|
window string
|
2026-10-18 17:40:41 +02:00
|
|
|
hour, minute int
|
|
|
|
|
want bool
|
|
|
|
|
}{
|
2026-10-24 11:15:35 +02:00
|
|
|
// day window 09:00-17:00
|
2026-10-18 17:40:41 +02:00
|
|
|
{"09:00-17:00", 8, 59, false},
|
2026-10-24 11:15:35 +02:00
|
|
|
{"09:00-17:00", 9, 0, true}, // start inclusive
|
2026-10-18 17:40:41 +02:00
|
|
|
{"09:00-17:00", 12, 30, true},
|
|
|
|
|
{"09:00-17:00", 16, 59, true},
|
2026-10-24 11:15:35 +02:00
|
|
|
{"09:00-17:00", 17, 0, false}, // end exclusive
|
2026-10-18 17:40:41 +02:00
|
|
|
{"09:00-17:00", 23, 0, false},
|
2026-10-24 11:15:35 +02:00
|
|
|
// night window 18:00-08:00 (spans midnight)
|
2026-10-18 17:40:41 +02:00
|
|
|
{"18:00-08:00", 17, 59, false},
|
|
|
|
|
{"18:00-08:00", 18, 0, true},
|
|
|
|
|
{"18:00-08:00", 23, 59, true},
|
2026-10-24 11:15:35 +02:00
|
|
|
{"18:00-08:00", 0, 0, true}, // past midnight
|
2026-10-18 17:40:41 +02:00
|
|
|
{"18:00-08:00", 7, 59, true},
|
2026-10-24 11:15:35 +02:00
|
|
|
{"18:00-08:00", 8, 0, false}, // end exclusive
|
2026-10-18 17:40:41 +02:00
|
|
|
{"18:00-08:00", 12, 0, false},
|
2026-10-24 11:15:35 +02:00
|
|
|
// another night window
|
2026-10-18 17:40:41 +02:00
|
|
|
{"22:00-06:00", 3, 0, true},
|
|
|
|
|
{"22:00-06:00", 21, 59, false},
|
2026-10-24 11:15:35 +02:00
|
|
|
// start == end: disabled
|
2026-10-18 17:40:41 +02:00
|
|
|
{"08:00-08:00", 8, 0, false},
|
|
|
|
|
{"08:00-08:00", 20, 0, false},
|
|
|
|
|
}
|
|
|
|
|
for _, tc := range cases {
|
2026-10-24 11:15:35 +02:00
|
|
|
q := mustQuiet(t, tc.window)
|
|
|
|
|
if got := q.Active(local(tc.hour, tc.minute)); got != tc.want {
|
|
|
|
|
t.Errorf("%s at %02d:%02d: Active = %v, want %v", tc.window, tc.hour, tc.minute, got, tc.want)
|
2026-10-18 17:40:41 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-24 11:15:35 +02:00
|
|
|
// TestQuietHoursUntil: window end (today, or tomorrow for night windows),
|
|
|
|
|
// zero time when inactive.
|
2026-10-18 17:40:41 +02:00
|
|
|
func TestQuietHoursUntil(t *testing.T) {
|
2026-10-24 11:15:35 +02:00
|
|
|
// Day window: ends today.
|
2026-10-18 17:40:41 +02:00
|
|
|
q := mustQuiet(t, "09:00-17:00")
|
2026-10-24 11:15:35 +02:00
|
|
|
if want := local(17, 0); !q.Until(local(12, 0)).Equal(want) {
|
|
|
|
|
t.Errorf("Until(12:00) = %v, want %v (today 17:00)", q.Until(local(12, 0)), want)
|
2026-10-18 17:40:41 +02:00
|
|
|
}
|
2026-10-24 11:15:35 +02:00
|
|
|
if !q.Until(local(3, 0)).IsZero() {
|
|
|
|
|
t.Error("Until outside the window: want zero time")
|
2026-10-18 17:40:41 +02:00
|
|
|
}
|
|
|
|
|
|
2026-10-24 11:15:35 +02:00
|
|
|
// Night window: from 18:00 the end is tomorrow morning.
|
2026-10-18 17:40:41 +02:00
|
|
|
n := mustQuiet(t, "18:00-08:00")
|
2026-10-24 11:15:35 +02:00
|
|
|
if want := local(0, 0).Add(32 * time.Hour); !n.Until(local(20, 0)).Equal(want) {
|
|
|
|
|
t.Errorf("Until(20:00) = %v, want %v (tomorrow 08:00)", n.Until(local(20, 0)), want)
|
2026-10-18 17:40:41 +02:00
|
|
|
}
|
2026-10-24 11:15:35 +02:00
|
|
|
// Past midnight (00:30) the end is today 08:00.
|
|
|
|
|
if want := local(8, 0); !n.Until(local(0, 30)).Equal(want) {
|
|
|
|
|
t.Errorf("Until(00:30) = %v, want %v (today 08:00)", n.Until(local(0, 30)), want)
|
2026-10-18 17:40:41 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-24 11:15:35 +02:00
|
|
|
// TestQuietHoursStringRoundtrip: String returns the configured form and
|
|
|
|
|
// parses back identically; the zero value renders empty.
|
2026-10-18 17:40:41 +02:00
|
|
|
func TestQuietHoursStringRoundtrip(t *testing.T) {
|
|
|
|
|
for _, s := range []string{"18:00-08:00", "09:00-17:00", "22:00-06:00"} {
|
|
|
|
|
q := mustQuiet(t, s)
|
|
|
|
|
if q.String() != s {
|
|
|
|
|
t.Errorf("String() = %q, want %q", q.String(), s)
|
|
|
|
|
}
|
|
|
|
|
q2 := mustQuiet(t, q.String())
|
|
|
|
|
if q2 != q {
|
2026-10-24 11:15:35 +02:00
|
|
|
t.Errorf("round trip of %q: %+v != %+v", s, q2, q)
|
2026-10-18 17:40:41 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (QuietHours{}).String() != "" {
|
2026-10-24 11:15:35 +02:00
|
|
|
t.Error("zero value String is not empty")
|
2026-10-18 17:40:41 +02:00
|
|
|
}
|
|
|
|
|
}
|