navidrome/tests/mock_data_store.go
Jiho Andrew Lee 19385dc28b Merge upstream/master into feat-podcast-ux-improvements/5420
Resolve conflicts with the stateless-repositories refactor (#6149):
- port podcast repositories, model interfaces, mocks, service and handlers to per-call ctx
- podcast channel repo now implements generic rest.Repository/Persistable
- register podcast repos in SQLStore and MockDataStore
- merge OpenSubsonic extensions (apiKeyAuthentication + podcast extensions)
- renumber podcast migrations after upstream's latest (20260930...)
2026-09-30 15:23:00 +09:00

454 lines
11 KiB
Go

package tests
import (
"context"
"sync"
"github.com/navidrome/navidrome/model"
)
type MockDataStore struct {
RealDS model.DataStore
MockedLibrary model.LibraryRepository
MockedFolder model.FolderRepository
MockedGenre model.GenreRepository
MockedAlbum model.AlbumRepository
MockedArtist model.ArtistRepository
MockedMediaFile model.MediaFileRepository
MockedTag model.TagRepository
MockedUser model.UserRepository
MockedProperty model.PropertyRepository
MockedPlayer model.PlayerRepository
MockedPlaylist model.PlaylistRepository
MockedPlayQueue model.PlayQueueRepository
MockedShare model.ShareRepository
MockedTranscoding model.TranscodingRepository
MockedUserProps model.UserPropsRepository
MockedScrobbleBuffer model.ScrobbleBufferRepository
MockedScrobble model.ScrobbleRepository
MockedRadio model.RadioRepository
MockedPlugin model.PluginRepository
MockedArtwork model.ArtworkRepository
MockedArtworkQueue model.ArtworkQueueRepository
MockedPodcastChannel model.PodcastChannelRepository
MockedPodcastEpisode model.PodcastEpisodeRepository
MockedPodcastTranscript model.PodcastTranscriptRepository
MockedPodcastPerson model.PodcastPersonRepository
MockedPodcastPodroll model.PodcastPodrollRepository
MockedPodcastLiveItem model.PodcastLiveItemRepository
MockedPodcastFunding model.PodcastFundingRepository
MockedPodcastImage model.PodcastImageRepository
scrobbleBufferMu sync.Mutex
repoMu sync.Mutex
// GC tracking
GCCalled bool
GCError error
}
func (db *MockDataStore) Library() model.LibraryRepository {
db.repoMu.Lock()
defer db.repoMu.Unlock()
if db.MockedLibrary != nil {
return db.MockedLibrary
}
if db.RealDS != nil {
return db.RealDS.Library()
}
db.MockedLibrary = &MockLibraryRepo{}
return db.MockedLibrary
}
func (db *MockDataStore) Folder() model.FolderRepository {
db.repoMu.Lock()
defer db.repoMu.Unlock()
if db.MockedFolder != nil {
return db.MockedFolder
}
if db.RealDS != nil {
return db.RealDS.Folder()
}
db.MockedFolder = struct{ model.FolderRepository }{}
return db.MockedFolder
}
func (db *MockDataStore) Tag() model.TagRepository {
db.repoMu.Lock()
defer db.repoMu.Unlock()
if db.MockedTag != nil {
return db.MockedTag
}
if db.RealDS != nil {
return db.RealDS.Tag()
}
db.MockedTag = &MockTagRepo{}
return db.MockedTag
}
func (db *MockDataStore) Album() model.AlbumRepository {
db.repoMu.Lock()
defer db.repoMu.Unlock()
if db.MockedAlbum != nil {
return db.MockedAlbum
}
if db.RealDS != nil {
return db.RealDS.Album()
}
db.MockedAlbum = CreateMockAlbumRepo()
return db.MockedAlbum
}
func (db *MockDataStore) Artist() model.ArtistRepository {
db.repoMu.Lock()
defer db.repoMu.Unlock()
if db.MockedArtist != nil {
return db.MockedArtist
}
if db.RealDS != nil {
return db.RealDS.Artist()
}
db.MockedArtist = CreateMockArtistRepo()
return db.MockedArtist
}
func (db *MockDataStore) MediaFile() model.MediaFileRepository {
db.repoMu.Lock()
defer db.repoMu.Unlock()
if db.RealDS != nil && db.MockedMediaFile == nil {
return db.RealDS.MediaFile()
}
if db.MockedMediaFile == nil {
db.MockedMediaFile = CreateMockMediaFileRepo()
}
return db.MockedMediaFile
}
func (db *MockDataStore) Genre() model.GenreRepository {
db.repoMu.Lock()
defer db.repoMu.Unlock()
if db.MockedGenre != nil {
return db.MockedGenre
}
if db.RealDS != nil {
return db.RealDS.Genre()
}
db.MockedGenre = &MockedGenreRepo{}
return db.MockedGenre
}
func (db *MockDataStore) Playlist() model.PlaylistRepository {
db.repoMu.Lock()
defer db.repoMu.Unlock()
if db.MockedPlaylist != nil {
return db.MockedPlaylist
}
if db.RealDS != nil {
return db.RealDS.Playlist()
}
db.MockedPlaylist = CreateMockPlaylistRepo()
return db.MockedPlaylist
}
func (db *MockDataStore) PlayQueue() model.PlayQueueRepository {
db.repoMu.Lock()
defer db.repoMu.Unlock()
if db.MockedPlayQueue != nil {
return db.MockedPlayQueue
}
if db.RealDS != nil {
return db.RealDS.PlayQueue()
}
db.MockedPlayQueue = &MockPlayQueueRepo{}
return db.MockedPlayQueue
}
func (db *MockDataStore) UserProps() model.UserPropsRepository {
db.repoMu.Lock()
defer db.repoMu.Unlock()
if db.MockedUserProps != nil {
return db.MockedUserProps
}
if db.RealDS != nil {
return db.RealDS.UserProps()
}
db.MockedUserProps = &MockedUserPropsRepo{}
return db.MockedUserProps
}
func (db *MockDataStore) Property() model.PropertyRepository {
db.repoMu.Lock()
defer db.repoMu.Unlock()
if db.MockedProperty != nil {
return db.MockedProperty
}
if db.RealDS != nil {
return db.RealDS.Property()
}
db.MockedProperty = &MockedPropertyRepo{}
return db.MockedProperty
}
func (db *MockDataStore) Share() model.ShareRepository {
db.repoMu.Lock()
defer db.repoMu.Unlock()
if db.MockedShare != nil {
return db.MockedShare
}
if db.RealDS != nil {
return db.RealDS.Share()
}
db.MockedShare = &MockShareRepo{}
return db.MockedShare
}
func (db *MockDataStore) User() model.UserRepository {
db.repoMu.Lock()
defer db.repoMu.Unlock()
if db.MockedUser != nil {
return db.MockedUser
}
if db.RealDS != nil {
return db.RealDS.User()
}
db.MockedUser = CreateMockUserRepo()
return db.MockedUser
}
func (db *MockDataStore) Transcoding() model.TranscodingRepository {
db.repoMu.Lock()
defer db.repoMu.Unlock()
if db.MockedTranscoding != nil {
return db.MockedTranscoding
}
if db.RealDS != nil {
return db.RealDS.Transcoding()
}
db.MockedTranscoding = struct{ model.TranscodingRepository }{}
return db.MockedTranscoding
}
func (db *MockDataStore) Player() model.PlayerRepository {
db.repoMu.Lock()
defer db.repoMu.Unlock()
if db.MockedPlayer != nil {
return db.MockedPlayer
}
if db.RealDS != nil {
return db.RealDS.Player()
}
db.MockedPlayer = CreateMockPlayerRepo()
return db.MockedPlayer
}
func (db *MockDataStore) ScrobbleBuffer() model.ScrobbleBufferRepository {
db.repoMu.Lock()
defer db.repoMu.Unlock()
if db.RealDS != nil && db.MockedScrobbleBuffer == nil {
return db.RealDS.ScrobbleBuffer()
}
db.scrobbleBufferMu.Lock()
defer db.scrobbleBufferMu.Unlock()
if db.MockedScrobbleBuffer == nil {
db.MockedScrobbleBuffer = &MockedScrobbleBufferRepo{}
}
return db.MockedScrobbleBuffer
}
func (db *MockDataStore) Scrobble() model.ScrobbleRepository {
db.repoMu.Lock()
defer db.repoMu.Unlock()
if db.MockedScrobble != nil {
return db.MockedScrobble
}
if db.RealDS != nil {
return db.RealDS.Scrobble()
}
db.MockedScrobble = &MockScrobbleRepo{}
return db.MockedScrobble
}
func (db *MockDataStore) Radio() model.RadioRepository {
db.repoMu.Lock()
defer db.repoMu.Unlock()
if db.MockedRadio != nil {
return db.MockedRadio
}
if db.RealDS != nil {
return db.RealDS.Radio()
}
db.MockedRadio = CreateMockedRadioRepo()
return db.MockedRadio
}
func (db *MockDataStore) Plugin() model.PluginRepository {
db.repoMu.Lock()
defer db.repoMu.Unlock()
if db.MockedPlugin != nil {
return db.MockedPlugin
}
if db.RealDS != nil {
return db.RealDS.Plugin()
}
db.MockedPlugin = CreateMockPluginRepo()
return db.MockedPlugin
}
func (db *MockDataStore) Artwork() model.ArtworkRepository {
db.repoMu.Lock()
defer db.repoMu.Unlock()
return db.artworkLocked()
}
// artworkLocked is the body of Artwork for callers already holding repoMu; repoMu is a plain
// Mutex, so re-entering through the exported method would deadlock.
func (db *MockDataStore) artworkLocked() model.ArtworkRepository {
if db.MockedArtwork != nil {
return db.MockedArtwork
}
if db.RealDS != nil {
return db.RealDS.Artwork()
}
db.MockedArtwork = CreateMockArtworkRepo()
return db.MockedArtwork
}
func (db *MockDataStore) ArtworkQueue() model.ArtworkQueueRepository {
db.repoMu.Lock()
defer db.repoMu.Unlock()
if db.MockedArtworkQueue != nil {
return db.MockedArtworkQueue
}
if db.RealDS != nil {
return db.RealDS.ArtworkQueue()
}
q := CreateMockArtworkQueueRepo()
if aw, ok := db.artworkLocked().(*MockArtworkRepo); ok {
q.ItemArtworkSource = aw
}
db.MockedArtworkQueue = q
return db.MockedArtworkQueue
}
func (db *MockDataStore) PodcastChannel() model.PodcastChannelRepository {
db.repoMu.Lock()
defer db.repoMu.Unlock()
if db.MockedPodcastChannel != nil {
return db.MockedPodcastChannel
}
if db.RealDS != nil {
return db.RealDS.PodcastChannel()
}
db.MockedPodcastChannel = CreateMockPodcastChannelRepo()
return db.MockedPodcastChannel
}
func (db *MockDataStore) PodcastEpisode() model.PodcastEpisodeRepository {
db.repoMu.Lock()
defer db.repoMu.Unlock()
if db.MockedPodcastEpisode != nil {
return db.MockedPodcastEpisode
}
if db.RealDS != nil {
return db.RealDS.PodcastEpisode()
}
db.MockedPodcastEpisode = CreateMockPodcastEpisodeRepo()
return db.MockedPodcastEpisode
}
func (db *MockDataStore) PodcastTranscript() model.PodcastTranscriptRepository {
db.repoMu.Lock()
defer db.repoMu.Unlock()
if db.MockedPodcastTranscript != nil {
return db.MockedPodcastTranscript
}
if db.RealDS != nil {
return db.RealDS.PodcastTranscript()
}
db.MockedPodcastTranscript = CreateMockPodcastTranscriptRepo()
return db.MockedPodcastTranscript
}
func (db *MockDataStore) PodcastPerson() model.PodcastPersonRepository {
db.repoMu.Lock()
defer db.repoMu.Unlock()
if db.MockedPodcastPerson != nil {
return db.MockedPodcastPerson
}
if db.RealDS != nil {
return db.RealDS.PodcastPerson()
}
db.MockedPodcastPerson = CreateMockPodcastPersonRepo()
return db.MockedPodcastPerson
}
func (db *MockDataStore) PodcastPodroll() model.PodcastPodrollRepository {
db.repoMu.Lock()
defer db.repoMu.Unlock()
if db.MockedPodcastPodroll != nil {
return db.MockedPodcastPodroll
}
if db.RealDS != nil {
return db.RealDS.PodcastPodroll()
}
db.MockedPodcastPodroll = CreateMockPodcastPodrollRepo()
return db.MockedPodcastPodroll
}
func (db *MockDataStore) PodcastLiveItem() model.PodcastLiveItemRepository {
db.repoMu.Lock()
defer db.repoMu.Unlock()
if db.MockedPodcastLiveItem != nil {
return db.MockedPodcastLiveItem
}
if db.RealDS != nil {
return db.RealDS.PodcastLiveItem()
}
db.MockedPodcastLiveItem = CreateMockPodcastLiveItemRepo()
return db.MockedPodcastLiveItem
}
func (db *MockDataStore) PodcastFunding() model.PodcastFundingRepository {
db.repoMu.Lock()
defer db.repoMu.Unlock()
if db.MockedPodcastFunding != nil {
return db.MockedPodcastFunding
}
if db.RealDS != nil {
return db.RealDS.PodcastFunding()
}
db.MockedPodcastFunding = CreateMockPodcastFundingRepo()
return db.MockedPodcastFunding
}
func (db *MockDataStore) PodcastImage() model.PodcastImageRepository {
db.repoMu.Lock()
defer db.repoMu.Unlock()
if db.MockedPodcastImage != nil {
return db.MockedPodcastImage
}
if db.RealDS != nil {
return db.RealDS.PodcastImage()
}
db.MockedPodcastImage = CreateMockPodcastImageRepo()
return db.MockedPodcastImage
}
func (db *MockDataStore) WithTx(block func(tx model.DataStore) error, label ...string) error {
return block(db)
}
func (db *MockDataStore) WithTxImmediate(block func(tx model.DataStore) error, label ...string) error {
return block(db)
}
func (db *MockDataStore) WithTxRetry(ctx context.Context, block func(ctx context.Context, tx model.DataStore) error, label ...string) error {
return block(ctx, db)
}
func (db *MockDataStore) GC(context.Context, ...int) error {
db.GCCalled = true
if db.GCError != nil {
return db.GCError
}
return nil
}