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(`
[Куплет 1]
Привет &
мирNext
lineEmbed[Припев]
Again
`))
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{`Just a moment
`, `
`, ``} {
_, err := parseLyrics([]byte(page))
require.ErrorIs(t, err, ErrLyricsUnavailable)
}
}
func TestEmbeddedLyrics(t *testing.T) {
text, err := parseLyrics([]byte(``))
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 := `Synthetic test text
`
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}))
}
}