150 lines
5.3 KiB
Go
150 lines
5.3 KiB
Go
package api
|
|||
|
|
|
||
|
|
import (
|
||
|
|
"encoding/json"
|
||
|
|
"testing"
|
||
|
|
"time"
|
||
|
|
|
||
|
|
"github.com/ponzischeme89/memby/server/internal/sonarr"
|
||
|
|
)
|
||
|
|
|
||
|
|
func calendarEpisode(id int, air time.Time, title string) sonarr.Episode {
|
||
|
|
utc := air.UTC()
|
||
|
|
return sonarr.Episode{
|
||
|
|
ID: id,
|
||
|
|
SeriesID: id,
|
||
|
|
SeasonNumber: 1,
|
||
|
|
EpisodeNumber: id,
|
||
|
|
Title: title,
|
||
|
|
AirDateUTC: &utc,
|
||
|
|
Monitored: true,
|
||
|
|
Series: sonarr.Series{ID: id, Title: title},
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestBuildCalendarMonthDescribesTheGrid(t *testing.T) {
|
||
|
|
location := time.UTC
|
||
|
|
// August 2026 begins on a Saturday and runs 31 days.
|
||
|
|
month := time.Date(2026, 8, 1, 0, 0, 0, 0, location)
|
||
|
|
now := time.Date(2026, 8, 11, 9, 0, 0, 0, location)
|
||
|
|
|
||
|
|
calendar := buildCalendarMonth(nil, month, now, location, nil)
|
||
|
|
|
||
|
|
if !calendar.Available {
|
||
|
|
t.Fatal("a configured Sonarr must report the month as available")
|
||
|
|
}
|
||
|
|
if calendar.Month != "2026-08" || calendar.Label != "August 2026" {
|
||
|
|
t.Fatalf("unexpected identity: %+v", calendar)
|
||
|
|
}
|
||
|
|
if calendar.FirstWeekday != int(time.Saturday) || calendar.DayCount != 31 {
|
||
|
|
t.Fatalf("unexpected grid shape: %+v", calendar)
|
||
|
|
}
|
||
|
|
if calendar.Previous != "2026-07" || calendar.Next != "2026-09" {
|
||
|
|
t.Fatalf("unexpected neighbours: %+v", calendar)
|
||
|
|
}
|
||
|
|
if calendar.Today != "2026-08-11" {
|
||
|
|
t.Fatalf("today was not marked: %+v", calendar)
|
||
|
|
}
|
||
|
|
if calendar.Days == nil {
|
||
|
|
t.Fatal("days must encode as an array rather than null")
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestBuildCalendarMonthCountsFebruaryInALeapYear(t *testing.T) {
|
||
|
|
location := time.UTC
|
||
|
|
now := time.Date(2028, 2, 1, 0, 0, 0, 0, location)
|
||
|
|
calendar := buildCalendarMonth(nil, now, now, location, nil)
|
||
|
|
if calendar.DayCount != 29 {
|
||
|
|
t.Fatalf("expected 29 days, got %d", calendar.DayCount)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestBuildCalendarMonthOmitsTodayOutsideTheMonth(t *testing.T) {
|
||
|
|
location := time.UTC
|
||
|
|
month := time.Date(2026, 10, 1, 0, 0, 0, 0, location)
|
||
|
|
now := time.Date(2026, 8, 11, 9, 0, 0, 0, location)
|
||
|
|
if today := buildCalendarMonth(nil, month, now, location, nil).Today; today != "" {
|
||
|
|
t.Fatalf("a month the household is not in must carry no today: %q", today)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// The arrows are what stop a held D-pad walking Sonarr into the next decade, so the edge of
|
||
|
|
// the range must be reported as having no neighbour rather than as one more request.
|
||
|
|
func TestBuildCalendarMonthStopsAtTheTravelLimit(t *testing.T) {
|
||
|
|
location := time.UTC
|
||
|
|
now := time.Date(2026, 8, 11, 9, 0, 0, 0, location)
|
||
|
|
last := time.Date(2027, 8, 1, 0, 0, 0, 0, location)
|
||
|
|
if next := buildCalendarMonth(nil, last, now, location, nil).Next; next != "" {
|
||
|
|
t.Fatalf("expected no next month at the limit, got %q", next)
|
||
|
|
}
|
||
|
|
first := time.Date(2025, 8, 1, 0, 0, 0, 0, location)
|
||
|
|
if previous := buildCalendarMonth(nil, first, now, location, nil).Previous; previous != "" {
|
||
|
|
t.Fatalf("expected no previous month at the limit, got %q", previous)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestBuildCalendarMonthGroupsEpisodesByLocalDay(t *testing.T) {
|
||
|
|
location := time.FixedZone("NZST", 12*60*60)
|
||
|
|
month := time.Date(2026, 8, 1, 0, 0, 0, 0, location)
|
||
|
|
now := time.Date(2026, 8, 11, 9, 0, 0, 0, location)
|
||
|
|
|
||
|
|
// Two on the 12th local, one on the 13th, and one that is the 31st in UTC but already
|
||
|
|
// September where the household lives.
|
||
|
|
episodes := []sonarr.Episode{
|
||
|
|
calendarEpisode(2, time.Date(2026, 8, 12, 21, 0, 0, 0, location), "Harbour"),
|
||
|
|
calendarEpisode(1, time.Date(2026, 8, 12, 20, 0, 0, 0, location), "Northbound"),
|
||
|
|
calendarEpisode(3, time.Date(2026, 8, 13, 20, 0, 0, 0, location), "Deep Water"),
|
||
|
|
calendarEpisode(4, time.Date(2026, 8, 31, 20, 0, 0, 0, time.UTC), "Rolled Over"),
|
||
|
|
}
|
||
|
|
|
||
|
|
calendar := buildCalendarMonth(episodes, month, now, location, nil)
|
||
|
|
|
||
|
|
if len(calendar.Days) != 2 {
|
||
|
|
t.Fatalf("expected two populated days, got %d: %+v", len(calendar.Days), calendar.Days)
|
||
|
|
}
|
||
|
|
if calendar.Days[0].Date != "2026-08-12" || calendar.Days[0].Day != 12 {
|
||
|
|
t.Fatalf("unexpected first day: %+v", calendar.Days[0])
|
||
|
|
}
|
||
|
|
if len(calendar.Days[0].Items) != 2 {
|
||
|
|
t.Fatalf("expected two episodes on the 12th: %+v", calendar.Days[0])
|
||
|
|
}
|
||
|
|
// Within a day the order is Sonarr's air time, which is the order they will be watched.
|
||
|
|
var first sonarrScheduleItem
|
||
|
|
if err := json.Unmarshal(calendar.Days[0].Items[0], &first); err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
if first.Name != "Northbound" {
|
||
|
|
t.Fatalf("episodes within a day must be ordered by air time: %+v", first)
|
||
|
|
}
|
||
|
|
if first.Type != "MembySonarrEpisode" || first.MembyPlayable {
|
||
|
|
t.Fatalf("a calendar card must be the schedule row's informational item: %+v", first)
|
||
|
|
}
|
||
|
|
if calendar.Days[1].Date != "2026-08-13" {
|
||
|
|
t.Fatalf("unexpected second day: %+v", calendar.Days[1])
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestParseCalendarMonthDefaultsToThePresent(t *testing.T) {
|
||
|
|
location := time.UTC
|
||
|
|
now := time.Date(2026, 8, 11, 9, 0, 0, 0, location)
|
||
|
|
month, ok := parseCalendarMonth(" ", now, location)
|
||
|
|
if !ok || month.Format("2006-01") != "2026-08" {
|
||
|
|
t.Fatalf("unexpected default month: %v %v", month, ok)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestParseCalendarMonthRefusesNonsenseAndDistantMonths(t *testing.T) {
|
||
|
|
location := time.UTC
|
||
|
|
now := time.Date(2026, 8, 11, 9, 0, 0, 0, location)
|
||
|
|
for _, value := range []string{"2026-13", "August", "2026-08-11", "2031-01", "2020-01"} {
|
||
|
|
if _, ok := parseCalendarMonth(value, now, location); ok {
|
||
|
|
t.Fatalf("expected %q to be refused", value)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
for _, value := range []string{"2026-09", "2027-08", "2025-08"} {
|
||
|
|
if _, ok := parseCalendarMonth(value, now, location); !ok {
|
||
|
|
t.Fatalf("expected %q to be accepted", value)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|