772d1f359b
* refactor: rename ArtistRadio to SimilarSongs for clarity and consistency Signed-off-by: Deluan <deluan@navidrome.org> * feat: implement GetSimilarSongsByTrack and related functionality for song similarity retrieval Signed-off-by: Deluan <deluan@navidrome.org> * feat: enhance GetSimilarSongsByTrack to include artist and album details and update tests Signed-off-by: Deluan <deluan@navidrome.org> * feat: enhance song matching by implementing title and artist filtering in loadTracksByTitleAndArtist Signed-off-by: Deluan <deluan@navidrome.org> * test: add unit tests for song matching functionality in provider Signed-off-by: Deluan <deluan@navidrome.org> * refactor: extract song matching functionality into its own file Signed-off-by: Deluan <deluan@navidrome.org> * docs: clarify similarSongsFallback function description in provider.go Signed-off-by: Deluan <deluan@navidrome.org> * refactor: initialize result slice for songs with capacity based on response length Signed-off-by: Deluan <deluan@navidrome.org> * refactor: simplify agent method calls for retrieving images and similar songs Signed-off-by: Deluan <deluan@navidrome.org> * refactor: simplify agent method calls for retrieving images and similar songs Signed-off-by: Deluan <deluan@navidrome.org> * refactor: remove outdated comments in GetSimilarSongs methods Signed-off-by: Deluan <deluan@navidrome.org> * fix: use composite key for song matches to handle duplicates by title and artist Signed-off-by: Deluan <deluan@navidrome.org> * refactor: consolidate expectations setup for similar songs tests Signed-off-by: Deluan <deluan@navidrome.org> * feat: add instant mix action to song context menu and update translations Signed-off-by: Deluan <deluan@navidrome.org> * fix(provider): handle unknown entity types in GetSimilarSongs Signed-off-by: Deluan <deluan@navidrome.org> * refactor: move playSimilar action to playbackActions and streamline song processing Signed-off-by: Deluan <deluan@navidrome.org> * format Signed-off-by: Deluan <deluan@navidrome.org> * feat: enhance instant mix functionality with loading notification and shuffle option Signed-off-by: Deluan <deluan@navidrome.org> * feat: implement fuzzy matching for similar songs based on configurable threshold Signed-off-by: Deluan <deluan@navidrome.org> * refactor: implement track matching with multiple specificity levels Signed-off-by: Deluan <deluan@navidrome.org> * refactor: enhance track matching by implementing unified scoring with specificity levels Signed-off-by: Deluan <deluan@navidrome.org> * feat: enhance deezer top tracks result with album Signed-off-by: Deluan <deluan@navidrome.org> * feat: enhance track matching with fuzzy album similarity for improved scoring Signed-off-by: Deluan <deluan@navidrome.org> * docs: document multi-phase song matching algorithm with detailed scoring and prioritization Signed-off-by: Deluan <deluan@navidrome.org> --------- Signed-off-by: Deluan <deluan@navidrome.org>
536 lines
22 KiB
Go
536 lines
22 KiB
Go
package lastfm
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"errors"
|
|
"io"
|
|
"net/http"
|
|
"os"
|
|
"strconv"
|
|
"time"
|
|
|
|
"github.com/navidrome/navidrome/conf"
|
|
"github.com/navidrome/navidrome/conf/configtest"
|
|
"github.com/navidrome/navidrome/core/agents"
|
|
"github.com/navidrome/navidrome/core/scrobbler"
|
|
"github.com/navidrome/navidrome/model"
|
|
"github.com/navidrome/navidrome/tests"
|
|
. "github.com/onsi/ginkgo/v2"
|
|
. "github.com/onsi/gomega"
|
|
)
|
|
|
|
const (
|
|
lastfmError3 = `{"error":3,"message":"Invalid Method - No method with that name in this package","links":[]}`
|
|
lastfmError6 = `{"error":6,"message":"The artist you supplied could not be found","links":[]}`
|
|
)
|
|
|
|
var _ = Describe("lastfmAgent", func() {
|
|
var ds model.DataStore
|
|
var ctx context.Context
|
|
BeforeEach(func() {
|
|
ds = &tests.MockDataStore{}
|
|
ctx = context.Background()
|
|
DeferCleanup(configtest.SetupConfig())
|
|
conf.Server.LastFM.Enabled = true
|
|
conf.Server.LastFM.ApiKey = "123"
|
|
conf.Server.LastFM.Secret = "secret"
|
|
})
|
|
Describe("lastFMConstructor", func() {
|
|
When("Agent is properly configured", func() {
|
|
It("uses configured api key and language", func() {
|
|
conf.Server.LastFM.Language = "pt"
|
|
agent := lastFMConstructor(ds)
|
|
Expect(agent.apiKey).To(Equal("123"))
|
|
Expect(agent.secret).To(Equal("secret"))
|
|
Expect(agent.lang).To(Equal("pt"))
|
|
})
|
|
})
|
|
When("Agent is disabled", func() {
|
|
It("returns nil", func() {
|
|
conf.Server.LastFM.Enabled = false
|
|
Expect(lastFMConstructor(ds)).To(BeNil())
|
|
})
|
|
})
|
|
When("ApiKey is empty", func() {
|
|
It("returns nil", func() {
|
|
conf.Server.LastFM.ApiKey = ""
|
|
Expect(lastFMConstructor(ds)).To(BeNil())
|
|
})
|
|
})
|
|
When("Secret is empty", func() {
|
|
It("returns nil", func() {
|
|
conf.Server.LastFM.Secret = ""
|
|
Expect(lastFMConstructor(ds)).To(BeNil())
|
|
})
|
|
})
|
|
})
|
|
|
|
Describe("GetArtistBiography", func() {
|
|
var agent *lastfmAgent
|
|
var httpClient *tests.FakeHttpClient
|
|
BeforeEach(func() {
|
|
httpClient = &tests.FakeHttpClient{}
|
|
client := newClient("API_KEY", "SECRET", "pt", httpClient)
|
|
agent = lastFMConstructor(ds)
|
|
agent.client = client
|
|
})
|
|
|
|
It("returns the biography", func() {
|
|
f, _ := os.Open("tests/fixtures/lastfm.artist.getinfo.json")
|
|
httpClient.Res = http.Response{Body: f, StatusCode: 200}
|
|
Expect(agent.GetArtistBiography(ctx, "123", "U2", "")).To(Equal("U2 é uma das mais importantes bandas de rock de todos os tempos. Formada em 1976 em Dublin, composta por Bono (vocalista e guitarrista), The Edge (guitarrista, pianista e backing vocal), Adam Clayton (baixista), Larry Mullen, Jr. (baterista e percussionista).\n\nDesde a década de 80, U2 é uma das bandas mais populares no mundo. Seus shows são únicos e um verdadeiro festival de efeitos especiais, além de serem um dos que mais arrecadam anualmente. <a href=\"https://www.last.fm/music/U2\">Read more on Last.fm</a>"))
|
|
Expect(httpClient.RequestCount).To(Equal(1))
|
|
Expect(httpClient.SavedRequest.URL.Query().Get("artist")).To(Equal("U2"))
|
|
})
|
|
|
|
It("returns an error if Last.fm call fails", func() {
|
|
httpClient.Err = errors.New("error")
|
|
_, err := agent.GetArtistBiography(ctx, "123", "U2", "")
|
|
Expect(err).To(HaveOccurred())
|
|
Expect(httpClient.RequestCount).To(Equal(1))
|
|
Expect(httpClient.SavedRequest.URL.Query().Get("artist")).To(Equal("U2"))
|
|
})
|
|
|
|
It("returns an error if Last.fm call returns an error", func() {
|
|
httpClient.Res = http.Response{Body: io.NopCloser(bytes.NewBufferString(lastfmError3)), StatusCode: 200}
|
|
_, err := agent.GetArtistBiography(ctx, "123", "U2", "")
|
|
Expect(err).To(HaveOccurred())
|
|
Expect(httpClient.RequestCount).To(Equal(1))
|
|
Expect(httpClient.SavedRequest.URL.Query().Get("artist")).To(Equal("U2"))
|
|
})
|
|
})
|
|
|
|
Describe("GetSimilarArtists", func() {
|
|
var agent *lastfmAgent
|
|
var httpClient *tests.FakeHttpClient
|
|
BeforeEach(func() {
|
|
httpClient = &tests.FakeHttpClient{}
|
|
client := newClient("API_KEY", "SECRET", "pt", httpClient)
|
|
agent = lastFMConstructor(ds)
|
|
agent.client = client
|
|
})
|
|
|
|
It("returns similar artists", func() {
|
|
f, _ := os.Open("tests/fixtures/lastfm.artist.getsimilar.json")
|
|
httpClient.Res = http.Response{Body: f, StatusCode: 200}
|
|
Expect(agent.GetSimilarArtists(ctx, "123", "U2", "", 2)).To(Equal([]agents.Artist{
|
|
{Name: "Passengers", MBID: "e110c11f-1c94-4471-a350-c38f46b29389"},
|
|
{Name: "INXS", MBID: "481bf5f9-2e7c-4c44-b08a-05b32bc7c00d"},
|
|
}))
|
|
Expect(httpClient.RequestCount).To(Equal(1))
|
|
Expect(httpClient.SavedRequest.URL.Query().Get("artist")).To(Equal("U2"))
|
|
})
|
|
|
|
It("returns an error if Last.fm call fails", func() {
|
|
httpClient.Err = errors.New("error")
|
|
_, err := agent.GetSimilarArtists(ctx, "123", "U2", "", 2)
|
|
Expect(err).To(HaveOccurred())
|
|
Expect(httpClient.RequestCount).To(Equal(1))
|
|
Expect(httpClient.SavedRequest.URL.Query().Get("artist")).To(Equal("U2"))
|
|
})
|
|
|
|
It("returns an error if Last.fm call returns an error", func() {
|
|
httpClient.Res = http.Response{Body: io.NopCloser(bytes.NewBufferString(lastfmError3)), StatusCode: 200}
|
|
_, err := agent.GetSimilarArtists(ctx, "123", "U2", "", 2)
|
|
Expect(err).To(HaveOccurred())
|
|
Expect(httpClient.RequestCount).To(Equal(1))
|
|
Expect(httpClient.SavedRequest.URL.Query().Get("artist")).To(Equal("U2"))
|
|
})
|
|
})
|
|
|
|
Describe("GetArtistTopSongs", func() {
|
|
var agent *lastfmAgent
|
|
var httpClient *tests.FakeHttpClient
|
|
BeforeEach(func() {
|
|
httpClient = &tests.FakeHttpClient{}
|
|
client := newClient("API_KEY", "SECRET", "pt", httpClient)
|
|
agent = lastFMConstructor(ds)
|
|
agent.client = client
|
|
})
|
|
|
|
It("returns top songs", func() {
|
|
f, _ := os.Open("tests/fixtures/lastfm.artist.gettoptracks.json")
|
|
httpClient.Res = http.Response{Body: f, StatusCode: 200}
|
|
Expect(agent.GetArtistTopSongs(ctx, "123", "U2", "", 2)).To(Equal([]agents.Song{
|
|
{Name: "Beautiful Day", MBID: "f7f264d0-a89b-4682-9cd7-a4e7c37637af"},
|
|
{Name: "With or Without You", MBID: "6b9a509f-6907-4a6e-9345-2f12da09ba4b"},
|
|
}))
|
|
Expect(httpClient.RequestCount).To(Equal(1))
|
|
Expect(httpClient.SavedRequest.URL.Query().Get("artist")).To(Equal("U2"))
|
|
})
|
|
|
|
It("returns an error if Last.fm call fails", func() {
|
|
httpClient.Err = errors.New("error")
|
|
_, err := agent.GetArtistTopSongs(ctx, "123", "U2", "", 2)
|
|
Expect(err).To(HaveOccurred())
|
|
Expect(httpClient.RequestCount).To(Equal(1))
|
|
Expect(httpClient.SavedRequest.URL.Query().Get("artist")).To(Equal("U2"))
|
|
})
|
|
|
|
It("returns an error if Last.fm call returns an error", func() {
|
|
httpClient.Res = http.Response{Body: io.NopCloser(bytes.NewBufferString(lastfmError3)), StatusCode: 200}
|
|
_, err := agent.GetArtistTopSongs(ctx, "123", "U2", "", 2)
|
|
Expect(err).To(HaveOccurred())
|
|
Expect(httpClient.RequestCount).To(Equal(1))
|
|
Expect(httpClient.SavedRequest.URL.Query().Get("artist")).To(Equal("U2"))
|
|
})
|
|
})
|
|
|
|
Describe("GetSimilarSongsByTrack", func() {
|
|
var agent *lastfmAgent
|
|
var httpClient *tests.FakeHttpClient
|
|
BeforeEach(func() {
|
|
httpClient = &tests.FakeHttpClient{}
|
|
client := newClient("API_KEY", "SECRET", "pt", httpClient)
|
|
agent = lastFMConstructor(ds)
|
|
agent.client = client
|
|
})
|
|
|
|
It("returns similar songs", func() {
|
|
f, _ := os.Open("tests/fixtures/lastfm.track.getsimilar.json")
|
|
httpClient.Res = http.Response{Body: f, StatusCode: 200}
|
|
Expect(agent.GetSimilarSongsByTrack(ctx, "123", "Just Can't Get Enough", "Depeche Mode", "", 5)).To(Equal([]agents.Song{
|
|
{Name: "Dreaming of Me", MBID: "027b553e-7c74-3ed4-a95e-1d4fea51f174", Artist: "Depeche Mode", ArtistMBID: "8538e728-ca0b-4321-b7e5-cff6565dd4c0"},
|
|
{Name: "Everything Counts", MBID: "5a5a3ca4-bdb8-4641-a674-9b54b9b319a6", Artist: "Depeche Mode", ArtistMBID: "8538e728-ca0b-4321-b7e5-cff6565dd4c0"},
|
|
{Name: "Don't You Want Me", MBID: "", Artist: "The Human League", ArtistMBID: "7adaabfb-acfb-47bc-8c7c-59471c2f0db8"},
|
|
{Name: "Tainted Love", MBID: "", Artist: "Soft Cell", ArtistMBID: "7fb50287-029d-47cc-825a-235ca28024b2"},
|
|
{Name: "Blue Monday", MBID: "727e84c6-1b56-31dd-a958-a5f46305cec0", Artist: "New Order", ArtistMBID: "f1106b17-dcbb-45f6-b938-199ccfab50cc"},
|
|
}))
|
|
Expect(httpClient.RequestCount).To(Equal(1))
|
|
Expect(httpClient.SavedRequest.URL.Query().Get("track")).To(Equal("Just Can't Get Enough"))
|
|
Expect(httpClient.SavedRequest.URL.Query().Get("artist")).To(Equal("Depeche Mode"))
|
|
})
|
|
|
|
It("returns ErrNotFound when no similar songs found", func() {
|
|
f, _ := os.Open("tests/fixtures/lastfm.track.getsimilar.unknown.json")
|
|
httpClient.Res = http.Response{Body: f, StatusCode: 200}
|
|
_, err := agent.GetSimilarSongsByTrack(ctx, "123", "UnknownTrack", "UnknownArtist", "", 3)
|
|
Expect(err).To(MatchError(agents.ErrNotFound))
|
|
Expect(httpClient.RequestCount).To(Equal(1))
|
|
})
|
|
|
|
It("returns an error if Last.fm call fails", func() {
|
|
httpClient.Err = errors.New("error")
|
|
_, err := agent.GetSimilarSongsByTrack(ctx, "123", "Believe", "Cher", "", 3)
|
|
Expect(err).To(HaveOccurred())
|
|
Expect(httpClient.RequestCount).To(Equal(1))
|
|
})
|
|
|
|
It("returns an error if Last.fm call returns an error", func() {
|
|
httpClient.Res = http.Response{Body: io.NopCloser(bytes.NewBufferString(lastfmError3)), StatusCode: 200}
|
|
_, err := agent.GetSimilarSongsByTrack(ctx, "123", "Believe", "Cher", "", 3)
|
|
Expect(err).To(HaveOccurred())
|
|
Expect(httpClient.RequestCount).To(Equal(1))
|
|
})
|
|
})
|
|
|
|
Describe("Scrobbling", func() {
|
|
var agent *lastfmAgent
|
|
var httpClient *tests.FakeHttpClient
|
|
var track *model.MediaFile
|
|
BeforeEach(func() {
|
|
_ = ds.UserProps(ctx).Put("user-1", sessionKeyProperty, "SK-1")
|
|
httpClient = &tests.FakeHttpClient{}
|
|
client := newClient("API_KEY", "SECRET", "en", httpClient)
|
|
agent = lastFMConstructor(ds)
|
|
agent.client = client
|
|
track = &model.MediaFile{
|
|
ID: "123",
|
|
Title: "Track Title",
|
|
Album: "Track Album",
|
|
Artist: "Track Artist",
|
|
AlbumArtist: "Track AlbumArtist",
|
|
TrackNumber: 1,
|
|
Duration: 180,
|
|
MbzRecordingID: "mbz-123",
|
|
Participants: map[model.Role]model.ParticipantList{
|
|
model.RoleArtist: []model.Participant{
|
|
{Artist: model.Artist{ID: "ar-1", Name: "First Artist"}},
|
|
{Artist: model.Artist{ID: "ar-2", Name: "Second Artist"}},
|
|
},
|
|
model.RoleAlbumArtist: []model.Participant{
|
|
{Artist: model.Artist{ID: "ar-1", Name: "First Album Artist"}},
|
|
{Artist: model.Artist{ID: "ar-2", Name: "Second Album Artist"}},
|
|
},
|
|
},
|
|
}
|
|
})
|
|
|
|
Describe("NowPlaying", func() {
|
|
It("calls Last.fm with correct params", func() {
|
|
httpClient.Res = http.Response{Body: io.NopCloser(bytes.NewBufferString("{}")), StatusCode: 200}
|
|
|
|
err := agent.NowPlaying(ctx, "user-1", track, 0)
|
|
|
|
Expect(err).ToNot(HaveOccurred())
|
|
Expect(httpClient.SavedRequest.Method).To(Equal(http.MethodPost))
|
|
sentParams := httpClient.SavedRequest.URL.Query()
|
|
Expect(sentParams.Get("method")).To(Equal("track.updateNowPlaying"))
|
|
Expect(sentParams.Get("sk")).To(Equal("SK-1"))
|
|
Expect(sentParams.Get("track")).To(Equal(track.Title))
|
|
Expect(sentParams.Get("album")).To(Equal(track.Album))
|
|
Expect(sentParams.Get("artist")).To(Equal(track.Artist))
|
|
Expect(sentParams.Get("albumArtist")).To(Equal(track.AlbumArtist))
|
|
Expect(sentParams.Get("trackNumber")).To(Equal(strconv.Itoa(track.TrackNumber)))
|
|
Expect(sentParams.Get("duration")).To(Equal(strconv.FormatFloat(float64(track.Duration), 'G', -1, 32)))
|
|
Expect(sentParams.Get("mbid")).To(Equal(track.MbzRecordingID))
|
|
})
|
|
|
|
It("returns ErrNotAuthorized if user is not linked", func() {
|
|
err := agent.NowPlaying(ctx, "user-2", track, 0)
|
|
Expect(err).To(MatchError(scrobbler.ErrNotAuthorized))
|
|
})
|
|
|
|
When("ScrobbleFirstArtistOnly is true", func() {
|
|
BeforeEach(func() {
|
|
conf.Server.LastFM.ScrobbleFirstArtistOnly = true
|
|
})
|
|
|
|
It("uses only the first artist", func() {
|
|
httpClient.Res = http.Response{Body: io.NopCloser(bytes.NewBufferString("{}")), StatusCode: 200}
|
|
|
|
err := agent.NowPlaying(ctx, "user-1", track, 0)
|
|
|
|
Expect(err).ToNot(HaveOccurred())
|
|
sentParams := httpClient.SavedRequest.URL.Query()
|
|
Expect(sentParams.Get("artist")).To(Equal("First Artist"))
|
|
Expect(sentParams.Get("albumArtist")).To(Equal("First Album Artist"))
|
|
})
|
|
})
|
|
})
|
|
|
|
Describe("scrobble", func() {
|
|
It("calls Last.fm with correct params", func() {
|
|
ts := time.Now()
|
|
httpClient.Res = http.Response{Body: io.NopCloser(bytes.NewBufferString("{}")), StatusCode: 200}
|
|
|
|
err := agent.Scrobble(ctx, "user-1", scrobbler.Scrobble{MediaFile: *track, TimeStamp: ts})
|
|
|
|
Expect(err).ToNot(HaveOccurred())
|
|
Expect(httpClient.SavedRequest.Method).To(Equal(http.MethodPost))
|
|
sentParams := httpClient.SavedRequest.URL.Query()
|
|
Expect(sentParams.Get("method")).To(Equal("track.scrobble"))
|
|
Expect(sentParams.Get("sk")).To(Equal("SK-1"))
|
|
Expect(sentParams.Get("track")).To(Equal(track.Title))
|
|
Expect(sentParams.Get("album")).To(Equal(track.Album))
|
|
Expect(sentParams.Get("artist")).To(Equal(track.Artist))
|
|
Expect(sentParams.Get("albumArtist")).To(Equal(track.AlbumArtist))
|
|
Expect(sentParams.Get("trackNumber")).To(Equal(strconv.Itoa(track.TrackNumber)))
|
|
Expect(sentParams.Get("duration")).To(Equal(strconv.FormatFloat(float64(track.Duration), 'G', -1, 32)))
|
|
Expect(sentParams.Get("mbid")).To(Equal(track.MbzRecordingID))
|
|
Expect(sentParams.Get("timestamp")).To(Equal(strconv.FormatInt(ts.Unix(), 10)))
|
|
})
|
|
|
|
When("ScrobbleFirstArtistOnly is true", func() {
|
|
BeforeEach(func() {
|
|
conf.Server.LastFM.ScrobbleFirstArtistOnly = true
|
|
})
|
|
|
|
It("uses only the first artist", func() {
|
|
ts := time.Now()
|
|
httpClient.Res = http.Response{Body: io.NopCloser(bytes.NewBufferString("{}")), StatusCode: 200}
|
|
|
|
err := agent.Scrobble(ctx, "user-1", scrobbler.Scrobble{MediaFile: *track, TimeStamp: ts})
|
|
|
|
Expect(err).ToNot(HaveOccurred())
|
|
sentParams := httpClient.SavedRequest.URL.Query()
|
|
Expect(sentParams.Get("artist")).To(Equal("First Artist"))
|
|
Expect(sentParams.Get("albumArtist")).To(Equal("First Album Artist"))
|
|
})
|
|
})
|
|
|
|
It("skips songs with less than 31 seconds", func() {
|
|
track.Duration = 29
|
|
httpClient.Res = http.Response{Body: io.NopCloser(bytes.NewBufferString("{}")), StatusCode: 200}
|
|
|
|
err := agent.Scrobble(ctx, "user-1", scrobbler.Scrobble{MediaFile: *track, TimeStamp: time.Now()})
|
|
|
|
Expect(err).ToNot(HaveOccurred())
|
|
Expect(httpClient.SavedRequest).To(BeNil())
|
|
})
|
|
|
|
It("returns ErrNotAuthorized if user is not linked", func() {
|
|
err := agent.Scrobble(ctx, "user-2", scrobbler.Scrobble{MediaFile: *track, TimeStamp: time.Now()})
|
|
Expect(err).To(MatchError(scrobbler.ErrNotAuthorized))
|
|
})
|
|
|
|
It("returns ErrRetryLater on error 11", func() {
|
|
httpClient.Res = http.Response{
|
|
Body: io.NopCloser(bytes.NewBufferString(`{"error":11,"message":"Service Offline - This service is temporarily offline. Try again later."}`)),
|
|
StatusCode: 400,
|
|
}
|
|
|
|
err := agent.Scrobble(ctx, "user-1", scrobbler.Scrobble{MediaFile: *track, TimeStamp: time.Now()})
|
|
Expect(err).To(MatchError(scrobbler.ErrRetryLater))
|
|
})
|
|
|
|
It("returns ErrRetryLater on error 16", func() {
|
|
httpClient.Res = http.Response{
|
|
Body: io.NopCloser(bytes.NewBufferString(`{"error":16,"message":"There was a temporary error processing your request. Please try again"}`)),
|
|
StatusCode: 400,
|
|
}
|
|
|
|
err := agent.Scrobble(ctx, "user-1", scrobbler.Scrobble{MediaFile: *track, TimeStamp: time.Now()})
|
|
Expect(err).To(MatchError(scrobbler.ErrRetryLater))
|
|
})
|
|
|
|
It("returns ErrRetryLater on http errors", func() {
|
|
httpClient.Res = http.Response{
|
|
Body: io.NopCloser(bytes.NewBufferString(`internal server error`)),
|
|
StatusCode: 500,
|
|
}
|
|
|
|
err := agent.Scrobble(ctx, "user-1", scrobbler.Scrobble{MediaFile: *track, TimeStamp: time.Now()})
|
|
Expect(err).To(MatchError(scrobbler.ErrRetryLater))
|
|
})
|
|
|
|
It("returns ErrUnrecoverable on other errors", func() {
|
|
httpClient.Res = http.Response{
|
|
Body: io.NopCloser(bytes.NewBufferString(`{"error":8,"message":"Operation failed - Something else went wrong"}`)),
|
|
StatusCode: 400,
|
|
}
|
|
|
|
err := agent.Scrobble(ctx, "user-1", scrobbler.Scrobble{MediaFile: *track, TimeStamp: time.Now()})
|
|
Expect(err).To(MatchError(scrobbler.ErrUnrecoverable))
|
|
})
|
|
})
|
|
})
|
|
|
|
Describe("GetAlbumInfo", func() {
|
|
var agent *lastfmAgent
|
|
var httpClient *tests.FakeHttpClient
|
|
BeforeEach(func() {
|
|
httpClient = &tests.FakeHttpClient{}
|
|
client := newClient("API_KEY", "SECRET", "pt", httpClient)
|
|
agent = lastFMConstructor(ds)
|
|
agent.client = client
|
|
})
|
|
|
|
It("returns the biography", func() {
|
|
f, _ := os.Open("tests/fixtures/lastfm.album.getinfo.json")
|
|
httpClient.Res = http.Response{Body: f, StatusCode: 200}
|
|
Expect(agent.GetAlbumInfo(ctx, "Believe", "Cher", "03c91c40-49a6-44a7-90e7-a700edf97a62")).To(Equal(&agents.AlbumInfo{
|
|
Name: "Believe",
|
|
MBID: "03c91c40-49a6-44a7-90e7-a700edf97a62",
|
|
Description: "Believe is the twenty-third studio album by American singer-actress Cher, released on November 10, 1998 by Warner Bros. Records. The RIAA certified it Quadruple Platinum on December 23, 1999, recognizing four million shipments in the United States; Worldwide, the album has sold more than 20 million copies, making it the biggest-selling album of her career. In 1999 the album received three Grammy Awards nominations including \"Record of the Year\", \"Best Pop Album\" and winning \"Best Dance Recording\" for the single \"Believe\". It was released by Warner Bros. Records at the end of 1998. The album was executive produced by Rob <a href=\"https://www.last.fm/music/Cher/Believe\">Read more on Last.fm</a>.",
|
|
URL: "https://www.last.fm/music/Cher/Believe",
|
|
}))
|
|
Expect(httpClient.RequestCount).To(Equal(1))
|
|
Expect(httpClient.SavedRequest.URL.Query().Get("mbid")).To(Equal("03c91c40-49a6-44a7-90e7-a700edf97a62"))
|
|
})
|
|
|
|
It("returns empty images if no images are available", func() {
|
|
f, _ := os.Open("tests/fixtures/lastfm.album.getinfo.empty_urls.json")
|
|
httpClient.Res = http.Response{Body: f, StatusCode: 200}
|
|
Expect(agent.GetAlbumInfo(ctx, "The Definitive Less Damage And More Joy", "The Jesus and Mary Chain", "")).To(Equal(&agents.AlbumInfo{
|
|
Name: "The Definitive Less Damage And More Joy",
|
|
URL: "https://www.last.fm/music/The+Jesus+and+Mary+Chain/The+Definitive+Less+Damage+And+More+Joy",
|
|
}))
|
|
Expect(httpClient.RequestCount).To(Equal(1))
|
|
Expect(httpClient.SavedRequest.URL.Query().Get("album")).To(Equal("The Definitive Less Damage And More Joy"))
|
|
})
|
|
|
|
It("returns an error if Last.fm call fails", func() {
|
|
httpClient.Err = errors.New("error")
|
|
_, err := agent.GetAlbumInfo(ctx, "123", "U2", "mbid-1234")
|
|
Expect(err).To(HaveOccurred())
|
|
Expect(httpClient.RequestCount).To(Equal(1))
|
|
Expect(httpClient.SavedRequest.URL.Query().Get("mbid")).To(Equal("mbid-1234"))
|
|
})
|
|
|
|
It("returns an error if Last.fm call returns an error", func() {
|
|
httpClient.Res = http.Response{Body: io.NopCloser(bytes.NewBufferString(lastfmError3)), StatusCode: 200}
|
|
_, err := agent.GetAlbumInfo(ctx, "123", "U2", "mbid-1234")
|
|
Expect(err).To(HaveOccurred())
|
|
Expect(httpClient.RequestCount).To(Equal(1))
|
|
Expect(httpClient.SavedRequest.URL.Query().Get("mbid")).To(Equal("mbid-1234"))
|
|
})
|
|
|
|
It("returns an error if Last.fm call returns an error 6 and mbid is empty", func() {
|
|
httpClient.Res = http.Response{Body: io.NopCloser(bytes.NewBufferString(lastfmError6)), StatusCode: 200}
|
|
_, err := agent.GetAlbumInfo(ctx, "123", "U2", "")
|
|
Expect(err).To(HaveOccurred())
|
|
Expect(httpClient.RequestCount).To(Equal(1))
|
|
})
|
|
|
|
Context("MBID non existent in Last.fm", func() {
|
|
It("calls again when last.fm returns an error 6", func() {
|
|
httpClient.Res = http.Response{Body: io.NopCloser(bytes.NewBufferString(lastfmError6)), StatusCode: 200}
|
|
_, _ = agent.GetAlbumInfo(ctx, "123", "U2", "mbid-1234")
|
|
Expect(httpClient.RequestCount).To(Equal(2))
|
|
Expect(httpClient.SavedRequest.URL.Query().Get("mbid")).To(BeEmpty())
|
|
})
|
|
})
|
|
})
|
|
|
|
Describe("GetArtistImages", func() {
|
|
var agent *lastfmAgent
|
|
var apiClient *tests.FakeHttpClient
|
|
var httpClient *tests.FakeHttpClient
|
|
|
|
BeforeEach(func() {
|
|
apiClient = &tests.FakeHttpClient{}
|
|
httpClient = &tests.FakeHttpClient{}
|
|
client := newClient("API_KEY", "SECRET", "pt", apiClient)
|
|
agent = lastFMConstructor(ds)
|
|
agent.client = client
|
|
agent.httpClient = httpClient
|
|
})
|
|
|
|
It("returns the artist image from the page", func() {
|
|
fApi, _ := os.Open("tests/fixtures/lastfm.artist.getinfo.json")
|
|
apiClient.Res = http.Response{Body: fApi, StatusCode: 200}
|
|
|
|
fScraper, _ := os.Open("tests/fixtures/lastfm.artist.page.html")
|
|
httpClient.Res = http.Response{Body: fScraper, StatusCode: 200}
|
|
|
|
images, err := agent.GetArtistImages(ctx, "123", "U2", "")
|
|
Expect(err).ToNot(HaveOccurred())
|
|
Expect(images).To(HaveLen(1))
|
|
Expect(images[0].URL).To(Equal("https://lastfm.freetls.fastly.net/i/u/ar0/818148bf682d429dc21b59a73ef6f68e.png"))
|
|
})
|
|
|
|
It("returns empty list if image is the ignored default image", func() {
|
|
fApi, _ := os.Open("tests/fixtures/lastfm.artist.getinfo.json")
|
|
apiClient.Res = http.Response{Body: fApi, StatusCode: 200}
|
|
|
|
fScraper, _ := os.Open("tests/fixtures/lastfm.artist.page.ignored.html")
|
|
httpClient.Res = http.Response{Body: fScraper, StatusCode: 200}
|
|
|
|
images, err := agent.GetArtistImages(ctx, "123", "U2", "")
|
|
Expect(err).ToNot(HaveOccurred())
|
|
Expect(images).To(BeEmpty())
|
|
})
|
|
|
|
It("returns empty list if page has no meta tags", func() {
|
|
fApi, _ := os.Open("tests/fixtures/lastfm.artist.getinfo.json")
|
|
apiClient.Res = http.Response{Body: fApi, StatusCode: 200}
|
|
|
|
fScraper, _ := os.Open("tests/fixtures/lastfm.artist.page.no_meta.html")
|
|
httpClient.Res = http.Response{Body: fScraper, StatusCode: 200}
|
|
|
|
images, err := agent.GetArtistImages(ctx, "123", "U2", "")
|
|
Expect(err).ToNot(HaveOccurred())
|
|
Expect(images).To(BeEmpty())
|
|
})
|
|
|
|
It("returns error if API call fails", func() {
|
|
apiClient.Err = errors.New("api error")
|
|
_, err := agent.GetArtistImages(ctx, "123", "U2", "")
|
|
Expect(err).To(HaveOccurred())
|
|
Expect(err.Error()).To(ContainSubstring("get artist info"))
|
|
})
|
|
|
|
It("returns error if scraper call fails", func() {
|
|
fApi, _ := os.Open("tests/fixtures/lastfm.artist.getinfo.json")
|
|
apiClient.Res = http.Response{Body: fApi, StatusCode: 200}
|
|
|
|
httpClient.Err = errors.New("scraper error")
|
|
_, err := agent.GetArtistImages(ctx, "123", "U2", "")
|
|
Expect(err).To(HaveOccurred())
|
|
Expect(err.Error()).To(ContainSubstring("get artist url"))
|
|
})
|
|
})
|
|
})
|