package api import ( "encoding/json" "testing" "time" "github.com/ponzischeme89/memby/server/internal/sonarr" ) func TestBuildSonarrRowIncludesScheduleAndAddedState(t *testing.T) { location := time.FixedZone("NZST", 12*60*60) air := time.Date(2026, 7, 27, 8, 0, 0, 0, time.UTC) added := time.Date(2026, 7, 27, 8, 12, 0, 0, time.UTC) row, err := buildSonarrRow([]sonarr.Episode{{ ID: 42, SeriesID: 7, SeasonNumber: 2, EpisodeNumber: 4, Title: "The Crossing", AirDateUTC: &air, HasFile: true, Monitored: true, EpisodeFile: &sonarr.EpisodeFile{DateAdded: &added}, Series: sonarr.Series{ ID: 7, Title: "Northbound", Images: []sonarr.Image{ {CoverType: "poster"}, {CoverType: "fanart"}, }, }, }}, time.Date(2026, 7, 27, 21, 0, 0, 0, location), location) if err != nil { t.Fatal(err) } if row.ID != "sonarr-airing-today" || row.Kind != "schedule" || len(row.Items) != 1 { t.Fatalf("unexpected row: %+v", row) } var item sonarrScheduleItem if err := json.Unmarshal(row.Items[0], &item); err != nil { t.Fatal(err) } if item.ID != "sonarr:7:42" || item.MembyEpisodeCode != "S02E04" { t.Fatalf("unexpected identity: %+v", item) } if item.MembyAvailability != "available" || item.MembyAvailabilityText != "Added at 8:12 PM" { t.Fatalf("unexpected availability: %+v", item) } if item.ImageTags["Primary"] == "" || len(item.BackdropImageTags) != 1 { t.Fatalf("artwork was not exposed: %+v", item) } } func TestBuildPrerollScheduleSplitsTodayAndWeek(t *testing.T) { location := time.FixedZone("NZST", 12*60*60) now := time.Date(2026, 7, 28, 10, 0, 0, 0, location) today := time.Date(2026, 7, 28, 20, 30, 0, 0, location).UTC() thursday := time.Date(2026, 7, 30, 19, 0, 0, 0, location).UTC() schedule := buildPrerollSchedule([]sonarr.Episode{ { ID: 14, SeriesID: 7, SeasonNumber: 1, EpisodeNumber: 4, Title: "Tonight", AirDateUTC: &today, Series: sonarr.Series{ ID: 7, Title: "Northbound", Images: []sonarr.Image{{CoverType: "fanart"}}, }, }, { SeasonNumber: 2, EpisodeNumber: 1, Title: "Later", AirDateUTC: &thursday, HasFile: true, Series: sonarr.Series{Title: "Harbour"}, }, }, now, location) if len(schedule.Today) != 1 || schedule.Today[0].Series != "Northbound" || schedule.Today[0].Schedule != "8:30 PM" || schedule.Today[0].ItemID != "sonarr:7:14" || schedule.Today[0].ImageType != "backdrop" { t.Fatalf("unexpected today schedule: %+v", schedule.Today) } if len(schedule.ThisWeek) != 1 || schedule.ThisWeek[0].EpisodeCode != "S02E01" || schedule.ThisWeek[0].Availability != "Downloaded" { t.Fatalf("unexpected week schedule: %+v", schedule.ThisWeek) } } func TestBuildSonarrRowCoversFiveDaysAndUsesRelativeAirLabels(t *testing.T) { location := time.FixedZone("NZST", 12*60*60) now := time.Date(2026, 7, 29, 8, 0, 0, 0, location) episode := func(id int, air time.Time) sonarr.Episode { utc := air.UTC() return sonarr.Episode{ ID: id, SeriesID: id, SeasonNumber: 1, EpisodeNumber: id, Title: "Episode", AirDateUTC: &utc, Series: sonarr.Series{ID: id, Title: "Show"}, } } row, err := buildSonarrRow([]sonarr.Episode{ episode(1, time.Date(2026, 7, 29, 16, 0, 0, 0, location)), episode(2, time.Date(2026, 7, 30, 16, 0, 0, 0, location)), episode(3, time.Date(2026, 8, 2, 16, 0, 0, 0, location)), episode(4, time.Date(2026, 8, 3, 16, 0, 0, 0, location)), }, now, location) if err != nil { t.Fatal(err) } if row.Title != "Shows airing in the next 5 days" || len(row.Items) != 3 { t.Fatalf("five-day row = %+v", row) } labels := make([]string, 0, len(row.Items)) dayLabels := make([]string, 0, len(row.Items)) for _, raw := range row.Items { var item sonarrScheduleItem if err := json.Unmarshal(raw, &item); err != nil { t.Fatal(err) } labels = append(labels, item.MembyAirLabel) dayLabels = append(dayLabels, item.MembyAirDayLabel) } want := []string{ "In 8 hours (4:00 PM)", "Tomorrow: 4:00 PM", "Sunday: 4:00 PM", } for i := range want { if labels[i] != want[i] { t.Fatalf("labels = %#v, want %#v", labels, want) } } wantDays := []string{"Today", "Tomorrow", "Sunday"} for i := range wantDays { if dayLabels[i] != wantDays[i] { t.Fatalf("day labels = %#v, want %#v", dayLabels, wantDays) } } }