getArtistInfo/2 and getAlbumInfo/2 now return a real MusicBrainz disambiguation/cover-art lookup for entities that carry a locally tagged MusicBrainz ID, gated behind the new VIBESONIC_ENABLE_EXTERNAL_METADATA flag (default off). Responses are cached in the existing external_metadata_cache table for 30 days to avoid hammering the free MusicBrainz/CAA services. VibeSonic still works completely offline: with the flag off, or when an entity has no tagged MusicBrainz ID, or on any network/lookup failure, the handlers fall back to local-only fields exactly as before — nothing is ever fabricated, and a failed external lookup never fails the request. internal/metadataenrich is a small, independently-testable HTTP client for the two real APIs (base URLs overridable for tests, exercised against httptest servers rather than the live network). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014r98AtcBbevpscWLsfwVvv
120 lines
3.5 KiB
Go
120 lines
3.5 KiB
Go
package metadataenrich
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
)
|
|
|
|
func TestFetchArtistParsesRealFields(t *testing.T) {
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if r.URL.Path != "/artist/abc-123" {
|
|
t.Errorf("unexpected path %q", r.URL.Path)
|
|
}
|
|
if r.Header.Get("User-Agent") == "" {
|
|
t.Error("MusicBrainz requires a non-empty User-Agent")
|
|
}
|
|
json.NewEncoder(w).Encode(map[string]any{
|
|
"disambiguation": "English rock band",
|
|
"tags": []map[string]string{{"name": "rock"}, {"name": "alternative"}},
|
|
})
|
|
}))
|
|
defer srv.Close()
|
|
|
|
c := &Client{MusicBrainzBaseURL: srv.URL}
|
|
data, err := c.FetchArtist(context.Background(), "abc-123")
|
|
if err != nil {
|
|
t.Fatalf("FetchArtist: %v", err)
|
|
}
|
|
if data.Disambiguation != "English rock band" {
|
|
t.Errorf("disambiguation = %q", data.Disambiguation)
|
|
}
|
|
if len(data.Genres) != 2 || data.Genres[0] != "rock" {
|
|
t.Errorf("genres = %v", data.Genres)
|
|
}
|
|
}
|
|
|
|
func TestFetchArtistHTTPError(t *testing.T) {
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.WriteHeader(http.StatusNotFound)
|
|
}))
|
|
defer srv.Close()
|
|
|
|
c := &Client{MusicBrainzBaseURL: srv.URL}
|
|
if _, err := c.FetchArtist(context.Background(), "does-not-exist"); err == nil {
|
|
t.Fatal("expected an error for a 404 from MusicBrainz, got nil")
|
|
}
|
|
}
|
|
|
|
func TestFetchReleaseCoverArtParsesFrontImage(t *testing.T) {
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
json.NewEncoder(w).Encode(map[string]any{
|
|
"images": []map[string]any{
|
|
{"front": false, "image": "http://example.com/back.jpg"},
|
|
{
|
|
"front": true,
|
|
"image": "http://example.com/full.jpg",
|
|
"thumbnails": map[string]string{
|
|
"small": "http://example.com/small.jpg",
|
|
"large": "http://example.com/large.jpg",
|
|
},
|
|
},
|
|
},
|
|
})
|
|
}))
|
|
defer srv.Close()
|
|
|
|
c := &Client{CoverArtArchiveBaseURL: srv.URL}
|
|
art, err := c.FetchReleaseCoverArt(context.Background(), "release-1")
|
|
if err != nil {
|
|
t.Fatalf("FetchReleaseCoverArt: %v", err)
|
|
}
|
|
if art == nil {
|
|
t.Fatal("expected cover art, got nil")
|
|
}
|
|
if art.LargeURL != "http://example.com/full.jpg" {
|
|
t.Errorf("LargeURL = %q", art.LargeURL)
|
|
}
|
|
if art.SmallURL != "http://example.com/small.jpg" || art.MediumURL != "http://example.com/large.jpg" {
|
|
t.Errorf("art = %+v", art)
|
|
}
|
|
}
|
|
|
|
func TestFetchReleaseCoverArtNotFoundReturnsNilNotError(t *testing.T) {
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.WriteHeader(http.StatusNotFound)
|
|
}))
|
|
defer srv.Close()
|
|
|
|
c := &Client{CoverArtArchiveBaseURL: srv.URL}
|
|
art, err := c.FetchReleaseCoverArt(context.Background(), "no-art")
|
|
if err != nil {
|
|
t.Fatalf("expected nil error for a release with no registered cover art, got %v", err)
|
|
}
|
|
if art != nil {
|
|
t.Errorf("expected nil CoverArt, got %+v", art)
|
|
}
|
|
}
|
|
|
|
func TestFetchReleaseCoverArtNoFrontImage(t *testing.T) {
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
json.NewEncoder(w).Encode(map[string]any{
|
|
"images": []map[string]any{
|
|
{"front": false, "image": "http://example.com/back.jpg"},
|
|
},
|
|
})
|
|
}))
|
|
defer srv.Close()
|
|
|
|
c := &Client{CoverArtArchiveBaseURL: srv.URL}
|
|
art, err := c.FetchReleaseCoverArt(context.Background(), "back-only")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if art != nil {
|
|
t.Errorf("expected nil when no image is marked front, got %+v (must never substitute a back cover)", art)
|
|
}
|
|
}
|