package arr import ( "context" "net/http" "net/http/httptest" "strings" "testing" ) func TestTMDBSized(t *testing.T) { for _, tc := range []struct { in string large bool want string }{ {"https://image.tmdb.org/t/p/original/abc.jpg", true, "https://image.tmdb.org/t/p/w780/abc.jpg"}, {"https://image.tmdb.org/t/p/original/abc.jpg", false, "https://image.tmdb.org/t/p/w342/abc.jpg"}, {"https://image.tmdb.org/t/p/w500/abc.jpg", true, "https://image.tmdb.org/t/p/w500/abc.jpg"}, {"https://artworks.thetvdb.com/banners/posters/1.jpg", true, "https://artworks.thetvdb.com/banners/posters/1.jpg"}, } { if got := tmdbSized(tc.in, tc.large); got != tc.want { t.Errorf("tmdbSized(%q, %v) = %q, want %q", tc.in, tc.large, got, tc.want) } } } func TestScaledVariant(t *testing.T) { for _, tc := range []struct { in string width int want string }{ {"/MediaCover/1/poster.jpg?lastWrite=5", 250, "/MediaCover/1/poster-250.jpg?lastWrite=5"}, {"/MediaCover/1/poster.jpg", 500, "/MediaCover/1/poster-500.jpg"}, {"/MediaCover/1/fanart.jpg", 250, "/MediaCover/1/fanart.jpg"}, } { if got := scaledVariant(tc.in, tc.width); got != tc.want { t.Errorf("scaledVariant(%q, %d) = %q, want %q", tc.in, tc.width, got, tc.want) } } } func TestLargePosterUsesTheOriginal(t *testing.T) { var paths []string srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { paths = append(paths, r.URL.Path) w.Write([]byte("IMG")) })) defer srv.Close() c := NewClient(srv.URL, "secret") _, fetch, _ := c.Poster([]Image{{CoverType: "poster", URL: "/MediaCover/1/poster.jpg"}}, true) if _, err := fetch(context.Background(), true); err != nil { t.Fatal(err) } if _, err := fetch(context.Background(), false); err != nil { t.Fatal(err) } if strings.Join(paths, " ") != "/MediaCover/1/poster.jpg /MediaCover/1/poster-250.jpg" { t.Errorf("requests = %v", paths) } } func TestPosterLocalUsesAPIKey(t *testing.T) { var paths []string var keys []string srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { paths = append(paths, r.URL.RequestURI()) keys = append(keys, r.Header.Get("X-Api-Key")) if r.URL.Path == "/MediaCover/1/poster-250.jpg" { http.NotFound(w, r) // no small variant: fall back to the original return } w.Write([]byte("IMG")) })) defer srv.Close() c := NewClient(srv.URL, "secret") imgs := []Image{{CoverType: "fanart", URL: "/MediaCover/1/fanart.jpg"}, {CoverType: "poster", URL: "/MediaCover/1/poster.jpg?lastWrite=1", RemoteURL: "https://remote/p.jpg"}} key, fetch, ok := c.Poster(imgs, true) if !ok || key == "" { t.Fatal("no poster") } data, err := fetch(context.Background(), false) if err != nil || string(data) != "IMG" { t.Fatalf("data=%q err=%v", data, err) } if len(paths) != 2 || paths[0] != "/MediaCover/1/poster-250.jpg?lastWrite=1" || paths[1] != "/MediaCover/1/poster.jpg?lastWrite=1" { t.Errorf("requests = %v", paths) } for _, k := range keys { if k != "secret" { t.Errorf("api key header = %q", k) } } } func TestPosterRemoteSendsNoAPIKey(t *testing.T) { var got string srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { got = r.Header.Get("X-Api-Key") w.Write([]byte("IMG")) })) defer srv.Close() c := NewClient("http://radarr.invalid", "secret") key, fetch, ok := c.Poster([]Image{{CoverType: "poster", URL: "/MediaCover/0/poster.jpg", RemoteURL: srv.URL + "/p.jpg"}}, false) if !ok || key != srv.URL+"/p.jpg" { t.Fatalf("key=%q ok=%v", key, ok) } if _, err := fetch(context.Background(), false); err != nil { t.Fatal(err) } if got != "" { t.Errorf("the API key leaked to a remote image host: %q", got) } if _, _, ok := c.Poster([]Image{{CoverType: "banner", RemoteURL: "x"}}, false); ok { t.Error("only posters count") } if _, err := FetchURL(context.Background(), "file:///etc/passwd"); err == nil { t.Error("non-http schemes must be refused") } } func TestResolveURLBase(t *testing.T) { c := NewClient("http://host:7878/radarr", "k") for ref, want := range map[string]string{ "/radarr/MediaCover/1/poster.jpg": "http://host:7878/radarr/MediaCover/1/poster.jpg", "/MediaCover/1/poster.jpg": "http://host:7878/radarr/MediaCover/1/poster.jpg", } { if got, _ := c.resolve(ref); got != want { t.Errorf("resolve(%q) = %q, want %q", ref, got, want) } } if got, _ := NewClient("http://host", "k").resolve("/MediaCover/1/poster.jpg"); got != "http://host/MediaCover/1/poster.jpg" { t.Errorf("no url base: %q", got) } } func TestPosterFallsBackToTheProviderWhenTheInstanceHasNoCopy(t *testing.T) { var remoteKey string remote := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { remoteKey = r.Header.Get("X-Api-Key") w.Write([]byte("REMOTE")) })) defer remote.Close() instance := httptest.NewServer(http.NotFoundHandler()) // MediaCover has nothing cached defer instance.Close() c := NewClient(instance.URL, "secret") _, fetch, ok := c.Poster([]Image{{CoverType: "poster", URL: "/MediaCover/9/poster.jpg", RemoteURL: remote.URL + "/p.jpg"}}, true) if !ok { t.Fatal("no poster") } data, err := fetch(context.Background(), false) if err != nil || string(data) != "REMOTE" { t.Fatalf("data=%q err=%v", data, err) } if remoteKey != "" { t.Errorf("the API key went to the provider: %q", remoteKey) } // Without a remote address the local failure is reported. _, fetch, _ = c.Poster([]Image{{CoverType: "poster", URL: "/MediaCover/9/poster.jpg"}}, true) if _, err := fetch(context.Background(), false); err == nil { t.Error("expected the local error") } } // loginWall answers like an instance that requires a login for /MediaCover: // a 200 with an HTML page, unless the request carries the key as a query parameter. func loginWall(acceptQuery bool, hits *[]string) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { *hits = append(*hits, r.URL.RequestURI()+" key-header="+r.Header.Get("X-Api-Key")) if acceptQuery && r.URL.Query().Get("apikey") == "secret" { w.Header().Set("Content-Type", "image/jpeg") w.Write([]byte("IMAGE")) return } w.Header().Set("Content-Type", "text/html; charset=utf-8") w.Write([]byte("