package config import ( "testing" "time" ) func at(hour, min int) time.Time { return time.Date(2026, 1, 1, hour, min, 0, 0, time.UTC) } func TestParseWindowEmpty(t *testing.T) { w, err := ParseWindow("") if err != nil { t.Fatalf("empty window: %v", err) } if !w.AlwaysOpen() { t.Error("empty input should produce AlwaysOpen window") } if !w.Contains(at(3, 0)) { t.Error("AlwaysOpen window should contain any time") } if !w.Contains(at(23, 59)) { t.Error("AlwaysOpen window should contain end-of-day") } } func TestParseWindowSameDay(t *testing.T) { w, err := ParseWindow("03:00-05:00") if err != nil { t.Fatalf("parse: %v", err) } tests := []struct { hour, min int want bool }{ {2, 59, false}, // just before {3, 0, true}, // start (inclusive) {4, 30, true}, // middle {4, 59, true}, // just before end {5, 0, false}, // end (exclusive) {15, 0, false}, // far outside } for _, tt := range tests { got := w.Contains(at(tt.hour, tt.min)) if got != tt.want { t.Errorf("Contains(%02d:%02d) = %v, want %v", tt.hour, tt.min, got, tt.want) } } } func TestParseWindowWrappingMidnight(t *testing.T) { w, err := ParseWindow("23:00-01:00") if err != nil { t.Fatalf("parse: %v", err) } tests := []struct { hour, min int want bool }{ {22, 59, false}, // just before {23, 0, true}, // start (inclusive) {23, 30, true}, // night-before {0, 0, true}, // midnight {0, 30, true}, // early morning {0, 59, true}, // just before end {1, 0, false}, // end (exclusive) {12, 0, false}, // far outside (noon) } for _, tt := range tests { got := w.Contains(at(tt.hour, tt.min)) if got != tt.want { t.Errorf("Contains(%02d:%02d) wrapping = %v, want %v", tt.hour, tt.min, got, tt.want) } } } func TestParseWindowDegenerateZeroLength(t *testing.T) { // 05:00-05:00 is a zero-length window — should never match. Different // from "always" (empty string). w, err := ParseWindow("05:00-05:00") if err != nil { t.Fatalf("parse: %v", err) } if w.AlwaysOpen() { t.Error("05:00-05:00 must not be AlwaysOpen") } if w.Contains(at(5, 0)) { t.Error("zero-length window must not contain its own boundary") } } func TestParseWindowRejectsBadInput(t *testing.T) { bad := []string{ "notatime", "03:00", // no end "03:00-", // empty end "03:00-05", // missing minutes "24:00-05:00", // hour out of range "03:60-05:00", // minute out of range "abc:00-05:00", // non-numeric } for _, s := range bad { _, err := ParseWindow(s) if err == nil { t.Errorf("ParseWindow(%q) accepted, want error", s) } } } func TestWindowString(t *testing.T) { w, _ := ParseWindow("03:05-05:45") if w.String() != "03:05-05:45" { t.Errorf("String = %q, want 03:05-05:45", w.String()) } always, _ := ParseWindow("") if always.String() != "always" { t.Errorf("AlwaysOpen.String = %q, want 'always'", always.String()) } }