201 lines
6.8 KiB
Go
201 lines
6.8 KiB
Go
package sonarr
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
const lookupJSON = `[
|
|
{"id":0,"title":"Breaking Bad","year":2008,"tvdbId":81189,"titleSlug":"breaking-bad","network":"AMC",
|
|
"status":"ended","runtime":47,"genres":["Crime","Drama"],"seriesType":"standard",
|
|
"ratings":{"votes":100,"value":9.3},
|
|
"seasons":[{"seasonNumber":0,"monitored":false},{"seasonNumber":1,"monitored":false},{"seasonNumber":2,"monitored":false}],
|
|
"images":[{"coverType":"poster","remoteUrl":"https://img/p.jpg"}]},
|
|
{"id":12,"title":"Better Call Saul","year":2015,"tvdbId":273181}
|
|
]`
|
|
|
|
func newTestClient(t *testing.T, h http.HandlerFunc) *Client {
|
|
t.Helper()
|
|
srv := httptest.NewServer(h)
|
|
t.Cleanup(srv.Close)
|
|
return NewClient(srv.URL, "secret")
|
|
}
|
|
|
|
func TestLookup(t *testing.T) {
|
|
c := newTestClient(t, func(w http.ResponseWriter, r *http.Request) {
|
|
if r.URL.Path != "/api/v3/series/lookup" || r.URL.Query().Get("term") != "breaking" {
|
|
t.Errorf("unexpected request %s", r.URL)
|
|
}
|
|
if r.Header.Get("X-Api-Key") != "secret" {
|
|
t.Error("missing api key header")
|
|
}
|
|
w.Write([]byte(lookupJSON))
|
|
})
|
|
series, err := c.Lookup(context.Background(), "breaking")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(series) != 2 || series[0].InLibrary() || !series[1].InLibrary() {
|
|
t.Fatalf("series = %+v", series)
|
|
}
|
|
if series[0].SeasonCount() != 2 {
|
|
t.Errorf("SeasonCount = %d, want 2 (specials excluded)", series[0].SeasonCount())
|
|
}
|
|
if series[0].Ratings.Value != 9.3 || series[0].Network != "AMC" {
|
|
t.Errorf("fields not decoded: %+v", series[0])
|
|
}
|
|
}
|
|
|
|
func TestAddSeries(t *testing.T) {
|
|
var got map[string]any
|
|
c := newTestClient(t, func(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != http.MethodPost || r.URL.Path != "/api/v3/series" {
|
|
t.Errorf("unexpected request %s %s", r.Method, r.URL)
|
|
}
|
|
json.NewDecoder(r.Body).Decode(&got)
|
|
w.WriteHeader(http.StatusCreated)
|
|
w.Write([]byte(`{"id":3,"title":"Breaking Bad","tvdbId":81189}`))
|
|
})
|
|
s := Series{
|
|
Title: "Breaking Bad", Year: 2008, TVDBID: 81189, SeriesType: "standard",
|
|
Seasons: []Season{{SeasonNumber: 1}, {SeasonNumber: 2}},
|
|
}
|
|
created, err := c.AddSeries(context.Background(), s, AddOptions{
|
|
QualityProfileID: 4, RootFolderPath: "/tv", Monitored: true,
|
|
Monitor: MonitorFirstSeason, SearchNow: true,
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if created.ID != 3 {
|
|
t.Errorf("id = %d", created.ID)
|
|
}
|
|
if got["tvdbId"] != float64(81189) || got["qualityProfileId"] != float64(4) ||
|
|
got["rootFolderPath"] != "/tv" || got["monitored"] != true || got["seasonFolder"] != true {
|
|
t.Errorf("request body = %v", got)
|
|
}
|
|
ao, _ := got["addOptions"].(map[string]any)
|
|
if ao["monitor"] != "firstSeason" || ao["searchForMissingEpisodes"] != true {
|
|
t.Errorf("addOptions = %v", ao)
|
|
}
|
|
if seasons, _ := got["seasons"].([]any); len(seasons) != 2 {
|
|
t.Errorf("seasons = %v", got["seasons"])
|
|
}
|
|
}
|
|
|
|
func TestAddSeriesDefaultMonitor(t *testing.T) {
|
|
var got map[string]any
|
|
c := newTestClient(t, func(w http.ResponseWriter, r *http.Request) {
|
|
json.NewDecoder(r.Body).Decode(&got)
|
|
w.WriteHeader(http.StatusCreated)
|
|
w.Write([]byte(`{}`))
|
|
})
|
|
if _, err := c.AddSeries(context.Background(), Series{}, AddOptions{}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if ao, _ := got["addOptions"].(map[string]any); ao["monitor"] != "all" {
|
|
t.Errorf("addOptions = %v", ao)
|
|
}
|
|
}
|
|
|
|
func TestSearchSeries(t *testing.T) {
|
|
var got map[string]any
|
|
c := newTestClient(t, func(w http.ResponseWriter, r *http.Request) {
|
|
json.NewDecoder(r.Body).Decode(&got)
|
|
w.WriteHeader(http.StatusCreated)
|
|
w.Write([]byte(`{}`))
|
|
})
|
|
if err := c.SearchSeries(context.Background(), 3); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if got["name"] != "SeriesSearch" || got["seriesId"] != float64(3) {
|
|
t.Errorf("body = %v", got)
|
|
}
|
|
}
|
|
|
|
func TestEpisodesAndReleaseQueries(t *testing.T) {
|
|
var queries []string
|
|
c := newTestClient(t, func(w http.ResponseWriter, r *http.Request) {
|
|
queries = append(queries, r.URL.Path+"?"+r.URL.RawQuery)
|
|
if r.URL.Path == "/api/v3/episode" {
|
|
w.Write([]byte(`[{"id":10,"seriesId":3,"seasonNumber":1,"episodeNumber":2,"title":"Two","hasFile":true}]`))
|
|
return
|
|
}
|
|
w.Write([]byte(`[]`))
|
|
})
|
|
ctx := context.Background()
|
|
eps, err := c.Episodes(ctx, 3)
|
|
if err != nil || len(eps) != 1 || eps[0].ID != 10 || !eps[0].HasFile || eps[0].EpisodeNumber != 2 {
|
|
t.Fatalf("episodes = %+v, %v", eps, err)
|
|
}
|
|
if _, err := c.EpisodeReleases(ctx, 10); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := c.SeasonReleases(ctx, 3, 1); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
want := []string{
|
|
"/api/v3/episode?seriesId=3",
|
|
"/api/v3/release?episodeId=10",
|
|
"/api/v3/release?seasonNumber=1&seriesId=3",
|
|
}
|
|
for i := range want {
|
|
if queries[i] != want[i] {
|
|
t.Errorf("query %d = %q, want %q", i, queries[i], want[i])
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestEpisodeFiles(t *testing.T) {
|
|
c := newTestClient(t, func(w http.ResponseWriter, r *http.Request) {
|
|
if r.URL.Path != "/api/v3/episodefile" || r.URL.Query().Get("seriesId") != "4" {
|
|
t.Errorf("unexpected request %s", r.URL)
|
|
}
|
|
w.Write([]byte(`[{"size":5,"quality":{"quality":{"name":"WEBDL-1080p","resolution":1080}},"mediaInfo":{"videoCodec":"x264"}}]`))
|
|
})
|
|
files, err := c.EpisodeFiles(context.Background(), 4)
|
|
if err != nil || len(files) != 1 {
|
|
t.Fatalf("files=%v err=%v", files, err)
|
|
}
|
|
if a := files[0].Attrs(); a.Resolution != 1080 || a.Codec != "x264" {
|
|
t.Errorf("attrs = %+v", a)
|
|
}
|
|
}
|
|
|
|
func TestDeleteSeries(t *testing.T) {
|
|
c := newTestClient(t, func(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != http.MethodDelete || r.URL.Path != "/api/v3/series/3" {
|
|
t.Errorf("unexpected request %s %s", r.Method, r.URL)
|
|
}
|
|
q := r.URL.Query()
|
|
if q.Get("deleteFiles") != "false" || q.Get("addImportListExclusion") != "true" {
|
|
t.Errorf("query = %v", q)
|
|
}
|
|
})
|
|
if err := c.DeleteSeries(context.Background(), 3, false, true); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func TestCalendar(t *testing.T) {
|
|
c := newTestClient(t, func(w http.ResponseWriter, r *http.Request) {
|
|
q := r.URL.Query()
|
|
if r.URL.Path != "/api/v3/calendar" || q.Get("includeSeries") != "true" || q.Get("unmonitored") != "false" || q.Get("start") != "2026-10-20" {
|
|
t.Errorf("unexpected request %s", r.URL)
|
|
}
|
|
w.Write([]byte(`[{"id":9,"seriesId":2,"seasonNumber":2,"episodeNumber":5,"title":"Welcome","airDate":"2026-10-21","airDateUtc":"2026-10-22T01:00:00Z","overview":"Pilot.","monitored":true,"series":{"id":2,"title":"Andor","images":[{"coverType":"poster","url":"/p.jpg"}]}}]`))
|
|
})
|
|
start := time.Date(2026, 10, 20, 0, 0, 0, 0, time.UTC)
|
|
eps, err := c.Calendar(context.Background(), start, start.AddDate(0, 0, 13), false)
|
|
if err != nil || len(eps) != 1 {
|
|
t.Fatalf("eps=%v err=%v", eps, err)
|
|
}
|
|
if e := eps[0]; e.Series == nil || e.Series.Title != "Andor" || e.AirDateUTC != "2026-10-22T01:00:00Z" || e.Overview != "Pilot." || len(e.Series.Images) != 1 {
|
|
t.Errorf("episode not decoded: %+v", e)
|
|
}
|
|
}
|