package artistphoto import ( "context" "encoding/json" "net/http" "net/http/httptest" "testing" ) func TestFetchPhotoURLUsesFanartTVWhenMBIDAndKeyProvided(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.URL.Path != "/mbid-1" { t.Errorf("unexpected path %q", r.URL.Path) } if r.URL.Query().Get("api_key") != "secret" { t.Errorf("api_key = %q", r.URL.Query().Get("api_key")) } json.NewEncoder(w).Encode(map[string]any{ "artistthumb": []map[string]string{{"url": "https://fanart.example/photo.jpg"}}, }) })) defer srv.Close() c := &Client{FanartTVBaseURL: srv.URL} u, err := c.FetchPhotoURL(context.Background(), "mbid-1", "Some Artist", "secret") if err != nil { t.Fatalf("FetchPhotoURL: %v", err) } if u != "https://fanart.example/photo.jpg" { t.Errorf("url = %q", u) } } func TestFetchPhotoURLSkipsFanartTVWithoutMBID(t *testing.T) { fanartCalled := false fanart := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { fanartCalled = true })) defer fanart.Close() wds := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { json.NewEncoder(w).Encode(map[string]any{"search": []map[string]any{}}) })) defer wds.Close() c := &Client{FanartTVBaseURL: fanart.URL, WikidataSearchBaseURL: wds.URL} if _, err := c.FetchPhotoURL(context.Background(), "", "Some Artist", "secret"); err != nil { t.Fatalf("FetchPhotoURL: %v", err) } if fanartCalled { t.Error("fanart.tv should never be called without a MusicBrainz ID (it has no name-search endpoint)") } } func TestFetchPhotoURLSearchesWikidataByName(t *testing.T) { wds := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if got := r.URL.Query().Get("search"); got != "The Fray" { t.Errorf("search query = %q", got) } json.NewEncoder(w).Encode(map[string]any{ "search": []map[string]any{ {"id": "Q191571", "label": "The Fray", "description": "American rock band"}, }, }) })) defer wds.Close() wde := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.URL.Path != "/Q191571.json" { t.Errorf("unexpected entity path %q", r.URL.Path) } json.NewEncoder(w).Encode(map[string]any{ "entities": map[string]any{ "Q191571": map[string]any{ "claims": map[string]any{ // A real Wikidata entity carries many other // properties whose datavalue.value is an // object, not a string (P31 "instance of" is // an entity reference, for example) — this // must not prevent P18 from being read. "P31": []map[string]any{ {"mainsnak": map[string]any{"datavalue": map[string]any{"value": map[string]any{"entity-type": "item", "id": "Q215380"}}}}, }, "P18": []map[string]any{ {"mainsnak": map[string]any{"datavalue": map[string]any{"value": "The Fray.jpg"}}}, }, }, }, }, }) })) defer wde.Close() commons := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})) defer commons.Close() c := &Client{WikidataSearchBaseURL: wds.URL, WikidataEntityBaseURL: wde.URL, CommonsBaseURL: commons.URL} u, err := c.FetchPhotoURL(context.Background(), "", "The Fray", "") if err != nil { t.Fatalf("FetchPhotoURL: %v", err) } want := commons.URL + "/The%20Fray.jpg?width=800" if u != want { t.Errorf("url = %q, want %q", u, want) } } func TestFetchPhotoURLFallsBackToWikidataWhenFanartHasNoPhoto(t *testing.T) { fanart := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusNotFound) })) defer fanart.Close() wds := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { json.NewEncoder(w).Encode(map[string]any{ "search": []map[string]any{ {"id": "Q1", "label": "Some Artist", "description": "American singer"}, }, }) })) defer wds.Close() wde := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { json.NewEncoder(w).Encode(map[string]any{"entities": map[string]any{"Q1": map[string]any{"claims": map[string]any{}}}}) })) defer wde.Close() c := &Client{FanartTVBaseURL: fanart.URL, WikidataSearchBaseURL: wds.URL, WikidataEntityBaseURL: wde.URL} u, err := c.FetchPhotoURL(context.Background(), "mbid-2", "Some Artist", "secret") if err != nil { t.Fatalf("FetchPhotoURL: %v", err) } if u != "" { t.Errorf("expected empty URL (no P18 claim), got %q", u) } } func TestSearchArtistWikidataIDPrefersMusicDescription(t *testing.T) { wds := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { json.NewEncoder(w).Encode(map[string]any{ "search": []map[string]any{ {"id": "Q-actor", "label": "Gigi Perez", "description": "Filipino actor"}, {"id": "Q-singer", "label": "Gigi Perez", "description": "American singer-songwriter"}, }, }) })) defer wds.Close() c := &Client{WikidataSearchBaseURL: wds.URL} qid, err := c.SearchArtistWikidataID(context.Background(), "Gigi Perez") if err != nil { t.Fatalf("SearchArtistWikidataID: %v", err) } if qid != "Q-singer" { t.Errorf("qid = %q, want the music-flavored match Q-singer", qid) } } func TestSearchArtistWikidataIDRequiresExactLabelMatch(t *testing.T) { wds := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { json.NewEncoder(w).Encode(map[string]any{ "search": []map[string]any{ {"id": "Q-tribute", "label": "Not Related! with Luke Smith Tribute", "description": "cover band"}, }, }) })) defer wds.Close() c := &Client{WikidataSearchBaseURL: wds.URL} qid, err := c.SearchArtistWikidataID(context.Background(), "Not Related! with Luke Smith") if err != nil { t.Fatalf("expected nil error, got %v", err) } if qid != "" { t.Errorf("expected no match for a non-exact label, got %q", qid) } } func TestFetchWikidataPhotoByNameRetriesOnTransientError(t *testing.T) { var calls int wds := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { calls++ if calls == 1 { // Simulate a transient network hiccup by closing the // connection without responding at all. hj, ok := w.(http.Hijacker) if !ok { t.Fatal("ResponseWriter does not support hijacking") } conn, _, err := hj.Hijack() if err != nil { t.Fatal(err) } conn.Close() return } json.NewEncoder(w).Encode(map[string]any{"search": []map[string]any{}}) })) defer wds.Close() c := &Client{WikidataSearchBaseURL: wds.URL} u, err := c.FetchPhotoURL(context.Background(), "", "Retry Artist", "") if err != nil { t.Fatalf("expected the retry to recover from the first request's transient failure, got %v", err) } if u != "" { t.Errorf("expected empty URL (no search results), got %q", u) } if calls != 2 { t.Errorf("expected exactly one retry (2 total calls), got %d calls", calls) } }