Files
go-sip/internal/callwindow/policy_test.go
T

144 lines
5.4 KiB
Go

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")
}
}