198 lines
6.6 KiB
Go
198 lines
6.6 KiB
Go
package radarr
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
"time"
|
|
|
|
"git.stinnesbeck.com/nils/arr-tui/internal/arr"
|
|
)
|
|
|
|
const lookupJSON = `[
|
|
{"id":0,"title":"The Matrix","year":1999,"tmdbId":603,"imdbId":"tt0133093","titleSlug":"the-matrix-603",
|
|
"runtime":136,"genres":["Action","Science Fiction"],"overview":"A hacker...",
|
|
"images":[{"coverType":"poster","remoteUrl":"https://img/p.jpg"}],
|
|
"ratings":{"imdb":{"votes":2000000,"value":8.7},"tmdb":{"votes":1,"value":8.2}}},
|
|
{"id":42,"title":"The Matrix Reloaded","year":2003,"tmdbId":604,"ratings":{"tmdb":{"votes":1,"value":7.0}}}
|
|
]`
|
|
|
|
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/movie/lookup" || r.URL.Query().Get("term") != "the matrix" {
|
|
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))
|
|
})
|
|
movies, err := c.Lookup(context.Background(), "the matrix")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(movies) != 2 {
|
|
t.Fatalf("got %d movies", len(movies))
|
|
}
|
|
if movies[0].InLibrary() || !movies[1].InLibrary() {
|
|
t.Error("InLibrary wrong")
|
|
}
|
|
if movies[0].Rating() != 8.7 || movies[1].Rating() != 7.0 {
|
|
t.Errorf("ratings: %v %v", movies[0].Rating(), movies[1].Rating())
|
|
}
|
|
if movies[0].Images[0].RemoteURL == "" {
|
|
t.Error("image not decoded")
|
|
}
|
|
}
|
|
|
|
func TestAddMovie(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/movie" {
|
|
t.Errorf("unexpected request %s %s", r.Method, r.URL)
|
|
}
|
|
json.NewDecoder(r.Body).Decode(&got)
|
|
w.WriteHeader(http.StatusCreated)
|
|
w.Write([]byte(`{"id":7,"title":"The Matrix","tmdbId":603}`))
|
|
})
|
|
m := Movie{Title: "The Matrix", Year: 1999, TMDBID: 603, TitleSlug: "the-matrix-603"}
|
|
created, err := c.AddMovie(context.Background(), m, AddOptions{
|
|
QualityProfileID: 4, RootFolderPath: "/movies", Monitored: true, SearchNow: true,
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if created.ID != 7 {
|
|
t.Errorf("id = %d", created.ID)
|
|
}
|
|
if got["tmdbId"] != float64(603) || got["qualityProfileId"] != float64(4) ||
|
|
got["rootFolderPath"] != "/movies" || got["monitored"] != true ||
|
|
got["minimumAvailability"] != "released" {
|
|
t.Errorf("request body = %v", got)
|
|
}
|
|
if ao, _ := got["addOptions"].(map[string]any); ao["searchForMovie"] != true {
|
|
t.Errorf("addOptions = %v", got["addOptions"])
|
|
}
|
|
}
|
|
|
|
func TestAPIErrors(t *testing.T) {
|
|
tests := map[string]struct {
|
|
status int
|
|
body string
|
|
want string
|
|
}{
|
|
"message": {http.StatusBadRequest, `{"message":"bad thing"}`, "HTTP 400: bad thing"},
|
|
"validation": {http.StatusBadRequest, `[{"propertyName":"TmdbId","errorMessage":"This movie has already been added"}]`, "HTTP 400: This movie has already been added"},
|
|
"unauth": {http.StatusUnauthorized, ``, "unexpected response: HTTP 401"},
|
|
}
|
|
for name, tc := range tests {
|
|
t.Run(name, func(t *testing.T) {
|
|
c := newTestClient(t, func(w http.ResponseWriter, r *http.Request) {
|
|
w.WriteHeader(tc.status)
|
|
w.Write([]byte(tc.body))
|
|
})
|
|
_, err := c.AddMovie(context.Background(), Movie{}, AddOptions{})
|
|
var apiErr *arr.APIError
|
|
if !errors.As(err, &apiErr) || apiErr.Status != tc.status || err.Error() != tc.want {
|
|
t.Fatalf("err = %v, want %q", err, tc.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestSharedEndpoints(t *testing.T) {
|
|
c := newTestClient(t, func(w http.ResponseWriter, r *http.Request) {
|
|
switch r.URL.Path {
|
|
case "/api/v3/system/status":
|
|
w.Write([]byte(`{"appName":"Radarr","version":"5.0.0"}`))
|
|
case "/api/v3/qualityprofile":
|
|
w.Write([]byte(`[{"id":1,"name":"Any"},{"id":4,"name":"HD-1080p"}]`))
|
|
case "/api/v3/rootfolder":
|
|
w.Write([]byte(`[{"id":1,"path":"/movies","freeSpace":1000,"accessible":true}]`))
|
|
default:
|
|
t.Errorf("unexpected %s", r.URL)
|
|
}
|
|
})
|
|
ctx := context.Background()
|
|
if s, err := c.SystemStatus(ctx); err != nil || s.AppName != "Radarr" {
|
|
t.Errorf("status = %+v, %v", s, err)
|
|
}
|
|
if p, err := c.QualityProfiles(ctx); err != nil || len(p) != 2 || p[1].Name != "HD-1080p" {
|
|
t.Errorf("profiles = %+v, %v", p, err)
|
|
}
|
|
if f, err := c.RootFolders(ctx); err != nil || len(f) != 1 || f[0].Path != "/movies" {
|
|
t.Errorf("folders = %+v, %v", f, err)
|
|
}
|
|
}
|
|
|
|
func TestSearchMovie(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.SearchMovie(context.Background(), 7); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if got["name"] != "MoviesSearch" {
|
|
t.Errorf("body = %v", got)
|
|
}
|
|
}
|
|
|
|
func TestReleases(t *testing.T) {
|
|
c := newTestClient(t, func(w http.ResponseWriter, r *http.Request) {
|
|
if r.URL.Path != "/api/v3/release" || r.URL.Query().Get("movieId") != "7" {
|
|
t.Errorf("unexpected request %s", r.URL)
|
|
}
|
|
w.Write([]byte(`[{"guid":"g","title":"x","approved":true}]`))
|
|
})
|
|
rels, err := c.Releases(context.Background(), 7)
|
|
if err != nil || len(rels) != 1 || rels[0].GUID != "g" {
|
|
t.Fatalf("releases = %+v, %v", rels, err)
|
|
}
|
|
}
|
|
|
|
func TestDeleteMovie(t *testing.T) {
|
|
c := newTestClient(t, func(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != http.MethodDelete || r.URL.Path != "/api/v3/movie/12" {
|
|
t.Errorf("unexpected request %s %s", r.Method, r.URL)
|
|
}
|
|
q := r.URL.Query()
|
|
if q.Get("deleteFiles") != "true" || q.Get("addImportExclusion") != "false" {
|
|
t.Errorf("query = %v", q)
|
|
}
|
|
})
|
|
if err := c.DeleteMovie(context.Background(), 12, true, false); 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("start") != "2026-10-20" || q.Get("end") != "2026-11-02" || q.Get("unmonitored") != "true" {
|
|
t.Errorf("unexpected request %s", r.URL)
|
|
}
|
|
w.Write([]byte(`[{"id":4,"title":"Dune","year":2021,"monitored":true,"hasFile":false,"inCinemas":"2026-10-22T00:00:00Z","digitalRelease":"2026-11-01T00:00:00Z"}]`))
|
|
})
|
|
start := time.Date(2026, 10, 20, 0, 0, 0, 0, time.UTC)
|
|
movies, err := c.Calendar(context.Background(), start, start.AddDate(0, 0, 13), true)
|
|
if err != nil || len(movies) != 1 {
|
|
t.Fatalf("movies=%v err=%v", movies, err)
|
|
}
|
|
if m := movies[0]; m.InCinemas != "2026-10-22T00:00:00Z" || m.DigitalRelease == "" || m.PhysicalRelease != "" {
|
|
t.Errorf("dates not decoded: %+v", m)
|
|
}
|
|
}
|