From 58367afaea666c45e114956747f8da1a54e1261a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Deluan=20Quint=C3=A3o?= Date: Tue, 8 Apr 2025 21:11:09 -0400 Subject: [PATCH] refactor: external_metadata -> external.Provider (#3903) * tests for TopSongs Signed-off-by: Deluan * convert to Ginkgo Signed-off-by: Deluan * consolidate tests Signed-off-by: Deluan * rename external metadata -wip Signed-off-by: Deluan * rename external metadata to extdata.Provider Signed-off-by: Deluan * refactor tests - wip Signed-off-by: Deluan * refactor test helpers Signed-off-by: Deluan * remove reflection Signed-off-by: Deluan * use mock.Mock Signed-off-by: Deluan * refactor Signed-off-by: Deluan * fix Signed-off-by: Deluan * receive Agents interface in Provider constructor Signed-off-by: Deluan * use mock for Agents Signed-off-by: Deluan * tests for SimilarSongs Signed-off-by: Deluan * remove duplication Signed-off-by: Deluan * ArtistImage tests Signed-off-by: Deluan * AlbumImage tests Signed-off-by: Deluan * fix provider error handling Signed-off-by: Deluan * UpdateAlbumInfo tests - wip Signed-off-by: Deluan * UpdateAlbumInfo tests - wip Signed-off-by: Deluan * refactor Signed-off-by: Deluan * refactor Signed-off-by: Deluan * refactor Signed-off-by: Deluan * UpdateArtistInfo tests - wip Signed-off-by: Deluan * clean up Signed-off-by: Deluan * refactor Signed-off-by: Deluan * fix test descriptions Signed-off-by: Deluan * refactor Signed-off-by: Deluan * refactor: rename extdata package to external Signed-off-by: Deluan --------- Signed-off-by: Deluan --- cmd/wire_gen.go | 19 +- core/artwork/artwork.go | 18 +- core/artwork/reader_album.go | 9 +- core/artwork/reader_artist.go | 9 +- core/artwork/sources.go | 10 +- core/external/extdata_helper_test.go | 270 ++++++++++++++++ core/external/extdata_suite_test.go | 17 + .../provider.go} | 106 +++--- core/external/provider_albumimage_test.go | 303 ++++++++++++++++++ core/external/provider_artistimage_test.go | 301 +++++++++++++++++ core/external/provider_similarsongs_test.go | 198 ++++++++++++ core/external/provider_topsongs_test.go | 193 +++++++++++ .../external/provider_updatealbuminfo_test.go | 170 ++++++++++ .../provider_updateartistinfo_test.go | 229 +++++++++++++ core/wire_providers.go | 4 +- server/subsonic/api.go | 51 +-- server/subsonic/browsing.go | 10 +- tests/mock_album_repo.go | 51 +-- tests/mock_artist_repo.go | 50 ++- tests/mock_genre_repo.go | 10 +- tests/mock_library_repo.go | 10 +- tests/mock_mediafile_repo.go | 54 ++-- tests/mock_property_repo.go | 14 +- tests/mock_radio_repository.go | 34 +- tests/mock_scrobble_buffer_repo.go | 14 +- tests/mock_user_props_repo.go | 14 +- 26 files changed, 1959 insertions(+), 209 deletions(-) create mode 100644 core/external/extdata_helper_test.go create mode 100644 core/external/extdata_suite_test.go rename core/{external_metadata.go => external/provider.go} (79%) create mode 100644 core/external/provider_albumimage_test.go create mode 100644 core/external/provider_artistimage_test.go create mode 100644 core/external/provider_similarsongs_test.go create mode 100644 core/external/provider_topsongs_test.go create mode 100644 core/external/provider_updatealbuminfo_test.go create mode 100644 core/external/provider_updateartistinfo_test.go diff --git a/cmd/wire_gen.go b/cmd/wire_gen.go index e5e72bf4f..d57aadc71 100644 --- a/cmd/wire_gen.go +++ b/cmd/wire_gen.go @@ -14,6 +14,7 @@ import ( "github.com/navidrome/navidrome/core/agents/lastfm" "github.com/navidrome/navidrome/core/agents/listenbrainz" "github.com/navidrome/navidrome/core/artwork" + "github.com/navidrome/navidrome/core/external" "github.com/navidrome/navidrome/core/ffmpeg" "github.com/navidrome/navidrome/core/metrics" "github.com/navidrome/navidrome/core/playback" @@ -66,8 +67,8 @@ func CreateSubsonicAPIRouter(ctx context.Context) *subsonic.Router { fileCache := artwork.GetImageCache() fFmpeg := ffmpeg.New() agentsAgents := agents.GetAgents(dataStore) - externalMetadata := core.NewExternalMetadata(dataStore, agentsAgents) - artworkArtwork := artwork.NewArtwork(dataStore, fileCache, fFmpeg, externalMetadata) + provider := external.NewProvider(dataStore, agentsAgents) + artworkArtwork := artwork.NewArtwork(dataStore, fileCache, fFmpeg, provider) transcodingCache := core.GetTranscodingCache() mediaStreamer := core.NewMediaStreamer(dataStore, fFmpeg, transcodingCache) share := core.NewShare(dataStore) @@ -80,7 +81,7 @@ func CreateSubsonicAPIRouter(ctx context.Context) *subsonic.Router { scannerScanner := scanner.New(ctx, dataStore, cacheWarmer, broker, playlists, metricsMetrics) playTracker := scrobbler.GetPlayTracker(dataStore, broker) playbackServer := playback.GetInstance(dataStore) - router := subsonic.New(dataStore, artworkArtwork, mediaStreamer, archiver, players, externalMetadata, scannerScanner, broker, playlists, playTracker, share, playbackServer) + router := subsonic.New(dataStore, artworkArtwork, mediaStreamer, archiver, players, provider, scannerScanner, broker, playlists, playTracker, share, playbackServer) return router } @@ -90,8 +91,8 @@ func CreatePublicRouter() *public.Router { fileCache := artwork.GetImageCache() fFmpeg := ffmpeg.New() agentsAgents := agents.GetAgents(dataStore) - externalMetadata := core.NewExternalMetadata(dataStore, agentsAgents) - artworkArtwork := artwork.NewArtwork(dataStore, fileCache, fFmpeg, externalMetadata) + provider := external.NewProvider(dataStore, agentsAgents) + artworkArtwork := artwork.NewArtwork(dataStore, fileCache, fFmpeg, provider) transcodingCache := core.GetTranscodingCache() mediaStreamer := core.NewMediaStreamer(dataStore, fFmpeg, transcodingCache) share := core.NewShare(dataStore) @@ -134,8 +135,8 @@ func CreateScanner(ctx context.Context) scanner.Scanner { fileCache := artwork.GetImageCache() fFmpeg := ffmpeg.New() agentsAgents := agents.GetAgents(dataStore) - externalMetadata := core.NewExternalMetadata(dataStore, agentsAgents) - artworkArtwork := artwork.NewArtwork(dataStore, fileCache, fFmpeg, externalMetadata) + provider := external.NewProvider(dataStore, agentsAgents) + artworkArtwork := artwork.NewArtwork(dataStore, fileCache, fFmpeg, provider) cacheWarmer := artwork.NewCacheWarmer(artworkArtwork, fileCache) broker := events.GetBroker() playlists := core.NewPlaylists(dataStore) @@ -150,8 +151,8 @@ func CreateScanWatcher(ctx context.Context) scanner.Watcher { fileCache := artwork.GetImageCache() fFmpeg := ffmpeg.New() agentsAgents := agents.GetAgents(dataStore) - externalMetadata := core.NewExternalMetadata(dataStore, agentsAgents) - artworkArtwork := artwork.NewArtwork(dataStore, fileCache, fFmpeg, externalMetadata) + provider := external.NewProvider(dataStore, agentsAgents) + artworkArtwork := artwork.NewArtwork(dataStore, fileCache, fFmpeg, provider) cacheWarmer := artwork.NewCacheWarmer(artworkArtwork, fileCache) broker := events.GetBroker() playlists := core.NewPlaylists(dataStore) diff --git a/core/artwork/artwork.go b/core/artwork/artwork.go index 3570dd7b4..f1f571e0d 100644 --- a/core/artwork/artwork.go +++ b/core/artwork/artwork.go @@ -8,7 +8,7 @@ import ( "time" "github.com/navidrome/navidrome/consts" - "github.com/navidrome/navidrome/core" + "github.com/navidrome/navidrome/core/external" "github.com/navidrome/navidrome/core/ffmpeg" "github.com/navidrome/navidrome/log" "github.com/navidrome/navidrome/model" @@ -24,15 +24,15 @@ type Artwork interface { GetOrPlaceholder(ctx context.Context, id string, size int, square bool) (io.ReadCloser, time.Time, error) } -func NewArtwork(ds model.DataStore, cache cache.FileCache, ffmpeg ffmpeg.FFmpeg, em core.ExternalMetadata) Artwork { - return &artwork{ds: ds, cache: cache, ffmpeg: ffmpeg, em: em} +func NewArtwork(ds model.DataStore, cache cache.FileCache, ffmpeg ffmpeg.FFmpeg, provider external.Provider) Artwork { + return &artwork{ds: ds, cache: cache, ffmpeg: ffmpeg, provider: provider} } type artwork struct { - ds model.DataStore - cache cache.FileCache - ffmpeg ffmpeg.FFmpeg - em core.ExternalMetadata + ds model.DataStore + cache cache.FileCache + ffmpeg ffmpeg.FFmpeg + provider external.Provider } type artworkReader interface { @@ -115,9 +115,9 @@ func (a *artwork) getArtworkReader(ctx context.Context, artID model.ArtworkID, s } else { switch artID.Kind { case model.KindArtistArtwork: - artReader, err = newArtistReader(ctx, a, artID, a.em) + artReader, err = newArtistReader(ctx, a, artID, a.provider) case model.KindAlbumArtwork: - artReader, err = newAlbumArtworkReader(ctx, a, artID, a.em) + artReader, err = newAlbumArtworkReader(ctx, a, artID, a.provider) case model.KindMediaFileArtwork: artReader, err = newMediafileArtworkReader(ctx, a, artID) case model.KindPlaylistArtwork: diff --git a/core/artwork/reader_album.go b/core/artwork/reader_album.go index f1ed9b63c..a320533ed 100644 --- a/core/artwork/reader_album.go +++ b/core/artwork/reader_album.go @@ -12,6 +12,7 @@ import ( "github.com/Masterminds/squirrel" "github.com/navidrome/navidrome/conf" "github.com/navidrome/navidrome/core" + "github.com/navidrome/navidrome/core/external" "github.com/navidrome/navidrome/core/ffmpeg" "github.com/navidrome/navidrome/model" ) @@ -19,14 +20,14 @@ import ( type albumArtworkReader struct { cacheKey a *artwork - em core.ExternalMetadata + provider external.Provider album model.Album updatedAt *time.Time imgFiles []string rootFolder string } -func newAlbumArtworkReader(ctx context.Context, artwork *artwork, artID model.ArtworkID, em core.ExternalMetadata) (*albumArtworkReader, error) { +func newAlbumArtworkReader(ctx context.Context, artwork *artwork, artID model.ArtworkID, provider external.Provider) (*albumArtworkReader, error) { al, err := artwork.ds.Album(ctx).Get(artID.ID) if err != nil { return nil, err @@ -37,7 +38,7 @@ func newAlbumArtworkReader(ctx context.Context, artwork *artwork, artID model.Ar } a := &albumArtworkReader{ a: artwork, - em: em, + provider: provider, album: *al, updatedAt: imagesUpdateAt, imgFiles: imgFiles, @@ -82,7 +83,7 @@ func (a *albumArtworkReader) fromCoverArtPriority(ctx context.Context, ffmpeg ff embedArtPath := filepath.Join(a.rootFolder, a.album.EmbedArtPath) ff = append(ff, fromTag(ctx, embedArtPath), fromFFmpegTag(ctx, ffmpeg, embedArtPath)) case pattern == "external": - ff = append(ff, fromAlbumExternalSource(ctx, a.album, a.em)) + ff = append(ff, fromAlbumExternalSource(ctx, a.album, a.provider)) case len(a.imgFiles) > 0: ff = append(ff, fromExternalFile(ctx, a.imgFiles, pattern)) } diff --git a/core/artwork/reader_artist.go b/core/artwork/reader_artist.go index e910ef93e..217044b7a 100644 --- a/core/artwork/reader_artist.go +++ b/core/artwork/reader_artist.go @@ -14,6 +14,7 @@ import ( "github.com/Masterminds/squirrel" "github.com/navidrome/navidrome/conf" "github.com/navidrome/navidrome/core" + "github.com/navidrome/navidrome/core/external" "github.com/navidrome/navidrome/log" "github.com/navidrome/navidrome/model" "github.com/navidrome/navidrome/utils/str" @@ -22,13 +23,13 @@ import ( type artistReader struct { cacheKey a *artwork - em core.ExternalMetadata + provider external.Provider artist model.Artist artistFolder string imgFiles []string } -func newArtistReader(ctx context.Context, artwork *artwork, artID model.ArtworkID, em core.ExternalMetadata) (*artistReader, error) { +func newArtistReader(ctx context.Context, artwork *artwork, artID model.ArtworkID, provider external.Provider) (*artistReader, error) { ar, err := artwork.ds.Artist(ctx).Get(artID.ID) if err != nil { return nil, err @@ -53,7 +54,7 @@ func newArtistReader(ctx context.Context, artwork *artwork, artID model.ArtworkI } a := &artistReader{ a: artwork, - em: em, + provider: provider, artist: *ar, artistFolder: artistFolder, imgFiles: imgFiles, @@ -95,7 +96,7 @@ func (a *artistReader) fromArtistArtPriority(ctx context.Context, priority strin pattern = strings.TrimSpace(pattern) switch { case pattern == "external": - ff = append(ff, fromArtistExternalSource(ctx, a.artist, a.em)) + ff = append(ff, fromArtistExternalSource(ctx, a.artist, a.provider)) case strings.HasPrefix(pattern, "album/"): ff = append(ff, fromExternalFile(ctx, a.imgFiles, strings.TrimPrefix(pattern, "album/"))) default: diff --git a/core/artwork/sources.go b/core/artwork/sources.go index f89708255..121e6c38b 100644 --- a/core/artwork/sources.go +++ b/core/artwork/sources.go @@ -17,7 +17,7 @@ import ( "github.com/dhowden/tag" "github.com/navidrome/navidrome/consts" - "github.com/navidrome/navidrome/core" + "github.com/navidrome/navidrome/core/external" "github.com/navidrome/navidrome/core/ffmpeg" "github.com/navidrome/navidrome/log" "github.com/navidrome/navidrome/model" @@ -157,9 +157,9 @@ func fromAlbumPlaceholder() sourceFunc { return r, consts.PlaceholderAlbumArt, nil } } -func fromArtistExternalSource(ctx context.Context, ar model.Artist, em core.ExternalMetadata) sourceFunc { +func fromArtistExternalSource(ctx context.Context, ar model.Artist, provider external.Provider) sourceFunc { return func() (io.ReadCloser, string, error) { - imageUrl, err := em.ArtistImage(ctx, ar.ID) + imageUrl, err := provider.ArtistImage(ctx, ar.ID) if err != nil { return nil, "", err } @@ -168,9 +168,9 @@ func fromArtistExternalSource(ctx context.Context, ar model.Artist, em core.Exte } } -func fromAlbumExternalSource(ctx context.Context, al model.Album, em core.ExternalMetadata) sourceFunc { +func fromAlbumExternalSource(ctx context.Context, al model.Album, provider external.Provider) sourceFunc { return func() (io.ReadCloser, string, error) { - imageUrl, err := em.AlbumImage(ctx, al.ID) + imageUrl, err := provider.AlbumImage(ctx, al.ID) if err != nil { return nil, "", err } diff --git a/core/external/extdata_helper_test.go b/core/external/extdata_helper_test.go new file mode 100644 index 000000000..367437815 --- /dev/null +++ b/core/external/extdata_helper_test.go @@ -0,0 +1,270 @@ +package external_test + +import ( + "context" + "errors" + + "github.com/navidrome/navidrome/core/agents" + "github.com/navidrome/navidrome/model" + "github.com/stretchr/testify/mock" +) + +// --- Shared Mock Implementations --- + +// mockArtistRepo mocks model.ArtistRepository +type mockArtistRepo struct { + mock.Mock + model.ArtistRepository +} + +func newMockArtistRepo() *mockArtistRepo { + return &mockArtistRepo{} +} + +// SetData sets up basic Get expectations. +func (m *mockArtistRepo) SetData(artists model.Artists) { + for _, a := range artists { + artistCopy := a + m.On("Get", artistCopy.ID).Return(&artistCopy, nil) + } +} + +// Get implements model.ArtistRepository. +func (m *mockArtistRepo) Get(id string) (*model.Artist, error) { + args := m.Called(id) + if args.Get(0) == nil { + return nil, args.Error(1) + } + return args.Get(0).(*model.Artist), args.Error(1) +} + +// GetAll implements model.ArtistRepository. +func (m *mockArtistRepo) GetAll(options ...model.QueryOptions) (model.Artists, error) { + argsSlice := make([]interface{}, len(options)) + for i, v := range options { + argsSlice[i] = v + } + args := m.Called(argsSlice...) + if args.Get(0) == nil { + return nil, args.Error(1) + } + return args.Get(0).(model.Artists), args.Error(1) +} + +// SetError is a helper to set up a generic error for GetAll. +func (m *mockArtistRepo) SetError(hasError bool) { + if hasError { + m.On("GetAll", mock.Anything).Return(nil, errors.New("mock repo error")) + } +} + +// FindByName is a helper to set up a GetAll expectation for finding by name. +func (m *mockArtistRepo) FindByName(name string, artist model.Artist) { + m.On("GetAll", mock.MatchedBy(func(opt model.QueryOptions) bool { + return opt.Filters != nil + })).Return(model.Artists{artist}, nil).Once() +} + +// mockMediaFileRepo mocks model.MediaFileRepository +type mockMediaFileRepo struct { + mock.Mock + model.MediaFileRepository +} + +func newMockMediaFileRepo() *mockMediaFileRepo { + return &mockMediaFileRepo{} +} + +// SetData sets up basic Get expectations. +func (m *mockMediaFileRepo) SetData(mediaFiles model.MediaFiles) { + for _, mf := range mediaFiles { + mfCopy := mf + m.On("Get", mfCopy.ID).Return(&mfCopy, nil) + } +} + +// Get implements model.MediaFileRepository. +func (m *mockMediaFileRepo) Get(id string) (*model.MediaFile, error) { + args := m.Called(id) + if args.Get(0) == nil { + return nil, args.Error(1) + } + return args.Get(0).(*model.MediaFile), args.Error(1) +} + +// GetAll implements model.MediaFileRepository. +func (m *mockMediaFileRepo) GetAll(options ...model.QueryOptions) (model.MediaFiles, error) { + argsSlice := make([]interface{}, len(options)) + for i, v := range options { + argsSlice[i] = v + } + args := m.Called(argsSlice...) + if args.Get(0) == nil { + return nil, args.Error(1) + } + return args.Get(0).(model.MediaFiles), args.Error(1) +} + +// SetError is a helper to set up a generic error for GetAll. +func (m *mockMediaFileRepo) SetError(hasError bool) { + if hasError { + m.On("GetAll", mock.Anything).Return(nil, errors.New("mock repo error")) + } +} + +// FindByMBID is a helper to set up a GetAll expectation for finding by MBID. +func (m *mockMediaFileRepo) FindByMBID(mbid string, mediaFile model.MediaFile) { + m.On("GetAll", mock.MatchedBy(func(opt model.QueryOptions) bool { + return opt.Filters != nil + })).Return(model.MediaFiles{mediaFile}, nil).Once() +} + +// FindByArtistAndTitle is a helper to set up a GetAll expectation for finding by artist/title. +func (m *mockMediaFileRepo) FindByArtistAndTitle(artistID string, title string, mediaFile model.MediaFile) { + m.On("GetAll", mock.MatchedBy(func(opt model.QueryOptions) bool { + return opt.Filters != nil + })).Return(model.MediaFiles{mediaFile}, nil).Once() +} + +// mockAlbumRepo mocks model.AlbumRepository +type mockAlbumRepo struct { + mock.Mock + model.AlbumRepository +} + +func newMockAlbumRepo() *mockAlbumRepo { + return &mockAlbumRepo{} +} + +// Get implements model.AlbumRepository. +func (m *mockAlbumRepo) Get(id string) (*model.Album, error) { + args := m.Called(id) + if args.Get(0) == nil { + return nil, args.Error(1) + } + return args.Get(0).(*model.Album), args.Error(1) +} + +// GetAll implements model.AlbumRepository. +func (m *mockAlbumRepo) GetAll(options ...model.QueryOptions) (model.Albums, error) { + argsSlice := make([]interface{}, len(options)) + for i, v := range options { + argsSlice[i] = v + } + args := m.Called(argsSlice...) + if args.Get(0) == nil { + return nil, args.Error(1) + } + return args.Get(0).(model.Albums), args.Error(1) +} + +// mockSimilarArtistAgent mocks agents implementing ArtistTopSongsRetriever and ArtistSimilarRetriever +type mockSimilarArtistAgent struct { + mock.Mock + agents.Interface // Embed to satisfy methods not explicitly mocked +} + +func (m *mockSimilarArtistAgent) AgentName() string { + return "mockSimilar" +} + +func (m *mockSimilarArtistAgent) GetArtistTopSongs(ctx context.Context, id, artistName, mbid string, count int) ([]agents.Song, error) { + args := m.Called(ctx, id, artistName, mbid, count) + if args.Get(0) != nil { + return args.Get(0).([]agents.Song), args.Error(1) + } + return nil, args.Error(1) +} + +func (m *mockSimilarArtistAgent) GetSimilarArtists(ctx context.Context, id, name, mbid string, limit int) ([]agents.Artist, error) { + args := m.Called(ctx, id, name, mbid, limit) + if args.Get(0) != nil { + return args.Get(0).([]agents.Artist), args.Error(1) + } + return nil, args.Error(1) +} + +// mockAgents mocks the main Agents interface used by Provider +type mockAgents struct { + mock.Mock // Embed testify mock + topSongsAgent agents.ArtistTopSongsRetriever + similarAgent agents.ArtistSimilarRetriever + imageAgent agents.ArtistImageRetriever + albumInfoAgent agents.AlbumInfoRetriever + bioAgent agents.ArtistBiographyRetriever + mbidAgent agents.ArtistMBIDRetriever + urlAgent agents.ArtistURLRetriever + agents.Interface +} + +func (m *mockAgents) AgentName() string { + return "mockCombined" +} + +func (m *mockAgents) GetSimilarArtists(ctx context.Context, id, name, mbid string, limit int) ([]agents.Artist, error) { + if m.similarAgent != nil { + return m.similarAgent.GetSimilarArtists(ctx, id, name, mbid, limit) + } + args := m.Called(ctx, id, name, mbid, limit) + if args.Get(0) != nil { + return args.Get(0).([]agents.Artist), args.Error(1) + } + return nil, args.Error(1) +} + +func (m *mockAgents) GetArtistTopSongs(ctx context.Context, id, artistName, mbid string, count int) ([]agents.Song, error) { + if m.topSongsAgent != nil { + return m.topSongsAgent.GetArtistTopSongs(ctx, id, artistName, mbid, count) + } + args := m.Called(ctx, id, artistName, mbid, count) + if args.Get(0) != nil { + return args.Get(0).([]agents.Song), args.Error(1) + } + return nil, args.Error(1) +} + +func (m *mockAgents) GetAlbumInfo(ctx context.Context, name, artist, mbid string) (*agents.AlbumInfo, error) { + if m.albumInfoAgent != nil { + return m.albumInfoAgent.GetAlbumInfo(ctx, name, artist, mbid) + } + args := m.Called(ctx, name, artist, mbid) + if args.Get(0) != nil { + return args.Get(0).(*agents.AlbumInfo), args.Error(1) + } + return nil, args.Error(1) +} + +func (m *mockAgents) GetArtistMBID(ctx context.Context, id string, name string) (string, error) { + if m.mbidAgent != nil { + return m.mbidAgent.GetArtistMBID(ctx, id, name) + } + args := m.Called(ctx, id, name) + return args.String(0), args.Error(1) +} + +func (m *mockAgents) GetArtistURL(ctx context.Context, id, name, mbid string) (string, error) { + if m.urlAgent != nil { + return m.urlAgent.GetArtistURL(ctx, id, name, mbid) + } + args := m.Called(ctx, id, name, mbid) + return args.String(0), args.Error(1) +} + +func (m *mockAgents) GetArtistBiography(ctx context.Context, id, name, mbid string) (string, error) { + if m.bioAgent != nil { + return m.bioAgent.GetArtistBiography(ctx, id, name, mbid) + } + args := m.Called(ctx, id, name, mbid) + return args.String(0), args.Error(1) +} + +func (m *mockAgents) GetArtistImages(ctx context.Context, id, name, mbid string) ([]agents.ExternalImage, error) { + if m.imageAgent != nil { + return m.imageAgent.GetArtistImages(ctx, id, name, mbid) + } + args := m.Called(ctx, id, name, mbid) + if args.Get(0) != nil { + return args.Get(0).([]agents.ExternalImage), args.Error(1) + } + return nil, args.Error(1) +} diff --git a/core/external/extdata_suite_test.go b/core/external/extdata_suite_test.go new file mode 100644 index 000000000..f059e76b3 --- /dev/null +++ b/core/external/extdata_suite_test.go @@ -0,0 +1,17 @@ +package external + +import ( + "testing" + + "github.com/navidrome/navidrome/log" + "github.com/navidrome/navidrome/tests" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +func TestExternal(t *testing.T) { + tests.Init(t, false) + log.SetLevel(log.LevelFatal) + RegisterFailHandler(Fail) + RunSpecs(t, "External Suite") +} diff --git a/core/external_metadata.go b/core/external/provider.go similarity index 79% rename from core/external_metadata.go rename to core/external/provider.go index d402c3a36..f27ded11b 100644 --- a/core/external_metadata.go +++ b/core/external/provider.go @@ -1,4 +1,4 @@ -package core +package external import ( "context" @@ -31,7 +31,7 @@ const ( refreshQueueLength = 2000 ) -type ExternalMetadata interface { +type Provider interface { UpdateAlbumInfo(ctx context.Context, id string) (*model.Album, error) UpdateArtistInfo(ctx context.Context, id string, count int, includeNotPresent bool) (*model.Artist, error) SimilarSongs(ctx context.Context, id string, count int) (model.MediaFiles, error) @@ -40,9 +40,9 @@ type ExternalMetadata interface { AlbumImage(ctx context.Context, id string) (*url.URL, error) } -type externalMetadata struct { +type provider struct { ds model.DataStore - ag *agents.Agents + ag Agents artistQueue refreshQueue[auxArtist] albumQueue refreshQueue[auxAlbum] } @@ -57,14 +57,24 @@ type auxArtist struct { Name string } -func NewExternalMetadata(ds model.DataStore, agents *agents.Agents) ExternalMetadata { - e := &externalMetadata{ds: ds, ag: agents} +type Agents interface { + agents.AlbumInfoRetriever + agents.ArtistBiographyRetriever + agents.ArtistMBIDRetriever + agents.ArtistImageRetriever + agents.ArtistSimilarRetriever + agents.ArtistTopSongsRetriever + agents.ArtistURLRetriever +} + +func NewProvider(ds model.DataStore, agents Agents) Provider { + e := &provider{ds: ds, ag: agents} e.artistQueue = newRefreshQueue(context.TODO(), e.populateArtistInfo) e.albumQueue = newRefreshQueue(context.TODO(), e.populateAlbumInfo) return e } -func (e *externalMetadata) getAlbum(ctx context.Context, id string) (auxAlbum, error) { +func (e *provider) getAlbum(ctx context.Context, id string) (auxAlbum, error) { var entity interface{} entity, err := model.GetEntityByID(ctx, e.ds, id) if err != nil { @@ -81,10 +91,11 @@ func (e *externalMetadata) getAlbum(ctx context.Context, id string) (auxAlbum, e default: return auxAlbum{}, model.ErrNotFound } + return album, nil } -func (e *externalMetadata) UpdateAlbumInfo(ctx context.Context, id string) (*model.Album, error) { +func (e *provider) UpdateAlbumInfo(ctx context.Context, id string) (*model.Album, error) { album, err := e.getAlbum(ctx, id) if err != nil { log.Info(ctx, "Not found", "id", id) @@ -109,7 +120,7 @@ func (e *externalMetadata) UpdateAlbumInfo(ctx context.Context, id string) (*mod return &album.Album, nil } -func (e *externalMetadata) populateAlbumInfo(ctx context.Context, album auxAlbum) (auxAlbum, error) { +func (e *provider) populateAlbumInfo(ctx context.Context, album auxAlbum) (auxAlbum, error) { start := time.Now() info, err := e.ag.GetAlbumInfo(ctx, album.Name, album.AlbumArtist, album.MbzAlbumID) if errors.Is(err, agents.ErrNotFound) { @@ -155,7 +166,7 @@ func (e *externalMetadata) populateAlbumInfo(ctx context.Context, album auxAlbum return album, nil } -func (e *externalMetadata) getArtist(ctx context.Context, id string) (auxArtist, error) { +func (e *provider) getArtist(ctx context.Context, id string) (auxArtist, error) { var entity interface{} entity, err := model.GetEntityByID(ctx, e.ds, id) if err != nil { @@ -177,7 +188,7 @@ func (e *externalMetadata) getArtist(ctx context.Context, id string) (auxArtist, return artist, nil } -func (e *externalMetadata) UpdateArtistInfo(ctx context.Context, id string, similarCount int, includeNotPresent bool) (*model.Artist, error) { +func (e *provider) UpdateArtistInfo(ctx context.Context, id string, similarCount int, includeNotPresent bool) (*model.Artist, error) { artist, err := e.refreshArtistInfo(ctx, id) if err != nil { return nil, err @@ -187,7 +198,7 @@ func (e *externalMetadata) UpdateArtistInfo(ctx context.Context, id string, simi return &artist.Artist, err } -func (e *externalMetadata) refreshArtistInfo(ctx context.Context, id string) (auxArtist, error) { +func (e *provider) refreshArtistInfo(ctx context.Context, id string) (auxArtist, error) { artist, err := e.getArtist(ctx, id) if err != nil { return auxArtist{}, err @@ -211,7 +222,7 @@ func (e *externalMetadata) refreshArtistInfo(ctx context.Context, id string) (au return artist, nil } -func (e *externalMetadata) populateArtistInfo(ctx context.Context, artist auxArtist) (auxArtist, error) { +func (e *provider) populateArtistInfo(ctx context.Context, artist auxArtist) (auxArtist, error) { start := time.Now() // Get MBID first, if it is not yet available if artist.MbzArtistID == "" { @@ -246,7 +257,7 @@ func (e *externalMetadata) populateArtistInfo(ctx context.Context, artist auxArt return artist, nil } -func (e *externalMetadata) SimilarSongs(ctx context.Context, id string, count int) (model.MediaFiles, error) { +func (e *provider) SimilarSongs(ctx context.Context, id string, count int) (model.MediaFiles, error) { artist, err := e.getArtist(ctx, id) if err != nil { return nil, err @@ -304,7 +315,7 @@ func (e *externalMetadata) SimilarSongs(ctx context.Context, id string, count in return similarSongs, nil } -func (e *externalMetadata) ArtistImage(ctx context.Context, id string) (*url.URL, error) { +func (e *provider) ArtistImage(ctx context.Context, id string) (*url.URL, error) { artist, err := e.getArtist(ctx, id) if err != nil { return nil, err @@ -318,24 +329,35 @@ func (e *externalMetadata) ArtistImage(ctx context.Context, id string) (*url.URL imageUrl := artist.ArtistImageUrl() if imageUrl == "" { - return nil, agents.ErrNotFound + return nil, model.ErrNotFound } return url.Parse(imageUrl) } -func (e *externalMetadata) AlbumImage(ctx context.Context, id string) (*url.URL, error) { +func (e *provider) AlbumImage(ctx context.Context, id string) (*url.URL, error) { album, err := e.getAlbum(ctx, id) if err != nil { return nil, err } info, err := e.ag.GetAlbumInfo(ctx, album.Name, album.AlbumArtist, album.MbzAlbumID) - if errors.Is(err, agents.ErrNotFound) { + if err != nil { + switch { + case errors.Is(err, agents.ErrNotFound): + log.Trace(ctx, "Album not found in agent", "albumID", id, "name", album.Name, "artist", album.AlbumArtist) + return nil, model.ErrNotFound + case errors.Is(err, context.Canceled): + log.Debug(ctx, "GetAlbumInfo call canceled", err) + default: + log.Warn(ctx, "Error getting album info from agent", "albumID", id, "name", album.Name, "artist", album.AlbumArtist, err) + } + return nil, err } - if utils.IsCtxDone(ctx) { - log.Warn(ctx, "AlbumImage call canceled", ctx.Err()) - return nil, ctx.Err() + + if info == nil { + log.Warn(ctx, "Agent returned nil info without error", "albumID", id, "name", album.Name, "artist", album.AlbumArtist) + return nil, model.ErrNotFound } // Return the biggest image @@ -346,26 +368,37 @@ func (e *externalMetadata) AlbumImage(ctx context.Context, id string) (*url.URL, } } if img.URL == "" { - return nil, agents.ErrNotFound + return nil, model.ErrNotFound } return url.Parse(img.URL) } -func (e *externalMetadata) TopSongs(ctx context.Context, artistName string, count int) (model.MediaFiles, error) { +func (e *provider) TopSongs(ctx context.Context, artistName string, count int) (model.MediaFiles, error) { artist, err := e.findArtistByName(ctx, artistName) if err != nil { log.Error(ctx, "Artist not found", "name", artistName, err) return nil, nil } - return e.getMatchingTopSongs(ctx, e.ag, artist, count) + songs, err := e.getMatchingTopSongs(ctx, e.ag, artist, count) + if err != nil { + switch { + case errors.Is(err, agents.ErrNotFound): + log.Trace(ctx, "TopSongs not found", "name", artistName) + return nil, model.ErrNotFound + case errors.Is(err, context.Canceled): + log.Debug(ctx, "TopSongs call canceled", err) + default: + log.Warn(ctx, "Error getting top songs from agent", "artist", artistName, err) + } + + return nil, err + } + return songs, nil } -func (e *externalMetadata) getMatchingTopSongs(ctx context.Context, agent agents.ArtistTopSongsRetriever, artist *auxArtist, count int) (model.MediaFiles, error) { +func (e *provider) getMatchingTopSongs(ctx context.Context, agent agents.ArtistTopSongsRetriever, artist *auxArtist, count int) (model.MediaFiles, error) { songs, err := agent.GetArtistTopSongs(ctx, artist.ID, artist.Name, artist.MbzArtistID, count) - if errors.Is(err, agents.ErrNotFound) { - return nil, nil - } if err != nil { return nil, err } @@ -386,10 +419,11 @@ func (e *externalMetadata) getMatchingTopSongs(ctx context.Context, agent agents } else { log.Debug(ctx, "Found matching top songs", "name", artist.Name, "numSongs", len(mfs)) } + return mfs, nil } -func (e *externalMetadata) findMatchingTrack(ctx context.Context, mbid string, artistID, title string) (*model.MediaFile, error) { +func (e *provider) findMatchingTrack(ctx context.Context, mbid string, artistID, title string) (*model.MediaFile, error) { if mbid != "" { mfs, err := e.ds.MediaFile(ctx).GetAll(model.QueryOptions{ Filters: squirrel.And{ @@ -420,7 +454,7 @@ func (e *externalMetadata) findMatchingTrack(ctx context.Context, mbid string, a return &mfs[0], nil } -func (e *externalMetadata) callGetURL(ctx context.Context, agent agents.ArtistURLRetriever, artist *auxArtist) { +func (e *provider) callGetURL(ctx context.Context, agent agents.ArtistURLRetriever, artist *auxArtist) { artisURL, err := agent.GetArtistURL(ctx, artist.ID, artist.Name, artist.MbzArtistID) if err != nil { return @@ -428,7 +462,7 @@ func (e *externalMetadata) callGetURL(ctx context.Context, agent agents.ArtistUR artist.ExternalUrl = artisURL } -func (e *externalMetadata) callGetBiography(ctx context.Context, agent agents.ArtistBiographyRetriever, artist *auxArtist) { +func (e *provider) callGetBiography(ctx context.Context, agent agents.ArtistBiographyRetriever, artist *auxArtist) { bio, err := agent.GetArtistBiography(ctx, artist.ID, str.Clear(artist.Name), artist.MbzArtistID) if err != nil { return @@ -438,7 +472,7 @@ func (e *externalMetadata) callGetBiography(ctx context.Context, agent agents.Ar artist.Biography = strings.ReplaceAll(bio, " 0 { m.Options = qo[0] } - if m.err { + if m.Err { return nil, errors.New("unexpected error") } - return m.all, nil + return m.All, nil } func (m *MockAlbumRepo) IncPlayCount(id string, timestamp time.Time) error { - if m.err { + if m.Err { return errors.New("unexpected error") } - if d, ok := m.data[id]; ok { + if d, ok := m.Data[id]; ok { d.PlayCount++ d.PlayDate = ×tamp return nil @@ -85,15 +85,15 @@ func (m *MockAlbumRepo) IncPlayCount(id string, timestamp time.Time) error { return model.ErrNotFound } func (m *MockAlbumRepo) CountAll(...model.QueryOptions) (int64, error) { - return int64(len(m.all)), nil + return int64(len(m.All)), nil } func (m *MockAlbumRepo) GetTouchedAlbums(libID int) (model.AlbumCursor, error) { - if m.err { + if m.Err { return nil, errors.New("unexpected error") } return func(yield func(model.Album, error) bool) { - for _, a := range m.data { + for _, a := range m.Data { if a.ID == "error" { if !yield(*a, errors.New("error")) { break @@ -110,4 +110,11 @@ func (m *MockAlbumRepo) GetTouchedAlbums(libID int) (model.AlbumCursor, error) { }, nil } +func (m *MockAlbumRepo) UpdateExternalInfo(album *model.Album) error { + if m.Err { + return errors.New("unexpected error") + } + return nil +} + var _ model.AlbumRepository = (*MockAlbumRepo)(nil) diff --git a/tests/mock_artist_repo.go b/tests/mock_artist_repo.go index fad7c78d3..7058cead0 100644 --- a/tests/mock_artist_repo.go +++ b/tests/mock_artist_repo.go @@ -10,61 +10,61 @@ import ( func CreateMockArtistRepo() *MockArtistRepo { return &MockArtistRepo{ - data: make(map[string]*model.Artist), + Data: make(map[string]*model.Artist), } } type MockArtistRepo struct { model.ArtistRepository - data map[string]*model.Artist - err bool + Data map[string]*model.Artist + Err bool } func (m *MockArtistRepo) SetError(err bool) { - m.err = err + m.Err = err } func (m *MockArtistRepo) SetData(artists model.Artists) { - m.data = make(map[string]*model.Artist) + m.Data = make(map[string]*model.Artist) for i, a := range artists { - m.data[a.ID] = &artists[i] + m.Data[a.ID] = &artists[i] } } func (m *MockArtistRepo) Exists(id string) (bool, error) { - if m.err { + if m.Err { return false, errors.New("Error!") } - _, found := m.data[id] + _, found := m.Data[id] return found, nil } func (m *MockArtistRepo) Get(id string) (*model.Artist, error) { - if m.err { + if m.Err { return nil, errors.New("Error!") } - if d, ok := m.data[id]; ok { + if d, ok := m.Data[id]; ok { return d, nil } return nil, model.ErrNotFound } func (m *MockArtistRepo) Put(ar *model.Artist, columsToUpdate ...string) error { - if m.err { + if m.Err { return errors.New("error") } if ar.ID == "" { ar.ID = id.NewRandom() } - m.data[ar.ID] = ar + m.Data[ar.ID] = ar return nil } func (m *MockArtistRepo) IncPlayCount(id string, timestamp time.Time) error { - if m.err { + if m.Err { return errors.New("error") } - if d, ok := m.data[id]; ok { + if d, ok := m.Data[id]; ok { d.PlayCount++ d.PlayDate = ×tamp return nil @@ -72,4 +72,26 @@ func (m *MockArtistRepo) IncPlayCount(id string, timestamp time.Time) error { return model.ErrNotFound } +func (m *MockArtistRepo) GetAll(options ...model.QueryOptions) (model.Artists, error) { + if m.Err { + return nil, errors.New("mock repo error") + } + var allArtists model.Artists + for _, artist := range m.Data { + allArtists = append(allArtists, *artist) + } + // Apply Max=1 if present (simple simulation for findArtistByName) + if len(options) > 0 && options[0].Max == 1 && len(allArtists) > 0 { + return allArtists[:1], nil + } + return allArtists, nil +} + +func (m *MockArtistRepo) UpdateExternalInfo(artist *model.Artist) error { + if m.Err { + return errors.New("mock repo error") + } + return nil +} + var _ model.ArtistRepository = (*MockArtistRepo)(nil) diff --git a/tests/mock_genre_repo.go b/tests/mock_genre_repo.go index a24f2b0c8..122ccc278 100644 --- a/tests/mock_genre_repo.go +++ b/tests/mock_genre_repo.go @@ -6,12 +6,12 @@ import ( type MockedGenreRepo struct { Error error - data map[string]model.Genre + Data map[string]model.Genre } func (r *MockedGenreRepo) init() { - if r.data == nil { - r.data = make(map[string]model.Genre) + if r.Data == nil { + r.Data = make(map[string]model.Genre) } } @@ -22,7 +22,7 @@ func (r *MockedGenreRepo) GetAll(...model.QueryOptions) (model.Genres, error) { r.init() var all model.Genres - for _, g := range r.data { + for _, g := range r.Data { all = append(all, g) } return all, nil @@ -33,6 +33,6 @@ func (r *MockedGenreRepo) Put(g *model.Genre) error { return r.Error } r.init() - r.data[g.ID] = *g + r.Data[g.ID] = *g return nil } diff --git a/tests/mock_library_repo.go b/tests/mock_library_repo.go index 264dbe24c..907a9d487 100644 --- a/tests/mock_library_repo.go +++ b/tests/mock_library_repo.go @@ -7,14 +7,14 @@ import ( type MockLibraryRepo struct { model.LibraryRepository - data map[int]model.Library + Data map[int]model.Library Err error } func (m *MockLibraryRepo) SetData(data model.Libraries) { - m.data = make(map[int]model.Library) + m.Data = make(map[int]model.Library) for _, d := range data { - m.data[d.ID] = d + m.Data[d.ID] = d } } @@ -22,14 +22,14 @@ func (m *MockLibraryRepo) GetAll(...model.QueryOptions) (model.Libraries, error) if m.Err != nil { return nil, m.Err } - return maps.Values(m.data), nil + return maps.Values(m.Data), nil } func (m *MockLibraryRepo) GetPath(id int) (string, error) { if m.Err != nil { return "", m.Err } - if lib, ok := m.data[id]; ok { + if lib, ok := m.Data[id]; ok { return lib.Path, nil } return "", model.ErrNotFound diff --git a/tests/mock_mediafile_repo.go b/tests/mock_mediafile_repo.go index 4978e88bb..01d82e03b 100644 --- a/tests/mock_mediafile_repo.go +++ b/tests/mock_mediafile_repo.go @@ -14,40 +14,40 @@ import ( func CreateMockMediaFileRepo() *MockMediaFileRepo { return &MockMediaFileRepo{ - data: make(map[string]*model.MediaFile), + Data: make(map[string]*model.MediaFile), } } type MockMediaFileRepo struct { model.MediaFileRepository - data map[string]*model.MediaFile - err bool + Data map[string]*model.MediaFile + Err bool } func (m *MockMediaFileRepo) SetError(err bool) { - m.err = err + m.Err = err } func (m *MockMediaFileRepo) SetData(mfs model.MediaFiles) { - m.data = make(map[string]*model.MediaFile) + m.Data = make(map[string]*model.MediaFile) for i, mf := range mfs { - m.data[mf.ID] = &mfs[i] + m.Data[mf.ID] = &mfs[i] } } func (m *MockMediaFileRepo) Exists(id string) (bool, error) { - if m.err { + if m.Err { return false, errors.New("error") } - _, found := m.data[id] + _, found := m.Data[id] return found, nil } func (m *MockMediaFileRepo) Get(id string) (*model.MediaFile, error) { - if m.err { + if m.Err { return nil, errors.New("error") } - if d, ok := m.data[id]; ok { + if d, ok := m.Data[id]; ok { // Intentionally clone the file and remove participants. This should // catch any caller that actually means to call GetWithParticipants res := *d @@ -58,52 +58,52 @@ func (m *MockMediaFileRepo) Get(id string) (*model.MediaFile, error) { } func (m *MockMediaFileRepo) GetWithParticipants(id string) (*model.MediaFile, error) { - if m.err { + if m.Err { return nil, errors.New("error") } - if d, ok := m.data[id]; ok { + if d, ok := m.Data[id]; ok { return d, nil } return nil, model.ErrNotFound } func (m *MockMediaFileRepo) GetAll(...model.QueryOptions) (model.MediaFiles, error) { - if m.err { + if m.Err { return nil, errors.New("error") } - values := slices.Collect(maps.Values(m.data)) + values := slices.Collect(maps.Values(m.Data)) return slice.Map(values, func(p *model.MediaFile) model.MediaFile { return *p }), nil } func (m *MockMediaFileRepo) Put(mf *model.MediaFile) error { - if m.err { + if m.Err { return errors.New("error") } if mf.ID == "" { mf.ID = id.NewRandom() } - m.data[mf.ID] = mf + m.Data[mf.ID] = mf return nil } func (m *MockMediaFileRepo) Delete(id string) error { - if m.err { + if m.Err { return errors.New("error") } - if _, ok := m.data[id]; !ok { + if _, ok := m.Data[id]; !ok { return model.ErrNotFound } - delete(m.data, id) + delete(m.Data, id) return nil } func (m *MockMediaFileRepo) IncPlayCount(id string, timestamp time.Time) error { - if m.err { + if m.Err { return errors.New("error") } - if d, ok := m.data[id]; ok { + if d, ok := m.Data[id]; ok { d.PlayCount++ d.PlayDate = ×tamp return nil @@ -112,12 +112,12 @@ func (m *MockMediaFileRepo) IncPlayCount(id string, timestamp time.Time) error { } func (m *MockMediaFileRepo) FindByAlbum(artistId string) (model.MediaFiles, error) { - if m.err { + if m.Err { return nil, errors.New("error") } - var res = make(model.MediaFiles, len(m.data)) + var res = make(model.MediaFiles, len(m.Data)) i := 0 - for _, a := range m.data { + for _, a := range m.Data { if a.AlbumID == artistId { res[i] = *a i++ @@ -128,17 +128,17 @@ func (m *MockMediaFileRepo) FindByAlbum(artistId string) (model.MediaFiles, erro } func (m *MockMediaFileRepo) GetMissingAndMatching(libId int) (model.MediaFileCursor, error) { - if m.err { + if m.Err { return nil, errors.New("error") } var res model.MediaFiles - for _, a := range m.data { + for _, a := range m.Data { if a.LibraryID == libId && a.Missing { res = append(res, *a) } } - for _, a := range m.data { + for _, a := range m.Data { if a.LibraryID == libId && !(*a).Missing && slices.IndexFunc(res, func(mediaFile model.MediaFile) bool { return mediaFile.PID == a.PID }) != -1 { diff --git a/tests/mock_property_repo.go b/tests/mock_property_repo.go index 39dec17b3..9adc66e6d 100644 --- a/tests/mock_property_repo.go +++ b/tests/mock_property_repo.go @@ -5,12 +5,12 @@ import "github.com/navidrome/navidrome/model" type MockedPropertyRepo struct { model.PropertyRepository Error error - data map[string]string + Data map[string]string } func (p *MockedPropertyRepo) init() { - if p.data == nil { - p.data = make(map[string]string) + if p.Data == nil { + p.Data = make(map[string]string) } } @@ -19,7 +19,7 @@ func (p *MockedPropertyRepo) Put(id string, value string) error { return p.Error } p.init() - p.data[id] = value + p.Data[id] = value return nil } @@ -28,7 +28,7 @@ func (p *MockedPropertyRepo) Get(id string) (string, error) { return "", p.Error } p.init() - if v, ok := p.data[id]; ok { + if v, ok := p.Data[id]; ok { return v, nil } return "", model.ErrNotFound @@ -39,8 +39,8 @@ func (p *MockedPropertyRepo) Delete(id string) error { return p.Error } p.init() - if _, ok := p.data[id]; ok { - delete(p.data, id) + if _, ok := p.Data[id]; ok { + delete(p.Data, id) return nil } return model.ErrNotFound diff --git a/tests/mock_radio_repository.go b/tests/mock_radio_repository.go index a1a584320..279b735db 100644 --- a/tests/mock_radio_repository.go +++ b/tests/mock_radio_repository.go @@ -9,9 +9,9 @@ import ( type MockedRadioRepo struct { model.RadioRepository - data map[string]*model.Radio - all model.Radios - err bool + Data map[string]*model.Radio + All model.Radios + Err bool Options model.QueryOptions } @@ -20,44 +20,44 @@ func CreateMockedRadioRepo() *MockedRadioRepo { } func (m *MockedRadioRepo) SetError(err bool) { - m.err = err + m.Err = err } func (m *MockedRadioRepo) CountAll(options ...model.QueryOptions) (int64, error) { - if m.err { + if m.Err { return 0, errors.New("error") } - return int64(len(m.data)), nil + return int64(len(m.Data)), nil } func (m *MockedRadioRepo) Delete(id string) error { - if m.err { + if m.Err { return errors.New("Error!") } - _, found := m.data[id] + _, found := m.Data[id] if !found { return errors.New("not found") } - delete(m.data, id) + delete(m.Data, id) return nil } func (m *MockedRadioRepo) Exists(id string) (bool, error) { - if m.err { + if m.Err { return false, errors.New("Error!") } - _, found := m.data[id] + _, found := m.Data[id] return found, nil } func (m *MockedRadioRepo) Get(id string) (*model.Radio, error) { - if m.err { + if m.Err { return nil, errors.New("Error!") } - if d, ok := m.data[id]; ok { + if d, ok := m.Data[id]; ok { return d, nil } return nil, model.ErrNotFound @@ -67,19 +67,19 @@ func (m *MockedRadioRepo) GetAll(qo ...model.QueryOptions) (model.Radios, error) if len(qo) > 0 { m.Options = qo[0] } - if m.err { + if m.Err { return nil, errors.New("Error!") } - return m.all, nil + return m.All, nil } func (m *MockedRadioRepo) Put(radio *model.Radio) error { - if m.err { + if m.Err { return errors.New("error") } if radio.ID == "" { radio.ID = id.NewRandom() } - m.data[radio.ID] = radio + m.Data[radio.ID] = radio return nil } diff --git a/tests/mock_scrobble_buffer_repo.go b/tests/mock_scrobble_buffer_repo.go index 06b28af75..407c673eb 100644 --- a/tests/mock_scrobble_buffer_repo.go +++ b/tests/mock_scrobble_buffer_repo.go @@ -8,7 +8,7 @@ import ( type MockedScrobbleBufferRepo struct { Error error - data model.ScrobbleEntries + Data model.ScrobbleEntries } func CreateMockedScrobbleBufferRepo() *MockedScrobbleBufferRepo { @@ -20,7 +20,7 @@ func (m *MockedScrobbleBufferRepo) UserIDs(service string) ([]string, error) { return nil, m.Error } userIds := make(map[string]struct{}) - for _, e := range m.data { + for _, e := range m.Data { if e.Service == service { userIds[e.UserID] = struct{}{} } @@ -36,7 +36,7 @@ func (m *MockedScrobbleBufferRepo) Enqueue(service, userId, mediaFileId string, if m.Error != nil { return m.Error } - m.data = append(m.data, model.ScrobbleEntry{ + m.Data = append(m.Data, model.ScrobbleEntry{ MediaFile: model.MediaFile{ID: mediaFileId}, Service: service, UserID: userId, @@ -50,7 +50,7 @@ func (m *MockedScrobbleBufferRepo) Next(service, userId string) (*model.Scrobble if m.Error != nil { return nil, m.Error } - for _, e := range m.data { + for _, e := range m.Data { if e.Service == service && e.UserID == userId { return &e, nil } @@ -63,13 +63,13 @@ func (m *MockedScrobbleBufferRepo) Dequeue(entry *model.ScrobbleEntry) error { return m.Error } newData := model.ScrobbleEntries{} - for _, e := range m.data { + for _, e := range m.Data { if e.Service == entry.Service && e.UserID == entry.UserID && e.PlayTime == entry.PlayTime && e.MediaFile.ID == entry.MediaFile.ID { continue } newData = append(newData, e) } - m.data = newData + m.Data = newData return nil } @@ -77,5 +77,5 @@ func (m *MockedScrobbleBufferRepo) Length() (int64, error) { if m.Error != nil { return 0, m.Error } - return int64(len(m.data)), nil + return int64(len(m.Data)), nil } diff --git a/tests/mock_user_props_repo.go b/tests/mock_user_props_repo.go index b1880c999..1b1e17650 100644 --- a/tests/mock_user_props_repo.go +++ b/tests/mock_user_props_repo.go @@ -5,12 +5,12 @@ import "github.com/navidrome/navidrome/model" type MockedUserPropsRepo struct { model.UserPropsRepository Error error - data map[string]string + Data map[string]string } func (p *MockedUserPropsRepo) init() { - if p.data == nil { - p.data = make(map[string]string) + if p.Data == nil { + p.Data = make(map[string]string) } } @@ -19,7 +19,7 @@ func (p *MockedUserPropsRepo) Put(userId, key string, value string) error { return p.Error } p.init() - p.data[userId+key] = value + p.Data[userId+key] = value return nil } @@ -28,7 +28,7 @@ func (p *MockedUserPropsRepo) Get(userId, key string) (string, error) { return "", p.Error } p.init() - if v, ok := p.data[userId+key]; ok { + if v, ok := p.Data[userId+key]; ok { return v, nil } return "", model.ErrNotFound @@ -39,8 +39,8 @@ func (p *MockedUserPropsRepo) Delete(userId, key string) error { return p.Error } p.init() - if _, ok := p.data[userId+key]; ok { - delete(p.data, userId+key) + if _, ok := p.Data[userId+key]; ok { + delete(p.Data, userId+key) return nil } return model.ErrNotFound