243 lines
8.2 KiB
Go
243 lines
8.2 KiB
Go
package coverart
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"sync/atomic"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
// newDeezerTestProvider returns a fresh enabled provider configured
|
|
// against a test server URL.
|
|
func newDeezerTestProvider(t *testing.T, baseURL string) *deezerProvider {
|
|
t.Helper()
|
|
old := deezerBaseURL
|
|
deezerBaseURL = baseURL
|
|
t.Cleanup(func() { deezerBaseURL = old })
|
|
|
|
p := &deezerProvider{client: newHTTPClient(httpClientOptions{Name: "deezer", HTTPClient: &http.Client{Timeout: 5 * time.Second}, BaseBackoff: time.Millisecond})}
|
|
p.enabled.Store(true)
|
|
return p
|
|
}
|
|
|
|
func TestDeezer_ID(t *testing.T) {
|
|
p := &deezerProvider{}
|
|
if p.ID() != "deezer" {
|
|
t.Errorf("ID = %q, want deezer", p.ID())
|
|
}
|
|
if p.RequiresAPIKey() {
|
|
t.Error("RequiresAPIKey = true, want false")
|
|
}
|
|
if !p.DefaultEnabled() {
|
|
t.Error("DefaultEnabled = false, want true")
|
|
}
|
|
}
|
|
|
|
func TestDeezer_FetchArtistArt_Success(t *testing.T) {
|
|
mux := http.NewServeMux()
|
|
srv := httptest.NewServer(mux)
|
|
defer srv.Close()
|
|
|
|
mux.HandleFunc("/search/artist", func(w http.ResponseWriter, r *http.Request) {
|
|
imgURL := "http://" + r.Host + "/img/cp.jpg"
|
|
w.Header().Set("Content-Type", "application/json")
|
|
_, _ = w.Write([]byte(`{"data":[{"name":"Caravan Palace","picture_xl":"` + imgURL + `"}]}`))
|
|
})
|
|
mux.HandleFunc("/img/", func(w http.ResponseWriter, _ *http.Request) {
|
|
w.Header().Set("Content-Type", "image/jpeg")
|
|
_, _ = w.Write([]byte("img-bytes"))
|
|
})
|
|
|
|
p := newDeezerTestProvider(t, srv.URL)
|
|
thumb, fanart, err := p.FetchArtistArt(context.Background(), ArtistRef{Name: "Caravan Palace"})
|
|
if err != nil {
|
|
t.Fatalf("err = %v, want nil", err)
|
|
}
|
|
if string(thumb) != "img-bytes" {
|
|
t.Errorf("thumb = %q, want img-bytes", thumb)
|
|
}
|
|
if fanart != nil {
|
|
t.Errorf("fanart = %v, want nil (Deezer has no separate fanart)", fanart)
|
|
}
|
|
}
|
|
|
|
func TestDeezer_FetchArtistArt_EmptyResults(t *testing.T) {
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
_, _ = w.Write([]byte(`{"data":[],"total":0}`))
|
|
}))
|
|
defer srv.Close()
|
|
|
|
p := newDeezerTestProvider(t, srv.URL)
|
|
_, _, err := p.FetchArtistArt(context.Background(), ArtistRef{Name: "Daisy Grenade"})
|
|
if !errors.Is(err, ErrNotFound) {
|
|
t.Errorf("err = %v, want ErrNotFound", err)
|
|
}
|
|
}
|
|
|
|
func TestDeezer_FetchArtistArt_NameMismatch(t *testing.T) {
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
_, _ = w.Write([]byte(`{"data":[{"name":"Some Other Artist","picture_xl":"http://example/x.jpg"}]}`))
|
|
}))
|
|
defer srv.Close()
|
|
|
|
p := newDeezerTestProvider(t, srv.URL)
|
|
_, _, err := p.FetchArtistArt(context.Background(), ArtistRef{Name: "Real Artist"})
|
|
if !errors.Is(err, ErrNotFound) {
|
|
t.Errorf("err = %v, want ErrNotFound (name mismatch should not match)", err)
|
|
}
|
|
}
|
|
|
|
func TestDeezer_FetchArtistArt_EmptyName(t *testing.T) {
|
|
p := &deezerProvider{client: newHTTPClient(httpClientOptions{Name: "deezer", HTTPClient: &http.Client{Timeout: 5 * time.Second}, BaseBackoff: time.Millisecond})}
|
|
p.enabled.Store(true)
|
|
_, _, err := p.FetchArtistArt(context.Background(), ArtistRef{})
|
|
if !errors.Is(err, ErrNotFound) {
|
|
t.Errorf("err = %v, want ErrNotFound on empty Name", err)
|
|
}
|
|
}
|
|
|
|
func TestDeezer_FetchArtistArt_Disabled(t *testing.T) {
|
|
p := &deezerProvider{client: newHTTPClient(httpClientOptions{Name: "deezer", HTTPClient: &http.Client{Timeout: 5 * time.Second}, BaseBackoff: time.Millisecond})}
|
|
// enabled defaults to false
|
|
_, _, err := p.FetchArtistArt(context.Background(), ArtistRef{Name: "X"})
|
|
if !errors.Is(err, ErrNotFound) {
|
|
t.Errorf("err = %v, want ErrNotFound when disabled", err)
|
|
}
|
|
}
|
|
|
|
func TestDeezer_FetchAlbumCover_Success(t *testing.T) {
|
|
mux := http.NewServeMux()
|
|
srv := httptest.NewServer(mux)
|
|
defer srv.Close()
|
|
|
|
mux.HandleFunc("/search/album", func(w http.ResponseWriter, r *http.Request) {
|
|
imgURL := "http://" + r.Host + "/img/r.jpg"
|
|
w.Header().Set("Content-Type", "application/json")
|
|
_, _ = w.Write([]byte(`{"data":[{"title":"The Record","cover_xl":"` + imgURL + `","artist":{"name":"The Band"}}]}`))
|
|
})
|
|
mux.HandleFunc("/img/", func(w http.ResponseWriter, _ *http.Request) {
|
|
w.Header().Set("Content-Type", "image/jpeg")
|
|
_, _ = w.Write([]byte("album-bytes"))
|
|
})
|
|
|
|
p := newDeezerTestProvider(t, srv.URL)
|
|
body, err := p.FetchAlbumCover(context.Background(), AlbumRef{ArtistName: "The Band", AlbumTitle: "The Record"})
|
|
if err != nil {
|
|
t.Fatalf("err = %v, want nil", err)
|
|
}
|
|
if string(body) != "album-bytes" {
|
|
t.Errorf("body = %q, want album-bytes", body)
|
|
}
|
|
}
|
|
|
|
func TestDeezer_FetchAlbumCover_TitleMismatch(t *testing.T) {
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
_, _ = w.Write([]byte(`{"data":[{"title":"Wrong Album","cover_xl":"http://example/x.jpg","artist":{"name":"The Band"}}]}`))
|
|
}))
|
|
defer srv.Close()
|
|
|
|
p := newDeezerTestProvider(t, srv.URL)
|
|
_, err := p.FetchAlbumCover(context.Background(), AlbumRef{ArtistName: "The Band", AlbumTitle: "The Record"})
|
|
if !errors.Is(err, ErrNotFound) {
|
|
t.Errorf("err = %v, want ErrNotFound (title mismatch)", err)
|
|
}
|
|
}
|
|
|
|
func TestDeezer_FetchAlbumCover_MissingFields(t *testing.T) {
|
|
p := &deezerProvider{client: newHTTPClient(httpClientOptions{Name: "deezer", HTTPClient: &http.Client{Timeout: 5 * time.Second}, BaseBackoff: time.Millisecond})}
|
|
p.enabled.Store(true)
|
|
|
|
cases := []AlbumRef{
|
|
{},
|
|
{ArtistName: "X"}, // no title
|
|
{AlbumTitle: "Y"}, // no artist
|
|
}
|
|
for _, ref := range cases {
|
|
_, err := p.FetchAlbumCover(context.Background(), ref)
|
|
if !errors.Is(err, ErrNotFound) {
|
|
t.Errorf("ref=%+v err=%v, want ErrNotFound", ref, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestDeezer_RateLimit_Serializes(t *testing.T) {
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
_, _ = w.Write([]byte(`{"data":[]}`))
|
|
}))
|
|
defer srv.Close()
|
|
|
|
old := deezerBaseURL
|
|
deezerBaseURL = srv.URL
|
|
t.Cleanup(func() { deezerBaseURL = old })
|
|
|
|
// Build a provider WITH the production MinInterval so this test can
|
|
// verify the rate-limit actually serializes. Other tests use
|
|
// newDeezerTestProvider which leaves MinInterval=0 for speed.
|
|
p := &deezerProvider{client: newHTTPClient(httpClientOptions{
|
|
Name: "deezer",
|
|
HTTPClient: &http.Client{Timeout: 5 * time.Second},
|
|
MinInterval: deezerMinPeriod,
|
|
BaseBackoff: time.Millisecond,
|
|
})}
|
|
p.enabled.Store(true)
|
|
|
|
start := time.Now()
|
|
_, _, _ = p.FetchArtistArt(context.Background(), ArtistRef{Name: "A"})
|
|
_, _, _ = p.FetchArtistArt(context.Background(), ArtistRef{Name: "B"})
|
|
elapsed := time.Since(start)
|
|
|
|
if elapsed < deezerMinPeriod {
|
|
t.Errorf("two back-to-back calls completed in %v, want at least %v (rate-limit didn't serialize)",
|
|
elapsed, deezerMinPeriod)
|
|
}
|
|
}
|
|
|
|
func TestDeezer_FetchArtistArt_ServerError(t *testing.T) {
|
|
var attempts atomic.Int32
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
|
attempts.Add(1)
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
}))
|
|
defer srv.Close()
|
|
|
|
p := newDeezerTestProvider(t, srv.URL)
|
|
_, _, err := p.FetchArtistArt(context.Background(), ArtistRef{Name: "X"})
|
|
if !errors.Is(err, ErrTransient) {
|
|
t.Errorf("err = %v, want ErrTransient on 500", err)
|
|
}
|
|
// Should have retried up to deezerMaxRetries+1 times total.
|
|
if attempts.Load() < 2 {
|
|
t.Errorf("attempts = %d, want >= 2 (retry should have run)", attempts.Load())
|
|
}
|
|
}
|
|
|
|
func TestDeezer_FetchArtistArt_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(`{"data":[]}`)) // second attempt: empty → ErrNotFound
|
|
}))
|
|
defer srv.Close()
|
|
|
|
p := newDeezerTestProvider(t, srv.URL)
|
|
_, _, err := p.FetchArtistArt(context.Background(), ArtistRef{Name: "X"})
|
|
if attempts.Load() < 2 {
|
|
t.Errorf("attempts = %d, want >= 2 (retry should have run)", attempts.Load())
|
|
}
|
|
if !errors.Is(err, ErrNotFound) {
|
|
t.Errorf("final err = %v, want ErrNotFound (empty results after retry)", err)
|
|
}
|
|
}
|