package callwindow import ( "encoding/json" "os" "strings" "testing" "time" ) func week(windows ...Window) map[string][]Window { return map[string][]Window{ "monday": windows, "tuesday": windows, "wednesday": windows, "thursday": windows, "friday": windows, "saturday": windows, "sunday": windows, } } func atShanghai(t *testing.T, dateTime string) time.Time { t.Helper() at, err := time.Parse(time.RFC3339, dateTime) if err != nil { t.Fatal(err) } return at } func TestEvaluateTaskAndLineIntersectionAtBoundaries(t *testing.T) { task := TaskSchedule{WeeklySchedule: WeeklySchedule{TimeZone: "Asia/Shanghai", WeeklyWindows: week(Window{"09:00", "20:00"})}} line := WeeklySchedule{TimeZone: "Asia/Shanghai", WeeklyWindows: week(Window{"10:00", "18:00"})} for _, tc := range []struct { when, end string allow bool }{ {"2026-09-21T09:59:59+08:00", "", false}, {"2026-09-21T10:00:00+08:00", "2026-09-21T18:00:00+08:00", true}, {"2026-09-21T17:59:59+08:00", "2026-09-21T18:00:00+08:00", true}, {"2026-09-21T18:00:00+08:00", "", false}, } { end, err := Evaluate(task, line, atShanghai(t, tc.when)) if (err == nil) != tc.allow { t.Errorf("at %s: end=%v err=%v allow=%v", tc.when, end, err, tc.allow) } else if tc.allow && !end.Equal(atShanghai(t, tc.end)) { t.Errorf("at %s: end=%v, want %s", tc.when, end, tc.end) } } } func TestEvaluateActualTaskAndLineConfigShape(t *testing.T) { readSchedule := func(path string, dest any) { t.Helper() body, err := os.ReadFile(path) if err != nil { t.Fatal(err) } if err := json.Unmarshal(body, dest); err != nil { t.Fatal(err) } } var task struct { Schedule TaskSchedule `json:"schedule"` } readSchedule("../../docs/contracts/examples/config-read-task-v0.1.json", &task) var sip struct { TrunkDetails []struct { Schedule WeeklySchedule `json:"schedule"` } `json:"trunk_details"` } readSchedule("../../docs/contracts/examples/config-read-sip-v0.1.json", &sip) if len(sip.TrunkDetails) != 1 { t.Fatalf("line sample count=%d", len(sip.TrunkDetails)) } end, err := Evaluate(task.Schedule, sip.TrunkDetails[0].Schedule, atShanghai(t, "2026-09-21T09:30:00+08:00")) if err != nil || !end.Equal(atShanghai(t, "2026-09-21T11:00:00+08:00")) { t.Fatalf("actual task and line schedules: end=%v err=%v", end, err) } } func TestEvaluateCrossMidnightSplitAndExcludedDate(t *testing.T) { weekly := week() weekly["monday"] = []Window{{"23:00", "24:00"}} weekly["tuesday"] = []Window{{"00:00", "01:00"}} task := TaskSchedule{WeeklySchedule: WeeklySchedule{TimeZone: "Asia/Shanghai", WeeklyWindows: weekly}} line := WeeklySchedule{TimeZone: "Asia/Shanghai", WeeklyWindows: weekly} for _, tc := range []struct{ when, end string }{ {"2026-09-21T23:59:59+08:00", "2026-09-22T00:00:00+08:00"}, {"2026-09-22T00:00:00+08:00", "2026-09-22T01:00:00+08:00"}, } { end, err := Evaluate(task, line, atShanghai(t, tc.when)) if err != nil || !end.Equal(atShanghai(t, tc.end)) { t.Fatalf("cross-midnight at %s: end=%v err=%v", tc.when, end, err) } } task.ExcludedDates = []string{"2026-09-22"} if _, err := Evaluate(task, line, atShanghai(t, "2026-09-22T00:00:00+08:00")); err == nil { t.Fatal("excluded Shanghai calendar date allowed") } } func TestEvaluateTaskValidityAndAuthorizationBounds(t *testing.T) { task := TaskSchedule{WeeklySchedule: WeeklySchedule{TimeZone: "Asia/Shanghai", WeeklyWindows: week(Window{"09:00", "20:00"})}, StartsAt: "2026-09-21T10:00:00+08:00", EndsAt: "2026-09-21T11:00:00+08:00"} line := WeeklySchedule{TimeZone: "Asia/Shanghai", WeeklyWindows: week(Window{"09:00", "20:00"})} if _, err := Evaluate(task, line, atShanghai(t, "2026-09-21T09:59:59+08:00")); err == nil { t.Fatal("before task validity allowed") } end, err := Evaluate(task, line, atShanghai(t, "2026-09-21T10:00:00+08:00")) if err != nil || !end.Equal(atShanghai(t, "2026-09-21T11:00:00+08:00")) { t.Fatalf("task start/end: %v %v", end, err) } if _, err := Evaluate(task, line, atShanghai(t, "2026-09-21T11:00:00+08:00")); err == nil { t.Fatal("at task validity end allowed") } } func TestEvaluateRejectsInvalidOrAmbiguousSchedules(t *testing.T) { for _, tc := range []struct { name string windows []Window }{ {"cross-day unsplit", []Window{{"23:00", "01:00"}}}, {"overlap", []Window{{"09:00", "11:00"}, {"10:00", "12:00"}}}, {"24:00 start", []Window{{"24:00", "24:00"}}}, {"non-time", []Window{{"9:00", "20:00"}}}, } { t.Run(tc.name, func(t *testing.T) { bad := TaskSchedule{WeeklySchedule: WeeklySchedule{TimeZone: "Asia/Shanghai", WeeklyWindows: week(tc.windows...)}} line := WeeklySchedule{TimeZone: "Asia/Shanghai", WeeklyWindows: week(Window{"09:00", "20:00"})} if _, err := Evaluate(bad, line, atShanghai(t, "2026-09-21T10:00:00+08:00")); err == nil { t.Fatal("invalid schedule allowed") } }) } badDate := TaskSchedule{WeeklySchedule: WeeklySchedule{TimeZone: "Asia/Shanghai", WeeklyWindows: week(Window{"09:00", "20:00"})}, ExcludedDates: []string{"2026-02-30"}} if _, err := Evaluate(badDate, badDate.WeeklySchedule, atShanghai(t, "2026-09-21T10:00:00+08:00")); err == nil || !strings.Contains(err.Error(), "date") { t.Fatalf("invalid excluded date: %v", err) } badZone := badDate badZone.ExcludedDates = nil badZone.TimeZone = "UTC" if _, err := Evaluate(badZone, badDate.WeeklySchedule, atShanghai(t, "2026-09-21T10:00:00+08:00")); err == nil { t.Fatal("wrong timezone allowed") } }