package musicbrainz import ( "context" "github.com/google/uuid" "github.com/stretchr/testify/require" "net/http" "net/http/httptest" "net/url" "strings" "testing" "time" ) type memoryCache map[string][]byte func (m memoryCache) GetMetadataCache(_ context.Context, key string) ([]byte, bool, error) { b, ok := m[key] return b, ok, nil } func (m memoryCache) PutMetadataCache(_ context.Context, key string, b []byte, _ time.Duration) error { m[key] = b return nil } func TestLookupParsesEditionTracksAndCachesRequests(t *testing.T) { id := uuid.NewString() requests := 0 server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { requests++ require.Equal(t, "PipuTest/1 (test@example.org)", r.UserAgent()) w.Header().Set("Content-Type", "application/json") if r.URL.Path == "/release" { require.Contains(t, r.URL.Query().Get("query"), `release:"Album"`) _, _ = w.Write([]byte(`{"releases":[{"id":"` + id + `"}]}`)) return } require.Equal(t, "recordings+artist-credits+release-groups", r.URL.Query().Get("inc")) _, _ = w.Write([]byte(`{"id":"` + id + `","title":"Album","date":"2024-01-01","country":"XE","artist-credit":[{"name":"Artist"}],"release-group":{"id":"` + uuid.NewString() + `","primary-type":"Single"},"media":[{"position":1,"tracks":[{"position":1,"recording":{"id":"recording","title":"Song","length":180000}}]}]}`)) })) defer server.Close() c := New(memoryCache{}, "PipuTest/1 (test@example.org)") c.base = server.URL c.interval = 0 for range 2 { result, err := c.Search(context.Background(), Query{Artist: "Artist", Album: "Album"}) require.NoError(t, err) require.Len(t, result, 1) require.Equal(t, "Single", result[0].Type) require.Equal(t, "Artist", result[0].Tracks[0].Artist) require.Equal(t, 180000, result[0].Tracks[0].DurationMs) } require.Equal(t, 2, requests) } func TestRecordingLookupRanksEditionsAndBoundsDiscovery(t *testing.T) { id, other := uuid.NewString(), uuid.NewString() lookups, details := 0, 0 server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.URL.Path == "/release" { lookups++ require.NotEmpty(t, r.URL.Query().Get("recording")) require.Equal(t, "100", r.URL.Query().Get("limit")) if lookups == 1 { _, _ = w.Write([]byte(`{"releases":[{"id":"` + other + `"},{"id":"` + id + `"}]}`)) } else { _, _ = w.Write([]byte(`{"releases":[{"id":"` + id + `"}]}`)) } return } details++ releaseID := strings.TrimPrefix(r.URL.Path, "/release/") _, _ = w.Write([]byte(`{"id":"` + releaseID + `","media":[{"position":1,"tracks":[{"position":1,"title":"Song","length":180000}]}]}`)) })) defer server.Close() c := New(memoryCache{}, "test") c.base = server.URL c.interval = 0 ids := []string{} for range 12 { ids = append(ids, uuid.NewString()) } ids = append([]string{ids[0]}, ids...) releases, err := c.Search(context.Background(), Query{RecordingIDs: ids}) require.NoError(t, err) require.Len(t, releases, 2) require.Equal(t, id, releases[0].ID) require.Equal(t, 8, lookups) require.Equal(t, 2, details) } func TestCoverFallbackIsExplicitAndRedirectsAreRestricted(t *testing.T) { r := Release{ID: uuid.NewString(), GroupID: uuid.NewString()} server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { if strings.HasPrefix(req.URL.Path, "/release/") { w.WriteHeader(404) return } _, _ = w.Write([]byte{0xff, 0xd8, 0xff, 1}) })) defer server.Close() c := New(memoryCache{}, "test") c.coverBase = server.URL cover, err := c.Cover(context.Background(), r) require.NoError(t, err) require.False(t, cover.Exact) require.Contains(t, cover.URL, "/release-group/") for _, address := range []string{"http://archive.org/a", "https://127.0.0.1/a", "https://archive.org.evil.example/a", "https://archive.org:1234/a"} { u, _ := url.Parse(address) require.Error(t, c.http.CheckRedirect(&http.Request{URL: u}, nil)) } } func TestLookupRateWaitCanBeCancelledAndResponsesAreBounded(t *testing.T) { c := New(memoryCache{}, "test") c.next = time.Now().Add(time.Second) ctx, cancel := context.WithTimeout(context.Background(), 10*time.Millisecond) defer cancel() _, err := c.Search(ctx, Query{Artist: "Artist", Album: "Album"}) require.ErrorIs(t, err, context.DeadlineExceeded) server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { _, _ = w.Write([]byte(strings.Repeat("x", 100))) })) defer server.Close() _, err = c.get(context.Background(), server.URL, false, 16) require.ErrorContains(t, err, "too large") }