46c8edfa82
Builds the per-user daily-at-03:00-local scheduler for #392 Half B. Uses github.com/go-co-op/gocron/v2 with WithLocation(userTZ) for each user's job. Hourly reconciliation pass keeps the in-memory job set in sync with the active-users query. Start sequence: clear stale in_flight rows; register a daily job for every active user at their stored timezone; fire a one-shot runOnce to catch up missed schedules during downtime; start gocron. Stop drains and shuts down the gocron loop. Refresh(ctx, userID) removes the user's existing job (if any) and registers a fresh one at their current timezone — wired into the PUT /api/me/timezone handler and new-user registration in the next commit. Server struct gains PlaylistScheduler; main.go constructs it after the eventbus and threads it through to api.Mount. The existing StartSystemPlaylistCron call stays for one more commit so we don't have a window where no scheduler is running; Task 3 deletes it. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
52 lines
1.4 KiB
Go
52 lines
1.4 KiB
Go
package playlists
|
|
|
|
import (
|
|
"testing"
|
|
)
|
|
|
|
func TestValidateTimezone_Valid(t *testing.T) {
|
|
cases := []string{"UTC", "America/New_York", "Europe/London", "Asia/Tokyo"}
|
|
for _, tz := range cases {
|
|
t.Run(tz, func(t *testing.T) {
|
|
loc, err := validateTimezone(tz)
|
|
if err != nil {
|
|
t.Fatalf("validateTimezone(%q): %v", tz, err)
|
|
}
|
|
if loc == nil {
|
|
t.Fatalf("validateTimezone(%q): nil location", tz)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestValidateTimezone_Invalid(t *testing.T) {
|
|
cases := []string{"", "Not/A/Zone", "GMT+5", "EST5EDT-not-iana"}
|
|
for _, tz := range cases {
|
|
t.Run(tz, func(t *testing.T) {
|
|
_, err := validateTimezone(tz)
|
|
if err == nil {
|
|
t.Fatalf("validateTimezone(%q): expected error, got nil", tz)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestValidateTimezoneOrUTC_FallbackToUTC(t *testing.T) {
|
|
// validateTimezoneOrUTC returns UTC for any unparseable input.
|
|
// Used by the scheduler when reading stored timezones — corrupted
|
|
// DB values should not crash; they should fall back to UTC.
|
|
loc := validateTimezoneOrUTC("Not/A/Zone")
|
|
if loc.String() != "UTC" {
|
|
t.Errorf("expected UTC fallback for invalid input, got %s", loc)
|
|
}
|
|
|
|
// Valid input returns the named location, not UTC.
|
|
loc = validateTimezoneOrUTC("America/New_York")
|
|
if loc.String() == "UTC" {
|
|
t.Errorf("valid input incorrectly fell back to UTC")
|
|
}
|
|
if loc.String() != "America/New_York" {
|
|
t.Errorf("expected America/New_York, got %s", loc)
|
|
}
|
|
}
|