291 lines
9.5 KiB
Go
291 lines
9.5 KiB
Go
package coverart
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"sync/atomic"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
// withTheAudioDBBaseURL temporarily redirects theAudioDBBaseURL to a
|
|
// test server, returning a function the caller defers to restore.
|
|
func withTheAudioDBBaseURL(t *testing.T, mockURL string) {
|
|
t.Helper()
|
|
old := theAudioDBBaseURL
|
|
theAudioDBBaseURL = mockURL
|
|
t.Cleanup(func() { theAudioDBBaseURL = old })
|
|
}
|
|
|
|
func newTheAudioDBProviderForTest(t *testing.T, mockURL string) *theAudioDBProvider {
|
|
t.Helper()
|
|
resetRegistryForTests()
|
|
t.Cleanup(resetRegistryForTests)
|
|
withTheAudioDBBaseURL(t, mockURL)
|
|
|
|
p := &theAudioDBProvider{
|
|
client: newHTTPClient(httpClientOptions{Name: "theaudiodb", HTTPClient: &http.Client{Timeout: 5 * time.Second}, TreatAuthAsTransient: true, BaseBackoff: time.Millisecond}),
|
|
}
|
|
Register(p)
|
|
p.enabled.Store(true)
|
|
key := theAudioDBTestKey
|
|
p.apiKey.Store(&key)
|
|
return p
|
|
}
|
|
|
|
func TestTheAudioDB_ID(t *testing.T) {
|
|
resetRegistryForTests()
|
|
defer resetRegistryForTests()
|
|
p := &theAudioDBProvider{}
|
|
if p.ID() != "theaudiodb" {
|
|
t.Errorf("ID = %q, want theaudiodb", p.ID())
|
|
}
|
|
if !p.RequiresAPIKey() {
|
|
t.Error("RequiresAPIKey = false, want true")
|
|
}
|
|
if !p.DefaultEnabled() {
|
|
t.Error("DefaultEnabled = false, want true")
|
|
}
|
|
}
|
|
|
|
func TestTheAudioDB_FetchAlbumCover_Success(t *testing.T) {
|
|
imageBody := []byte("FAKE_JPEG")
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
switch {
|
|
case strings.Contains(r.URL.Path, "/album-mb.php"):
|
|
w.Header().Set("Content-Type", "application/json")
|
|
// Use the test server's own URL for the image, so getImage
|
|
// hits the same server.
|
|
imageURL := "http://" + r.Host + "/img/album.jpg"
|
|
_ = json.NewEncoder(w).Encode(map[string]any{
|
|
"album": []map[string]any{
|
|
{"strAlbumThumb": imageURL},
|
|
},
|
|
})
|
|
case r.URL.Path == "/img/album.jpg":
|
|
w.Header().Set("Content-Type", "image/jpeg")
|
|
_, _ = w.Write(imageBody)
|
|
default:
|
|
http.Error(w, "unexpected path: "+r.URL.Path, http.StatusBadRequest)
|
|
}
|
|
}))
|
|
defer srv.Close()
|
|
|
|
p := newTheAudioDBProviderForTest(t, srv.URL)
|
|
body, err := p.FetchAlbumCover(context.Background(), AlbumRef{MBID: "test-mbid"})
|
|
if err != nil {
|
|
t.Fatalf("FetchAlbumCover err = %v, want nil", err)
|
|
}
|
|
if string(body) != string(imageBody) {
|
|
t.Errorf("body = %q, want %q", body, imageBody)
|
|
}
|
|
}
|
|
|
|
func TestTheAudioDB_FetchAlbumCover_NotFound(t *testing.T) {
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
_, _ = w.Write([]byte(`{"album":null}`))
|
|
}))
|
|
defer srv.Close()
|
|
|
|
p := newTheAudioDBProviderForTest(t, srv.URL)
|
|
_, err := p.FetchAlbumCover(context.Background(), AlbumRef{MBID: "missing-mbid"})
|
|
if !errors.Is(err, ErrNotFound) {
|
|
t.Errorf("err = %v, want ErrNotFound", err)
|
|
}
|
|
}
|
|
|
|
func TestTheAudioDB_FetchAlbumCover_NullThumbURL(t *testing.T) {
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
_, _ = w.Write([]byte(`{"album":[{"strAlbumThumb":null}]}`))
|
|
}))
|
|
defer srv.Close()
|
|
|
|
p := newTheAudioDBProviderForTest(t, srv.URL)
|
|
_, err := p.FetchAlbumCover(context.Background(), AlbumRef{MBID: "mbid"})
|
|
if !errors.Is(err, ErrNotFound) {
|
|
t.Errorf("err = %v, want ErrNotFound when thumb URL is null", err)
|
|
}
|
|
}
|
|
|
|
func TestTheAudioDB_FetchAlbumCover_429TriggersRetry(t *testing.T) {
|
|
var attempts atomic.Int32
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
|
n := attempts.Add(1)
|
|
if n == 1 {
|
|
http.Error(w, "rate limit", http.StatusTooManyRequests)
|
|
return
|
|
}
|
|
w.Header().Set("Content-Type", "application/json")
|
|
_, _ = w.Write([]byte(`{"album":null}`)) // second attempt: empty (still ErrNotFound, but past the retry)
|
|
}))
|
|
defer srv.Close()
|
|
|
|
p := newTheAudioDBProviderForTest(t, srv.URL)
|
|
_, err := p.FetchAlbumCover(context.Background(), AlbumRef{MBID: "mbid"})
|
|
if attempts.Load() < 2 {
|
|
t.Errorf("attempts = %d, want >= 2 (retry should have run)", attempts.Load())
|
|
}
|
|
// Final result is ErrNotFound (empty album response on retry).
|
|
if !errors.Is(err, ErrNotFound) {
|
|
t.Errorf("final err = %v, want ErrNotFound", err)
|
|
}
|
|
}
|
|
|
|
func TestTheAudioDB_FetchArtistArt_BothPresent(t *testing.T) {
|
|
thumbBody := []byte("THUMB")
|
|
fanartBody := []byte("FANART")
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
switch {
|
|
case strings.Contains(r.URL.Path, "/artist-mb.php"):
|
|
thumbURL := "http://" + r.Host + "/img/thumb.jpg"
|
|
fanartURL := "http://" + r.Host + "/img/fanart.jpg"
|
|
w.Header().Set("Content-Type", "application/json")
|
|
_ = json.NewEncoder(w).Encode(map[string]any{
|
|
"artists": []map[string]any{
|
|
{"strArtistThumb": thumbURL, "strArtistFanart": fanartURL},
|
|
},
|
|
})
|
|
case r.URL.Path == "/img/thumb.jpg":
|
|
w.Header().Set("Content-Type", "image/jpeg")
|
|
_, _ = w.Write(thumbBody)
|
|
case r.URL.Path == "/img/fanart.jpg":
|
|
w.Header().Set("Content-Type", "image/jpeg")
|
|
_, _ = w.Write(fanartBody)
|
|
default:
|
|
http.Error(w, "unexpected: "+r.URL.Path, http.StatusBadRequest)
|
|
}
|
|
}))
|
|
defer srv.Close()
|
|
|
|
p := newTheAudioDBProviderForTest(t, srv.URL)
|
|
thumb, fanart, err := p.FetchArtistArt(context.Background(), ArtistRef{MBID: "mbid"})
|
|
if err != nil {
|
|
t.Fatalf("err = %v", err)
|
|
}
|
|
if string(thumb) != "THUMB" {
|
|
t.Errorf("thumb = %q, want THUMB", thumb)
|
|
}
|
|
if string(fanart) != "FANART" {
|
|
t.Errorf("fanart = %q, want FANART", fanart)
|
|
}
|
|
}
|
|
|
|
func TestTheAudioDB_FetchArtistArt_ThumbOnly(t *testing.T) {
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
switch {
|
|
case strings.Contains(r.URL.Path, "/artist-mb.php"):
|
|
thumbURL := "http://" + r.Host + "/img/thumb.jpg"
|
|
w.Header().Set("Content-Type", "application/json")
|
|
_, _ = w.Write([]byte(`{"artists":[{"strArtistThumb":"` + thumbURL + `","strArtistFanart":null}]}`))
|
|
case r.URL.Path == "/img/thumb.jpg":
|
|
w.Header().Set("Content-Type", "image/jpeg")
|
|
_, _ = w.Write([]byte("T"))
|
|
default:
|
|
http.Error(w, "unexpected: "+r.URL.Path, http.StatusBadRequest)
|
|
}
|
|
}))
|
|
defer srv.Close()
|
|
|
|
p := newTheAudioDBProviderForTest(t, srv.URL)
|
|
thumb, fanart, err := p.FetchArtistArt(context.Background(), ArtistRef{MBID: "mbid"})
|
|
if err != nil {
|
|
t.Fatalf("err = %v", err)
|
|
}
|
|
if string(thumb) != "T" {
|
|
t.Errorf("thumb = %q, want T", thumb)
|
|
}
|
|
if fanart != nil {
|
|
t.Errorf("fanart = %v, want nil", fanart)
|
|
}
|
|
}
|
|
|
|
func TestTheAudioDB_FetchArtistArt_BothEmpty(t *testing.T) {
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
_, _ = w.Write([]byte(`{"artists":[{"strArtistThumb":null,"strArtistFanart":null}]}`))
|
|
}))
|
|
defer srv.Close()
|
|
|
|
p := newTheAudioDBProviderForTest(t, srv.URL)
|
|
_, _, err := p.FetchArtistArt(context.Background(), ArtistRef{MBID: "mbid"})
|
|
if !errors.Is(err, ErrNotFound) {
|
|
t.Errorf("err = %v, want ErrNotFound", err)
|
|
}
|
|
}
|
|
|
|
func TestTheAudioDB_DefaultsToTestKeyOnEmpty(t *testing.T) {
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
// Inspect the URL — should contain "/2/" (the test key).
|
|
if !strings.Contains(r.URL.Path, "/"+theAudioDBTestKey+"/") {
|
|
t.Errorf("URL path %q missing test key", r.URL.Path)
|
|
}
|
|
w.Header().Set("Content-Type", "application/json")
|
|
_, _ = w.Write([]byte(`{"album":null}`))
|
|
}))
|
|
defer srv.Close()
|
|
|
|
resetRegistryForTests()
|
|
defer resetRegistryForTests()
|
|
withTheAudioDBBaseURL(t, srv.URL)
|
|
|
|
p := &theAudioDBProvider{client: newHTTPClient(httpClientOptions{Name: "theaudiodb", HTTPClient: &http.Client{Timeout: 5 * time.Second}, TreatAuthAsTransient: true, BaseBackoff: time.Millisecond})}
|
|
Register(p)
|
|
// Configure with empty key — should fall back to test key.
|
|
if err := p.Configure(ProviderSettings{Enabled: true, APIKey: ""}); err != nil {
|
|
t.Fatalf("Configure: %v", err)
|
|
}
|
|
|
|
_, _ = p.FetchAlbumCover(context.Background(), AlbumRef{MBID: "mbid"})
|
|
}
|
|
|
|
func TestTheAudioDB_DisabledReturnsNotFound(t *testing.T) {
|
|
srv := httptest.NewServer(http.HandlerFunc(func(_ http.ResponseWriter, _ *http.Request) {
|
|
t.Error("server should not be hit when provider is disabled")
|
|
}))
|
|
defer srv.Close()
|
|
|
|
p := newTheAudioDBProviderForTest(t, srv.URL)
|
|
p.enabled.Store(false)
|
|
|
|
_, err := p.FetchAlbumCover(context.Background(), AlbumRef{MBID: "any"})
|
|
if !errors.Is(err, ErrNotFound) {
|
|
t.Errorf("err = %v, want ErrNotFound (disabled)", err)
|
|
}
|
|
|
|
_, _, err = p.FetchArtistArt(context.Background(), ArtistRef{MBID: "any"})
|
|
if !errors.Is(err, ErrNotFound) {
|
|
t.Errorf("artist err = %v, want ErrNotFound (disabled)", err)
|
|
}
|
|
}
|
|
|
|
func TestTheAudioDB_TestConnection_SuccessOnEmptyAlbum(t *testing.T) {
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
_, _ = w.Write([]byte(`{"album":null}`))
|
|
}))
|
|
defer srv.Close()
|
|
|
|
p := newTheAudioDBProviderForTest(t, srv.URL)
|
|
if err := p.TestConnection(context.Background()); err != nil {
|
|
t.Errorf("TestConnection err = %v, want nil (empty album response is still 200)", err)
|
|
}
|
|
}
|
|
|
|
func TestTheAudioDB_TestConnection_FailureOn401(t *testing.T) {
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
|
http.Error(w, "unauthorized", http.StatusUnauthorized)
|
|
}))
|
|
defer srv.Close()
|
|
|
|
p := newTheAudioDBProviderForTest(t, srv.URL)
|
|
if err := p.TestConnection(context.Background()); err == nil {
|
|
t.Error("TestConnection err = nil, want non-nil for 401")
|
|
}
|
|
}
|