Files
pipu-music/services/api/internal/genius/client_test.go
T
mrleo1nidandCursor 73ad61e7a5
ci / ts / ts (packages + web) (push) Canceled after 1m0s
ci / api / api (push) Successful in 46s
ci / contract / contract (сгенерированный код совпадает с openapi.yaml) (push) Successful in 56s
ci / db / db (push) Successful in 1m32s
ci / mobile / mobile (Metro-бандл Android) (push) Successful in 1m27s
ci / docker / docker (образы собираются — их сервер собирает сам при обновлении) (push) Successful in 1m31s
feat(genius): load song metadata, lyrics, and artwork
Search hits omit album covers and artist images. The worker now reads the full song and artist, stores the description, and leaves Genius image URLs unchanged on mobile.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-22 08:23:54 +03:00

93 lines
4.0 KiB
Go

package genius
import (
"context"
"errors"
"io"
"net/http"
"strings"
"testing"
"github.com/stretchr/testify/require"
)
type transportFunc func(*http.Request) (*http.Response, error)
func (f transportFunc) RoundTrip(r *http.Request) (*http.Response, error) { return f(r) }
func TestLyricsFormatting(t *testing.T) {
text, err := parseLyrics([]byte(`<header>Not lyrics</header><div data-lyrics-container="true"><div class="LyricsHeader-x">Title</div>[Куплет 1]<br>Привет &amp; <a href="/annotation">мир</a><br><br>Next <b>line</b><span data-exclude-from-selection="true">Embed</span><script>bad()</script></div><div data-lyrics-container="true">[Припев]<br>Again<button>Share</button></div>`))
require.NoError(t, err)
require.Equal(t, "[Куплет 1]\nПривет & мир\n\nNext line\n\n[Припев]\nAgain", text)
}
func TestUnavailable(t *testing.T) {
for _, page := range []string{`<h1>Just a moment</h1>`, `<div data-lyrics-container="true"> </div>`, `<script>window.__PRELOADED_STATE__ = JSON.parse('broken')</script>`} {
_, err := parseLyrics([]byte(page))
require.ErrorIs(t, err, ErrLyricsUnavailable)
}
}
func TestEmbeddedLyrics(t *testing.T) {
text, err := parseLyrics([]byte(`<script>window.__PRELOADED_STATE__ = JSON.parse('{"songPage":{"lyricsData":{"body":{"html":"<p>[Verse]<br>It\'s \\"fine\\" &amp; good<br>Привет</p>"}}}}')</script>`))
require.NoError(t, err)
require.Equal(t, "[Verse]\nIt's \"fine\" & good\nПривет", text)
}
func TestURLValidation(t *testing.T) {
c := New("secret")
c.http.Transport = transportFunc(func(*http.Request) (*http.Response, error) { t.Fatal("unexpected network call"); return nil, nil })
for _, u := range []string{"https://evil.test/song-lyrics", "http://genius.com/song-lyrics", "https://genius.com:443/song-lyrics", "https://secret@genius.com/song-lyrics", "https://genius.com/artists/test"} {
_, err := c.FetchLyrics(context.Background(), u)
require.Error(t, err)
}
}
func TestRequests(t *testing.T) {
c := New("secret")
c.http.Transport = transportFunc(func(r *http.Request) (*http.Response, error) {
body := `<div data-lyrics-container="true">Synthetic test text</div>`
if r.URL.Host == "api.genius.com" {
require.Equal(t, "Bearer secret", r.Header.Get("Authorization"))
require.Equal(t, "artist & title", r.URL.Query().Get("q"))
body = `{"response":{"hits":[{"type":"song","result":{"id":12,"title":"Example","url":"https://genius.com/example-lyrics","primary_artist":{"name":"Artist"}}}]}}`
} else {
require.Empty(t, r.Header.Get("Authorization"))
require.Empty(t, r.URL.RawQuery)
}
return &http.Response{StatusCode: 200, Body: io.NopCloser(strings.NewReader(body)), Header: make(http.Header)}, nil
})
songs, err := c.Search(context.Background(), "artist & title")
require.NoError(t, err)
require.Len(t, songs, 1)
lyrics, err := c.FetchLyrics(context.Background(), songs[0].URL+"?tracking=1")
require.NoError(t, err)
require.Equal(t, "Synthetic test text", lyrics.Text)
require.Equal(t, songs[0].URL, lyrics.SourceURL)
require.False(t, lyrics.FetchedAt.IsZero())
}
func TestHTTPFailures(t *testing.T) {
for _, status := range []int{401, 403, 404, 429, 500} {
c := New("secret")
c.http.Transport = transportFunc(func(*http.Request) (*http.Response, error) {
return &http.Response{StatusCode: status, Header: http.Header{"Retry-After": {"60"}}, Body: io.NopCloser(strings.NewReader("private upstream body"))}, nil
})
_, err := c.Search(context.Background(), "test")
var httpErr *HTTPError
require.True(t, errors.As(err, &httpErr))
require.Equal(t, status, httpErr.StatusCode)
require.Equal(t, "60", httpErr.RetryAfter)
require.NotContains(t, err.Error(), "private")
}
}
func TestRedirects(t *testing.T) {
c := New("secret")
first, _ := http.NewRequest("GET", "https://api.genius.com/search", nil)
for _, endpoint := range []string{"http://api.genius.com/search", "https://genius.com/song-lyrics", "https://evil.test/"} {
req, _ := http.NewRequest("GET", endpoint, nil)
require.Error(t, c.http.CheckRedirect(req, []*http.Request{first}))
}
}