444 lines
18 KiB
Go
444 lines
18 KiB
Go
|
|
package external_test
|
||
|
|
|
||
|
|
import (
|
||
|
|
"context"
|
||
|
|
"errors"
|
||
|
|
|
||
|
|
"github.com/Masterminds/squirrel"
|
||
|
|
"github.com/navidrome/navidrome/core/agents"
|
||
|
|
. "github.com/navidrome/navidrome/core/external"
|
||
|
|
"github.com/navidrome/navidrome/model"
|
||
|
|
"github.com/navidrome/navidrome/tests"
|
||
|
|
. "github.com/onsi/ginkgo/v2"
|
||
|
|
. "github.com/onsi/gomega"
|
||
|
|
"github.com/stretchr/testify/mock"
|
||
|
|
)
|
||
|
|
|
||
|
|
var _ = Describe("Provider - SimilarSongs", func() {
|
||
|
|
var ds model.DataStore
|
||
|
|
var provider Provider
|
||
|
|
var mockAgent *mockSimilarArtistAgent
|
||
|
|
var mockTopAgent agents.ArtistTopSongsRetriever
|
||
|
|
var mockSimilarAgent agents.ArtistSimilarRetriever
|
||
|
|
var agentsCombined *mockAgents
|
||
|
|
var artistRepo *mockArtistRepo
|
||
|
|
var mediaFileRepo *mockMediaFileRepo
|
||
|
|
var albumRepo *mockAlbumRepo
|
||
|
|
var ctx context.Context
|
||
|
|
|
||
|
|
BeforeEach(func() {
|
||
|
|
ctx = GinkgoT().Context()
|
||
|
|
|
||
|
|
artistRepo = newMockArtistRepo()
|
||
|
|
mediaFileRepo = newMockMediaFileRepo()
|
||
|
|
albumRepo = newMockAlbumRepo()
|
||
|
|
|
||
|
|
ds = &tests.MockDataStore{
|
||
|
|
MockedArtist: artistRepo,
|
||
|
|
MockedMediaFile: mediaFileRepo,
|
||
|
|
MockedAlbum: albumRepo,
|
||
|
|
}
|
||
|
|
|
||
|
|
mockAgent = &mockSimilarArtistAgent{}
|
||
|
|
mockTopAgent = mockAgent
|
||
|
|
mockSimilarAgent = mockAgent
|
||
|
|
|
||
|
|
agentsCombined = &mockAgents{
|
||
|
|
topSongsAgent: mockTopAgent,
|
||
|
|
similarAgent: mockSimilarAgent,
|
||
|
|
}
|
||
|
|
|
||
|
|
provider = NewProvider(ds, agentsCombined)
|
||
|
|
})
|
||
|
|
|
||
|
|
Describe("dispatch by entity type", func() {
|
||
|
|
Context("when ID is a MediaFile (track)", func() {
|
||
|
|
It("calls GetSimilarSongsByTrack and returns matched songs", func() {
|
||
|
|
track := model.MediaFile{ID: "track-1", Title: "Just Can't Get Enough", Artist: "Depeche Mode", MbzRecordingID: "track-mbid"}
|
||
|
|
matchedSong := model.MediaFile{ID: "matched-1", Title: "Dreaming of Me", Artist: "Depeche Mode"}
|
||
|
|
|
||
|
|
// GetEntityByID tries Artist, Album, Playlist, then MediaFile
|
||
|
|
artistRepo.On("Get", "track-1").Return(nil, model.ErrNotFound).Once()
|
||
|
|
albumRepo.On("Get", "track-1").Return(nil, model.ErrNotFound).Once()
|
||
|
|
mediaFileRepo.On("Get", "track-1").Return(&track, nil).Once()
|
||
|
|
|
||
|
|
agentsCombined.On("GetSimilarSongsByTrack", mock.Anything, "track-1", "Just Can't Get Enough", "Depeche Mode", "track-mbid", 5).
|
||
|
|
Return([]agents.Song{
|
||
|
|
{Name: "Dreaming of Me", MBID: "", Artist: "Depeche Mode", ArtistMBID: "artist-mbid"},
|
||
|
|
}, nil).Once()
|
||
|
|
|
||
|
|
// Mock loadTracksByID - no ID matches
|
||
|
|
mediaFileRepo.On("GetAll", mock.MatchedBy(func(opt model.QueryOptions) bool {
|
||
|
|
_, ok := opt.Filters.(squirrel.Eq)
|
||
|
|
return ok
|
||
|
|
})).Return(model.MediaFiles{}, nil).Once()
|
||
|
|
|
||
|
|
// Mock loadTracksByMBID - no MBID matches (empty MBID means this won't be called)
|
||
|
|
mediaFileRepo.On("GetAll", mock.MatchedBy(func(opt model.QueryOptions) bool {
|
||
|
|
and, ok := opt.Filters.(squirrel.And)
|
||
|
|
if !ok || len(and) < 1 {
|
||
|
|
return false
|
||
|
|
}
|
||
|
|
eq, hasEq := and[0].(squirrel.Eq)
|
||
|
|
if !hasEq {
|
||
|
|
return false
|
||
|
|
}
|
||
|
|
_, hasMBID := eq["mbz_recording_id"]
|
||
|
|
return hasMBID
|
||
|
|
})).Return(model.MediaFiles{}, nil).Maybe()
|
||
|
|
|
||
|
|
// Mock loadTracksByTitleAndArtist - queries by artist name
|
||
|
|
mediaFileRepo.On("GetAll", mock.MatchedBy(func(opt model.QueryOptions) bool {
|
||
|
|
and, ok := opt.Filters.(squirrel.And)
|
||
|
|
if !ok || len(and) < 2 {
|
||
|
|
return false
|
||
|
|
}
|
||
|
|
eq, hasEq := and[0].(squirrel.Eq)
|
||
|
|
if !hasEq {
|
||
|
|
return false
|
||
|
|
}
|
||
|
|
_, hasArtist := eq["order_artist_name"]
|
||
|
|
return hasArtist
|
||
|
|
})).Return(model.MediaFiles{matchedSong}, nil).Maybe()
|
||
|
|
|
||
|
|
songs, err := provider.SimilarSongs(ctx, "track-1", 5)
|
||
|
|
|
||
|
|
Expect(err).ToNot(HaveOccurred())
|
||
|
|
Expect(songs).To(HaveLen(1))
|
||
|
|
Expect(songs[0].ID).To(Equal("matched-1"))
|
||
|
|
})
|
||
|
|
|
||
|
|
It("falls back to artist-based algorithm when GetSimilarSongsByTrack returns empty", func() {
|
||
|
|
track := model.MediaFile{ID: "track-1", Title: "Track", Artist: "Artist", ArtistID: "artist-1"}
|
||
|
|
artist := model.Artist{ID: "artist-1", Name: "Artist"}
|
||
|
|
song := model.MediaFile{ID: "song-1", Title: "Song One", ArtistID: "artist-1", MbzRecordingID: "mbid-1"}
|
||
|
|
|
||
|
|
// GetEntityByID for the initial call tries Artist, Album, Playlist, then MediaFile
|
||
|
|
artistRepo.On("Get", "track-1").Return(nil, model.ErrNotFound).Once()
|
||
|
|
albumRepo.On("Get", "track-1").Return(nil, model.ErrNotFound).Once()
|
||
|
|
mediaFileRepo.On("Get", "track-1").Return(&track, nil).Once()
|
||
|
|
|
||
|
|
agentsCombined.On("GetSimilarSongsByTrack", mock.Anything, "track-1", "Track", "Artist", "", mock.Anything).
|
||
|
|
Return([]agents.Song{}, nil).Once()
|
||
|
|
|
||
|
|
// Fallback calls getArtist(id) which calls GetEntityByID again - this time it finds the mediafile
|
||
|
|
// and recursively calls getArtist(v.ArtistID)
|
||
|
|
artistRepo.On("Get", "track-1").Return(nil, model.ErrNotFound).Once()
|
||
|
|
albumRepo.On("Get", "track-1").Return(nil, model.ErrNotFound).Once()
|
||
|
|
mediaFileRepo.On("Get", "track-1").Return(&track, nil).Once()
|
||
|
|
|
||
|
|
// Then it recurses with the artist-1 ID
|
||
|
|
artistRepo.On("Get", "artist-1").Return(&artist, nil).Maybe()
|
||
|
|
artistRepo.On("GetAll", mock.MatchedBy(func(opt model.QueryOptions) bool {
|
||
|
|
return opt.Max == 1 && opt.Filters != nil
|
||
|
|
})).Return(model.Artists{artist}, nil).Maybe()
|
||
|
|
|
||
|
|
mockAgent.On("GetSimilarArtists", mock.Anything, "artist-1", "Artist", "", 15).
|
||
|
|
Return([]agents.Artist{}, nil).Once()
|
||
|
|
|
||
|
|
artistRepo.On("GetAll", mock.MatchedBy(func(opt model.QueryOptions) bool {
|
||
|
|
return opt.Max == 0 && opt.Filters != nil
|
||
|
|
})).Return(model.Artists{}, nil).Once()
|
||
|
|
|
||
|
|
mockAgent.On("GetArtistTopSongs", mock.Anything, "artist-1", "Artist", "", mock.Anything).
|
||
|
|
Return([]agents.Song{{Name: "Song One", MBID: "mbid-1"}}, nil).Once()
|
||
|
|
|
||
|
|
mediaFileRepo.On("GetAll", mock.AnythingOfType("model.QueryOptions")).Return(model.MediaFiles{song}, nil).Once()
|
||
|
|
|
||
|
|
songs, err := provider.SimilarSongs(ctx, "track-1", 5)
|
||
|
|
|
||
|
|
Expect(err).ToNot(HaveOccurred())
|
||
|
|
Expect(songs).To(HaveLen(1))
|
||
|
|
Expect(songs[0].ID).To(Equal("song-1"))
|
||
|
|
})
|
||
|
|
})
|
||
|
|
|
||
|
|
Context("when ID is an Album", func() {
|
||
|
|
It("calls GetSimilarSongsByAlbum and returns matched songs", func() {
|
||
|
|
album := model.Album{ID: "album-1", Name: "Speak & Spell", AlbumArtist: "Depeche Mode", MbzAlbumID: "album-mbid"}
|
||
|
|
matchedSong := model.MediaFile{ID: "matched-1", Title: "New Life", Artist: "Depeche Mode", MbzRecordingID: "song-mbid"}
|
||
|
|
|
||
|
|
// GetEntityByID tries Artist, Album, Playlist, then MediaFile
|
||
|
|
artistRepo.On("Get", "album-1").Return(nil, model.ErrNotFound).Once()
|
||
|
|
albumRepo.On("Get", "album-1").Return(&album, nil).Once()
|
||
|
|
|
||
|
|
agentsCombined.On("GetSimilarSongsByAlbum", mock.Anything, "album-1", "Speak & Spell", "Depeche Mode", "album-mbid", 5).
|
||
|
|
Return([]agents.Song{
|
||
|
|
{Name: "New Life", MBID: "song-mbid", Artist: "Depeche Mode"},
|
||
|
|
}, nil).Once()
|
||
|
|
|
||
|
|
// Mock loadTracksByID - no ID matches
|
||
|
|
mediaFileRepo.On("GetAll", mock.MatchedBy(func(opt model.QueryOptions) bool {
|
||
|
|
_, ok := opt.Filters.(squirrel.Eq)
|
||
|
|
return ok
|
||
|
|
})).Return(model.MediaFiles{}, nil).Once()
|
||
|
|
|
||
|
|
// Mock loadTracksByMBID - MBID match
|
||
|
|
mediaFileRepo.On("GetAll", mock.MatchedBy(func(opt model.QueryOptions) bool {
|
||
|
|
and, ok := opt.Filters.(squirrel.And)
|
||
|
|
if !ok || len(and) < 1 {
|
||
|
|
return false
|
||
|
|
}
|
||
|
|
_, hasEq := and[0].(squirrel.Eq)
|
||
|
|
return hasEq
|
||
|
|
})).Return(model.MediaFiles{matchedSong}, nil).Once()
|
||
|
|
|
||
|
|
songs, err := provider.SimilarSongs(ctx, "album-1", 5)
|
||
|
|
|
||
|
|
Expect(err).ToNot(HaveOccurred())
|
||
|
|
Expect(songs).To(HaveLen(1))
|
||
|
|
Expect(songs[0].ID).To(Equal("matched-1"))
|
||
|
|
})
|
||
|
|
|
||
|
|
It("falls back when GetSimilarSongsByAlbum returns ErrNotFound", func() {
|
||
|
|
album := model.Album{ID: "album-1", Name: "Album", AlbumArtist: "Artist", AlbumArtistID: "artist-1"}
|
||
|
|
artist := model.Artist{ID: "artist-1", Name: "Artist"}
|
||
|
|
song := model.MediaFile{ID: "song-1", Title: "Song One", ArtistID: "artist-1", MbzRecordingID: "mbid-1"}
|
||
|
|
|
||
|
|
// GetEntityByID for the initial call tries Artist, Album, Playlist, then MediaFile
|
||
|
|
artistRepo.On("Get", "album-1").Return(nil, model.ErrNotFound).Once()
|
||
|
|
albumRepo.On("Get", "album-1").Return(&album, nil).Once()
|
||
|
|
|
||
|
|
agentsCombined.On("GetSimilarSongsByAlbum", mock.Anything, "album-1", "Album", "Artist", "", mock.Anything).
|
||
|
|
Return(nil, agents.ErrNotFound).Once()
|
||
|
|
|
||
|
|
// Fallback calls getArtist(id) which calls GetEntityByID again - this time it finds the album
|
||
|
|
// and recursively calls getArtist(v.AlbumArtistID)
|
||
|
|
artistRepo.On("Get", "album-1").Return(nil, model.ErrNotFound).Once()
|
||
|
|
albumRepo.On("Get", "album-1").Return(&album, nil).Once()
|
||
|
|
|
||
|
|
// Then it recurses with the artist-1 ID
|
||
|
|
artistRepo.On("Get", "artist-1").Return(&artist, nil).Maybe()
|
||
|
|
artistRepo.On("GetAll", mock.MatchedBy(func(opt model.QueryOptions) bool {
|
||
|
|
return opt.Max == 1 && opt.Filters != nil
|
||
|
|
})).Return(model.Artists{artist}, nil).Maybe()
|
||
|
|
|
||
|
|
mockAgent.On("GetSimilarArtists", mock.Anything, "artist-1", "Artist", "", 15).
|
||
|
|
Return([]agents.Artist{}, nil).Once()
|
||
|
|
|
||
|
|
artistRepo.On("GetAll", mock.MatchedBy(func(opt model.QueryOptions) bool {
|
||
|
|
return opt.Max == 0 && opt.Filters != nil
|
||
|
|
})).Return(model.Artists{}, nil).Once()
|
||
|
|
|
||
|
|
mockAgent.On("GetArtistTopSongs", mock.Anything, "artist-1", "Artist", "", mock.Anything).
|
||
|
|
Return([]agents.Song{{Name: "Song One", MBID: "mbid-1"}}, nil).Once()
|
||
|
|
|
||
|
|
mediaFileRepo.On("GetAll", mock.AnythingOfType("model.QueryOptions")).Return(model.MediaFiles{song}, nil).Once()
|
||
|
|
|
||
|
|
songs, err := provider.SimilarSongs(ctx, "album-1", 5)
|
||
|
|
|
||
|
|
Expect(err).ToNot(HaveOccurred())
|
||
|
|
Expect(songs).To(HaveLen(1))
|
||
|
|
Expect(songs[0].ID).To(Equal("song-1"))
|
||
|
|
})
|
||
|
|
})
|
||
|
|
|
||
|
|
Context("when ID is an Artist", func() {
|
||
|
|
It("calls GetSimilarSongsByArtist and returns matched songs", func() {
|
||
|
|
artist := model.Artist{ID: "artist-1", Name: "Depeche Mode", MbzArtistID: "artist-mbid"}
|
||
|
|
matchedSong := model.MediaFile{ID: "matched-1", Title: "Enjoy the Silence", Artist: "Depeche Mode", MbzRecordingID: "song-mbid"}
|
||
|
|
|
||
|
|
artistRepo.On("Get", "artist-1").Return(&artist, nil).Once()
|
||
|
|
agentsCombined.On("GetSimilarSongsByArtist", mock.Anything, "artist-1", "Depeche Mode", "artist-mbid", 5).
|
||
|
|
Return([]agents.Song{
|
||
|
|
{Name: "Enjoy the Silence", MBID: "song-mbid", Artist: "Depeche Mode"},
|
||
|
|
}, nil).Once()
|
||
|
|
|
||
|
|
// Mock loadTracksByID - no ID matches
|
||
|
|
mediaFileRepo.On("GetAll", mock.MatchedBy(func(opt model.QueryOptions) bool {
|
||
|
|
_, ok := opt.Filters.(squirrel.Eq)
|
||
|
|
return ok
|
||
|
|
})).Return(model.MediaFiles{}, nil).Once()
|
||
|
|
|
||
|
|
// Mock loadTracksByMBID - MBID match
|
||
|
|
mediaFileRepo.On("GetAll", mock.MatchedBy(func(opt model.QueryOptions) bool {
|
||
|
|
and, ok := opt.Filters.(squirrel.And)
|
||
|
|
if !ok || len(and) < 1 {
|
||
|
|
return false
|
||
|
|
}
|
||
|
|
_, hasEq := and[0].(squirrel.Eq)
|
||
|
|
return hasEq
|
||
|
|
})).Return(model.MediaFiles{matchedSong}, nil).Once()
|
||
|
|
|
||
|
|
songs, err := provider.SimilarSongs(ctx, "artist-1", 5)
|
||
|
|
|
||
|
|
Expect(err).ToNot(HaveOccurred())
|
||
|
|
Expect(songs).To(HaveLen(1))
|
||
|
|
Expect(songs[0].ID).To(Equal("matched-1"))
|
||
|
|
})
|
||
|
|
})
|
||
|
|
})
|
||
|
|
|
||
|
|
It("returns similar songs from main artist and similar artists", func() {
|
||
|
|
artist1 := model.Artist{ID: "artist-1", Name: "Artist One"}
|
||
|
|
similarArtist := model.Artist{ID: "artist-3", Name: "Similar Artist"}
|
||
|
|
song1 := model.MediaFile{ID: "song-1", Title: "Song One", ArtistID: "artist-1", MbzRecordingID: "mbid-1"}
|
||
|
|
song2 := model.MediaFile{ID: "song-2", Title: "Song Two", ArtistID: "artist-1", MbzRecordingID: "mbid-2"}
|
||
|
|
song3 := model.MediaFile{ID: "song-3", Title: "Song Three", ArtistID: "artist-3", MbzRecordingID: "mbid-3"}
|
||
|
|
|
||
|
|
artistRepo.On("Get", "artist-1").Return(&artist1, nil).Maybe()
|
||
|
|
artistRepo.On("Get", "artist-3").Return(&similarArtist, nil).Maybe()
|
||
|
|
|
||
|
|
artistRepo.On("GetAll", mock.MatchedBy(func(opt model.QueryOptions) bool {
|
||
|
|
return opt.Max == 1 && opt.Filters != nil
|
||
|
|
})).Return(model.Artists{artist1}, nil).Once()
|
||
|
|
|
||
|
|
// New similar songs by artist returns ErrNotFound to trigger fallback
|
||
|
|
agentsCombined.On("GetSimilarSongsByArtist", mock.Anything, "artist-1", "Artist One", "", mock.Anything).
|
||
|
|
Return(nil, agents.ErrNotFound).Once()
|
||
|
|
|
||
|
|
similarAgentsResp := []agents.Artist{
|
||
|
|
{Name: "Similar Artist", MBID: "similar-mbid"},
|
||
|
|
}
|
||
|
|
mockAgent.On("GetSimilarArtists", mock.Anything, "artist-1", "Artist One", "", 15).
|
||
|
|
Return(similarAgentsResp, nil).Once()
|
||
|
|
|
||
|
|
// Mock the three-phase artist lookup: ID (skipped - no IDs), MBID, then Name
|
||
|
|
// MBID lookup returns empty (no match)
|
||
|
|
artistRepo.On("GetAll", mock.MatchedBy(func(opt model.QueryOptions) bool {
|
||
|
|
_, ok := opt.Filters.(squirrel.Eq)
|
||
|
|
return opt.Max == 0 && ok
|
||
|
|
})).Return(model.Artists{}, nil).Once()
|
||
|
|
// Name lookup returns the similar artist
|
||
|
|
artistRepo.On("GetAll", mock.MatchedBy(func(opt model.QueryOptions) bool {
|
||
|
|
_, ok := opt.Filters.(squirrel.Or)
|
||
|
|
return opt.Max == 0 && ok
|
||
|
|
})).Return(model.Artists{similarArtist}, nil).Once()
|
||
|
|
|
||
|
|
mockAgent.On("GetArtistTopSongs", mock.Anything, "artist-1", "Artist One", "", mock.Anything).
|
||
|
|
Return([]agents.Song{
|
||
|
|
{Name: "Song One", MBID: "mbid-1"},
|
||
|
|
{Name: "Song Two", MBID: "mbid-2"},
|
||
|
|
}, nil).Once()
|
||
|
|
|
||
|
|
mockAgent.On("GetArtistTopSongs", mock.Anything, "artist-3", "Similar Artist", "", mock.Anything).
|
||
|
|
Return([]agents.Song{
|
||
|
|
{Name: "Song Three", MBID: "mbid-3"},
|
||
|
|
}, nil).Once()
|
||
|
|
|
||
|
|
mediaFileRepo.On("GetAll", mock.AnythingOfType("model.QueryOptions")).Return(model.MediaFiles{song1, song2}, nil).Once()
|
||
|
|
mediaFileRepo.On("GetAll", mock.AnythingOfType("model.QueryOptions")).Return(model.MediaFiles{song3}, nil).Once()
|
||
|
|
|
||
|
|
songs, err := provider.SimilarSongs(ctx, "artist-1", 3)
|
||
|
|
|
||
|
|
Expect(err).ToNot(HaveOccurred())
|
||
|
|
Expect(songs).To(HaveLen(3))
|
||
|
|
for _, song := range songs {
|
||
|
|
Expect(song.ID).To(BeElementOf("song-1", "song-2", "song-3"))
|
||
|
|
}
|
||
|
|
})
|
||
|
|
|
||
|
|
It("returns ErrNotFound when artist is not found", func() {
|
||
|
|
artistRepo.On("Get", "artist-unknown-artist").Return(nil, model.ErrNotFound)
|
||
|
|
mediaFileRepo.On("Get", "artist-unknown-artist").Return(nil, model.ErrNotFound)
|
||
|
|
albumRepo.On("Get", "artist-unknown-artist").Return(nil, model.ErrNotFound)
|
||
|
|
|
||
|
|
artistRepo.On("GetAll", mock.MatchedBy(func(opt model.QueryOptions) bool {
|
||
|
|
return opt.Max == 1 && opt.Filters != nil
|
||
|
|
})).Return(model.Artists{}, nil).Maybe()
|
||
|
|
|
||
|
|
songs, err := provider.SimilarSongs(ctx, "artist-unknown-artist", 5)
|
||
|
|
|
||
|
|
Expect(err).To(Equal(model.ErrNotFound))
|
||
|
|
Expect(songs).To(BeNil())
|
||
|
|
})
|
||
|
|
|
||
|
|
It("returns songs from main artist when GetSimilarArtists returns error", func() {
|
||
|
|
artist1 := model.Artist{ID: "artist-1", Name: "Artist One"}
|
||
|
|
song1 := model.MediaFile{ID: "song-1", Title: "Song One", ArtistID: "artist-1", MbzRecordingID: "mbid-1"}
|
||
|
|
|
||
|
|
artistRepo.On("Get", "artist-1").Return(&artist1, nil).Maybe()
|
||
|
|
artistRepo.On("GetAll", mock.MatchedBy(func(opt model.QueryOptions) bool {
|
||
|
|
return opt.Max == 1 && opt.Filters != nil
|
||
|
|
})).Return(model.Artists{artist1}, nil).Maybe()
|
||
|
|
|
||
|
|
// New similar songs by artist returns ErrNotFound to trigger fallback
|
||
|
|
agentsCombined.On("GetSimilarSongsByArtist", mock.Anything, "artist-1", "Artist One", "", mock.Anything).
|
||
|
|
Return(nil, agents.ErrNotFound).Once()
|
||
|
|
|
||
|
|
mockAgent.On("GetSimilarArtists", mock.Anything, "artist-1", "Artist One", "", 15).
|
||
|
|
Return(nil, errors.New("error getting similar artists")).Once()
|
||
|
|
|
||
|
|
artistRepo.On("GetAll", mock.MatchedBy(func(opt model.QueryOptions) bool {
|
||
|
|
return opt.Max == 0 && opt.Filters != nil
|
||
|
|
})).Return(model.Artists{}, nil).Once()
|
||
|
|
|
||
|
|
mockAgent.On("GetArtistTopSongs", mock.Anything, "artist-1", "Artist One", "", mock.Anything).
|
||
|
|
Return([]agents.Song{
|
||
|
|
{Name: "Song One", MBID: "mbid-1"},
|
||
|
|
}, nil).Once()
|
||
|
|
|
||
|
|
mediaFileRepo.On("GetAll", mock.AnythingOfType("model.QueryOptions")).Return(model.MediaFiles{song1}, nil).Once()
|
||
|
|
|
||
|
|
songs, err := provider.SimilarSongs(ctx, "artist-1", 5)
|
||
|
|
|
||
|
|
Expect(err).ToNot(HaveOccurred())
|
||
|
|
Expect(songs).To(HaveLen(1))
|
||
|
|
Expect(songs[0].ID).To(Equal("song-1"))
|
||
|
|
})
|
||
|
|
|
||
|
|
It("returns empty list when GetArtistTopSongs returns error", func() {
|
||
|
|
artist1 := model.Artist{ID: "artist-1", Name: "Artist One"}
|
||
|
|
|
||
|
|
artistRepo.On("Get", "artist-1").Return(&artist1, nil).Maybe()
|
||
|
|
artistRepo.On("GetAll", mock.MatchedBy(func(opt model.QueryOptions) bool {
|
||
|
|
return opt.Max == 1 && opt.Filters != nil
|
||
|
|
})).Return(model.Artists{artist1}, nil).Maybe()
|
||
|
|
|
||
|
|
// New similar songs by artist returns ErrNotFound to trigger fallback
|
||
|
|
agentsCombined.On("GetSimilarSongsByArtist", mock.Anything, "artist-1", "Artist One", "", mock.Anything).
|
||
|
|
Return(nil, agents.ErrNotFound).Once()
|
||
|
|
|
||
|
|
mockAgent.On("GetSimilarArtists", mock.Anything, "artist-1", "Artist One", "", 15).
|
||
|
|
Return([]agents.Artist{}, nil).Once()
|
||
|
|
|
||
|
|
artistRepo.On("GetAll", mock.MatchedBy(func(opt model.QueryOptions) bool {
|
||
|
|
return opt.Max == 0 && opt.Filters != nil
|
||
|
|
})).Return(model.Artists{}, nil).Once()
|
||
|
|
|
||
|
|
mockAgent.On("GetArtistTopSongs", mock.Anything, "artist-1", "Artist One", "", mock.Anything).
|
||
|
|
Return(nil, errors.New("error getting top songs")).Once()
|
||
|
|
|
||
|
|
songs, err := provider.SimilarSongs(ctx, "artist-1", 5)
|
||
|
|
|
||
|
|
Expect(err).ToNot(HaveOccurred())
|
||
|
|
Expect(songs).To(BeEmpty())
|
||
|
|
})
|
||
|
|
|
||
|
|
It("respects count parameter", func() {
|
||
|
|
artist1 := model.Artist{ID: "artist-1", Name: "Artist One"}
|
||
|
|
song1 := model.MediaFile{ID: "song-1", Title: "Song One", ArtistID: "artist-1", MbzRecordingID: "mbid-1"}
|
||
|
|
song2 := model.MediaFile{ID: "song-2", Title: "Song Two", ArtistID: "artist-1", MbzRecordingID: "mbid-2"}
|
||
|
|
|
||
|
|
artistRepo.On("Get", "artist-1").Return(&artist1, nil).Maybe()
|
||
|
|
artistRepo.On("GetAll", mock.MatchedBy(func(opt model.QueryOptions) bool {
|
||
|
|
return opt.Max == 1 && opt.Filters != nil
|
||
|
|
})).Return(model.Artists{artist1}, nil).Maybe()
|
||
|
|
|
||
|
|
// New similar songs by artist returns ErrNotFound to trigger fallback
|
||
|
|
agentsCombined.On("GetSimilarSongsByArtist", mock.Anything, "artist-1", "Artist One", "", mock.Anything).
|
||
|
|
Return(nil, agents.ErrNotFound).Once()
|
||
|
|
|
||
|
|
mockAgent.On("GetSimilarArtists", mock.Anything, "artist-1", "Artist One", "", 15).
|
||
|
|
Return([]agents.Artist{}, nil).Once()
|
||
|
|
|
||
|
|
artistRepo.On("GetAll", mock.MatchedBy(func(opt model.QueryOptions) bool {
|
||
|
|
return opt.Max == 0 && opt.Filters != nil
|
||
|
|
})).Return(model.Artists{}, nil).Once()
|
||
|
|
|
||
|
|
mockAgent.On("GetArtistTopSongs", mock.Anything, "artist-1", "Artist One", "", mock.Anything).
|
||
|
|
Return([]agents.Song{
|
||
|
|
{Name: "Song One", MBID: "mbid-1"},
|
||
|
|
{Name: "Song Two", MBID: "mbid-2"},
|
||
|
|
}, nil).Once()
|
||
|
|
|
||
|
|
mediaFileRepo.On("GetAll", mock.AnythingOfType("model.QueryOptions")).Return(model.MediaFiles{song1, song2}, nil).Once()
|
||
|
|
|
||
|
|
songs, err := provider.SimilarSongs(ctx, "artist-1", 1)
|
||
|
|
|
||
|
|
Expect(err).ToNot(HaveOccurred())
|
||
|
|
Expect(songs).To(HaveLen(1))
|
||
|
|
Expect(songs[0].ID).To(BeElementOf("song-1", "song-2"))
|
||
|
|
})
|
||
|
|
})
|