Files
memby/server/internal/api/sonarr_test.go
T
ponzischeme89andClaude Opus 5 4a4df7a73c App v0.2.26 and gateway 0.1.20
Client: seek controls, Bazarr subtitle download and cast panel in the
player; MDBList ratings strip; episode and schedule detail pages; series
pace estimate; what's new panel; install-permission onboarding step;
synced per-profile preferences; Emby outage banner.

Gateway: rebuilt admin console (one fragment per page), preference
history and restore, merged Continue Watching, Emby health probe,
subtitle selection and Bazarr download, structured request logging with
per-request identity, and embedded build version.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-06 22:33:56 +12:00

180 lines
5.9 KiB
Go

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, nil)
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 TestBuildSonarrRowLinksTheEmbySeries(t *testing.T) {
location := time.UTC
now := time.Date(2026, 7, 27, 8, 0, 0, 0, location)
air := time.Date(2026, 7, 27, 20, 0, 0, 0, location)
index := seriesIndex{
"northbound": "emby-old",
"northbound|2024": "emby-2024",
"harbour": "emby-harbour",
}
episode := func(title string, year int) sonarr.Episode {
utc := air
return sonarr.Episode{
ID: 1, SeriesID: 1, SeasonNumber: 1, EpisodeNumber: 1, AirDateUTC: &utc,
Series: sonarr.Series{ID: 1, Title: title, Year: year},
}
}
cases := map[string]struct {
episode sonarr.Episode
want string
}{
// The year decides between a remake and its original.
"year qualified": {episode("Northbound", 2024), "emby-2024"},
// Sonarr and Emby disagree about a year more often than about a name.
"year mismatch falls back to the title": {episode("Northbound", 1999), "emby-old"},
"punctuation and case are ignored": {episode("Harbour!", 0), "emby-harbour"},
"not in the library": {episode("Unimported", 2026), ""},
}
for name, testCase := range cases {
t.Run(name, func(t *testing.T) {
row, err := buildSonarrRow([]sonarr.Episode{testCase.episode}, now, location, index)
if err != nil {
t.Fatal(err)
}
var item sonarrScheduleItem
if err := json.Unmarshal(row.Items[0], &item); err != nil {
t.Fatal(err)
}
if item.MembySeriesItemID != testCase.want {
t.Fatalf("series id = %q, want %q", item.MembySeriesItemID, testCase.want)
}
})
}
}
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{
// Sonarr's response order is not part of its contract. Deliberately put the
// later episode first to ensure the row is authored chronologically.
episode(3, time.Date(2026, 8, 2, 16, 0, 0, 0, location)),
episode(2, time.Date(2026, 7, 30, 16, 0, 0, 0, location)),
episode(1, time.Date(2026, 7, 29, 16, 0, 0, 0, location)),
episode(4, time.Date(2026, 8, 3, 16, 0, 0, 0, location)),
}, now, location, nil)
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)
}
}
}