mirror of
https://github.com/navidrome/navidrome.git
synced 2026-05-03 06:51:16 +00:00
Replace the timer-per-playlist + WaitGroup.Go model with a single worker goroutine processing work items from a buffered channel. This eliminates race conditions from parallel plugin calls (rate limiting risk), unsynchronized map/field access across goroutines, and overlapping discovery and refresh timers. The worker owns all mutable state (refreshTimers, discoveryTimer) exclusively, while retryInterval and refreshTimerCount use atomics for safe test observability. Also adds retry-on-failure for GetAvailablePlaylists (5 min delay) and makes MockPlaylistRepo thread-safe with sync.RWMutex.
159 lines
3.3 KiB
Go
159 lines
3.3 KiB
Go
package tests
|
|
|
|
import (
|
|
"errors"
|
|
"sync"
|
|
|
|
"github.com/deluan/rest"
|
|
"github.com/navidrome/navidrome/model"
|
|
"github.com/navidrome/navidrome/model/id"
|
|
)
|
|
|
|
func CreateMockPlaylistRepo() *MockPlaylistRepo {
|
|
return &MockPlaylistRepo{
|
|
Data: make(map[string]*model.Playlist),
|
|
PathMap: make(map[string]*model.Playlist),
|
|
}
|
|
}
|
|
|
|
type MockPlaylistRepo struct {
|
|
model.PlaylistRepository
|
|
mu sync.RWMutex
|
|
Data map[string]*model.Playlist // keyed by ID
|
|
PathMap map[string]*model.Playlist // keyed by path
|
|
Last *model.Playlist
|
|
Deleted []string
|
|
Err bool
|
|
TracksRepo model.PlaylistTrackRepository
|
|
}
|
|
|
|
func (m *MockPlaylistRepo) SetError(err bool) {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
m.Err = err
|
|
}
|
|
|
|
func (m *MockPlaylistRepo) Get(id string) (*model.Playlist, error) {
|
|
m.mu.RLock()
|
|
defer m.mu.RUnlock()
|
|
if m.Err {
|
|
return nil, errors.New("error")
|
|
}
|
|
if m.Data != nil {
|
|
if pls, ok := m.Data[id]; ok {
|
|
return pls, nil
|
|
}
|
|
}
|
|
return nil, model.ErrNotFound
|
|
}
|
|
|
|
func (m *MockPlaylistRepo) GetWithTracks(id string, _, _ bool) (*model.Playlist, error) {
|
|
return m.Get(id)
|
|
}
|
|
|
|
func (m *MockPlaylistRepo) Put(pls *model.Playlist) error {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
if m.Err {
|
|
return errors.New("error")
|
|
}
|
|
if pls.ID == "" {
|
|
pls.ID = id.NewRandom()
|
|
}
|
|
m.Last = pls
|
|
if m.Data != nil {
|
|
m.Data[pls.ID] = pls
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (m *MockPlaylistRepo) FindByPath(path string) (*model.Playlist, error) {
|
|
m.mu.RLock()
|
|
defer m.mu.RUnlock()
|
|
if m.Err {
|
|
return nil, errors.New("error")
|
|
}
|
|
if m.PathMap != nil {
|
|
if pls, ok := m.PathMap[path]; ok {
|
|
return pls, nil
|
|
}
|
|
}
|
|
return nil, model.ErrNotFound
|
|
}
|
|
|
|
func (m *MockPlaylistRepo) Delete(id string) error {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
if m.Err {
|
|
return errors.New("error")
|
|
}
|
|
m.Deleted = append(m.Deleted, id)
|
|
return nil
|
|
}
|
|
|
|
func (m *MockPlaylistRepo) Tracks(_ string, _ bool) model.PlaylistTrackRepository {
|
|
m.mu.RLock()
|
|
defer m.mu.RUnlock()
|
|
return m.TracksRepo
|
|
}
|
|
|
|
func (m *MockPlaylistRepo) Exists(id string) (bool, error) {
|
|
m.mu.RLock()
|
|
defer m.mu.RUnlock()
|
|
if m.Err {
|
|
return false, errors.New("error")
|
|
}
|
|
if m.Data != nil {
|
|
_, found := m.Data[id]
|
|
return found, nil
|
|
}
|
|
return false, nil
|
|
}
|
|
|
|
func (m *MockPlaylistRepo) Count(_ ...rest.QueryOptions) (int64, error) {
|
|
m.mu.RLock()
|
|
defer m.mu.RUnlock()
|
|
if m.Err {
|
|
return 0, errors.New("error")
|
|
}
|
|
return int64(len(m.Data)), nil
|
|
}
|
|
|
|
func (m *MockPlaylistRepo) CountAll(_ ...model.QueryOptions) (int64, error) {
|
|
m.mu.RLock()
|
|
defer m.mu.RUnlock()
|
|
if m.Err {
|
|
return 0, errors.New("error")
|
|
}
|
|
return int64(len(m.Data)), nil
|
|
}
|
|
|
|
// Len returns the number of playlists in the repo in a thread-safe manner.
|
|
func (m *MockPlaylistRepo) Len() int {
|
|
m.mu.RLock()
|
|
defer m.mu.RUnlock()
|
|
return len(m.Data)
|
|
}
|
|
|
|
// GetData returns a playlist by ID in a thread-safe manner.
|
|
func (m *MockPlaylistRepo) GetData(id string) (*model.Playlist, bool) {
|
|
m.mu.RLock()
|
|
defer m.mu.RUnlock()
|
|
pls, ok := m.Data[id]
|
|
return pls, ok
|
|
}
|
|
|
|
// FindByPluginPlaylistID returns the first playlist with the given plugin playlist ID.
|
|
func (m *MockPlaylistRepo) FindByPluginPlaylistID(pluginPlaylistID string) *model.Playlist {
|
|
m.mu.RLock()
|
|
defer m.mu.RUnlock()
|
|
for _, pls := range m.Data {
|
|
if pls.PluginPlaylistID == pluginPlaylistID {
|
|
return pls
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
var _ model.PlaylistRepository = (*MockPlaylistRepo)(nil)
|