mirror of
https://github.com/navidrome/navidrome.git
synced 2026-08-01 07:21:17 +00:00
227 lines
7.7 KiB
Go
227 lines
7.7 KiB
Go
package artwork
|
|
|
|
import (
|
|
"context"
|
|
"time"
|
|
|
|
"github.com/navidrome/navidrome/conf"
|
|
"github.com/navidrome/navidrome/conf/configtest"
|
|
"github.com/navidrome/navidrome/model"
|
|
"github.com/navidrome/navidrome/model/request"
|
|
"github.com/navidrome/navidrome/tests"
|
|
. "github.com/onsi/ginkgo/v2"
|
|
. "github.com/onsi/gomega"
|
|
)
|
|
|
|
// visibilityPlaylistDS models playlist_repository's userFilter: a private playlist is only
|
|
// visible when the ctx carries an admin, so headless work must wrap ctx with one first.
|
|
type visibilityPlaylistDS struct {
|
|
*tests.MockDataStore
|
|
private model.Playlist
|
|
tracks model.PlaylistTrackRepository
|
|
}
|
|
|
|
func (v *visibilityPlaylistDS) Playlist(ctx context.Context) model.PlaylistRepository {
|
|
repo := tests.CreateMockPlaylistRepo()
|
|
repo.TracksRepo = v.tracks
|
|
if u, ok := request.UserFrom(ctx); ok && u.IsAdmin {
|
|
repo.SetData(model.Playlists{v.private})
|
|
}
|
|
return repo
|
|
}
|
|
|
|
func adminUserRepo() *tests.MockedUserRepo {
|
|
repo := tests.CreateMockUserRepo()
|
|
Expect(repo.Put(&model.User{ID: "admin", UserName: "admin", IsAdmin: true})).To(Succeed())
|
|
return repo
|
|
}
|
|
|
|
// orderTrackingQueueRepo records the item kind of each Enqueue call, so tests can
|
|
// assert phase ordering (artists-first) that same-priority timestamps can't guarantee.
|
|
type orderTrackingQueueRepo struct {
|
|
*tests.MockArtworkQueueRepo
|
|
callKinds []string
|
|
}
|
|
|
|
func (o *orderTrackingQueueRepo) Enqueue(items ...model.ArtworkQueueItem) error {
|
|
if len(items) > 0 {
|
|
o.callKinds = append(o.callKinds, items[0].ItemKind)
|
|
}
|
|
return o.MockArtworkQueueRepo.Enqueue(items...)
|
|
}
|
|
|
|
var _ = Describe("Housekeeping", func() {
|
|
var (
|
|
ctx context.Context
|
|
ds *tests.MockDataStore
|
|
queueRepo *orderTrackingQueueRepo
|
|
propRepo *tests.MockedPropertyRepo
|
|
)
|
|
|
|
BeforeEach(func() {
|
|
DeferCleanup(configtest.SetupConfig())
|
|
ctx = context.Background()
|
|
conf.Server.CoverArtPriority = "embedded, folder"
|
|
conf.Server.ArtistArtPriority = "artist.jpg"
|
|
conf.Server.Agents = "spotify"
|
|
conf.Server.EnableExternalServices = true
|
|
|
|
queueRepo = &orderTrackingQueueRepo{MockArtworkQueueRepo: tests.CreateMockArtworkQueueRepo()}
|
|
propRepo = &tests.MockedPropertyRepo{}
|
|
ds = &tests.MockDataStore{MockedArtworkQueue: queueRepo, MockedProperty: propRepo}
|
|
})
|
|
|
|
seedEntities := func() {
|
|
artistRepo := tests.CreateMockArtistRepo()
|
|
artistRepo.SetData(model.Artists{{ID: "ar1"}, {ID: "ar2"}})
|
|
ds.MockedArtist = artistRepo
|
|
|
|
albumRepo := tests.CreateMockAlbumRepo()
|
|
albumRepo.SetData(model.Albums{{ID: "al1"}})
|
|
ds.MockedAlbum = albumRepo
|
|
|
|
playlistRepo := tests.CreateMockPlaylistRepo()
|
|
playlistRepo.SetData(model.Playlists{{ID: "pl1"}})
|
|
ds.MockedPlaylist = playlistRepo
|
|
|
|
radioRepo := tests.CreateMockedRadioRepo()
|
|
radioRepo.All = model.Radios{{ID: "ra1"}}
|
|
ds.MockedRadio = radioRepo
|
|
}
|
|
|
|
Describe("Fingerprint", func() {
|
|
It("changes when a fingerprint-affecting config value changes", func() {
|
|
f1 := Fingerprint()
|
|
conf.Server.CoverArtPriority = "folder, embedded"
|
|
f2 := Fingerprint()
|
|
Expect(f1).NotTo(Equal(f2))
|
|
})
|
|
|
|
It("changes when ArtistImageFolder changes", func() {
|
|
conf.Server.ArtistImageFolder = "/before"
|
|
f1 := Fingerprint()
|
|
conf.Server.ArtistImageFolder = "/after"
|
|
Expect(Fingerprint()).NotTo(Equal(f1))
|
|
})
|
|
|
|
It("changes when EnableM3UExternalAlbumArt is toggled", func() {
|
|
conf.Server.EnableM3UExternalAlbumArt = false
|
|
f1 := Fingerprint()
|
|
conf.Server.EnableM3UExternalAlbumArt = true
|
|
Expect(Fingerprint()).NotTo(Equal(f1))
|
|
})
|
|
})
|
|
|
|
Describe("Backfill", func() {
|
|
It("enqueues nothing and returns false when the stored fingerprint matches", func() {
|
|
seedEntities()
|
|
Expect(propRepo.Put(FingerprintPropertyKey, Fingerprint())).To(Succeed())
|
|
|
|
did, err := Backfill(ctx, ds)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
Expect(did).To(BeFalse())
|
|
|
|
count, err := queueRepo.Count()
|
|
Expect(err).ToNot(HaveOccurred())
|
|
Expect(count).To(BeZero())
|
|
})
|
|
|
|
It("runs the backfill when no fingerprint was ever stored", func() {
|
|
seedEntities()
|
|
|
|
did, err := Backfill(ctx, ds)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
Expect(did).To(BeTrue())
|
|
|
|
count, err := queueRepo.Count()
|
|
Expect(err).ToNot(HaveOccurred())
|
|
Expect(count).To(Equal(int64(5))) // 2 artists + 1 album + 1 playlist + 1 radio
|
|
|
|
stored, err := propRepo.Get(FingerprintPropertyKey)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
Expect(stored).To(Equal(Fingerprint()))
|
|
})
|
|
|
|
It("enqueues a private playlist by resolving it under an admin context", func() {
|
|
ds.MockedUser = adminUserRepo()
|
|
vds := &visibilityPlaylistDS{
|
|
MockDataStore: ds,
|
|
private: model.Playlist{ID: "plPrivate", OwnerID: "admin"},
|
|
tracks: &tests.MockPlaylistTrackRepo{},
|
|
}
|
|
|
|
did, err := Backfill(ctx, vds)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
Expect(did).To(BeTrue())
|
|
Expect(findQueued(queueRepo.MockArtworkQueueRepo, "pl", "plPrivate")).ToNot(BeNil())
|
|
})
|
|
|
|
It("enqueues artists before albums/playlists/radios, all at Backfill priority", func() {
|
|
seedEntities()
|
|
Expect(propRepo.Put(FingerprintPropertyKey, "stale-fingerprint")).To(Succeed())
|
|
|
|
did, err := Backfill(ctx, ds)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
Expect(did).To(BeTrue())
|
|
|
|
Expect(queueRepo.callKinds).ToNot(BeEmpty())
|
|
artistCallIdx := -1
|
|
for i, k := range queueRepo.callKinds {
|
|
if k == "ar" {
|
|
artistCallIdx = i
|
|
break
|
|
}
|
|
}
|
|
Expect(artistCallIdx).To(Equal(0), "artists must be the first Enqueue call")
|
|
for i, k := range queueRepo.callKinds {
|
|
if k != "ar" {
|
|
Expect(i).To(BeNumerically(">", artistCallIdx))
|
|
}
|
|
}
|
|
|
|
for _, it := range queueRepo.Data {
|
|
Expect(it.Priority).To(Equal(model.ArtworkPriorityBackfill))
|
|
Expect(it.ItemKind).To(BeElementOf("ar", "al", "pl", "ra"))
|
|
}
|
|
})
|
|
})
|
|
|
|
Describe("EnqueueStaleAbsentAll", func() {
|
|
var artRepo *tests.MockArtworkRepo
|
|
|
|
BeforeEach(func() {
|
|
artRepo = tests.CreateMockArtworkRepo()
|
|
ds.MockedArtwork = artRepo
|
|
queueRepo.ItemArtworkSource = artRepo
|
|
})
|
|
|
|
It("enqueues only absent entries older than the recheck window, across all kinds", func() {
|
|
old := time.Now().Add(-48 * time.Hour)
|
|
recent := time.Now().Add(-time.Hour)
|
|
|
|
artRepo.ItemData["ar-stale"] = model.ItemArtwork{ItemKind: "ar", ItemID: "ar1", ImageType: model.ImageTypePrimary, Hash: "", AttemptedAt: old}
|
|
artRepo.ItemData["al-stale"] = model.ItemArtwork{ItemKind: "al", ItemID: "al1", ImageType: model.ImageTypePrimary, Hash: "", AttemptedAt: old}
|
|
artRepo.ItemData["pl-stale"] = model.ItemArtwork{ItemKind: "pl", ItemID: "pl1", ImageType: model.ImageTypePrimary, Hash: "", AttemptedAt: old}
|
|
artRepo.ItemData["ra-stale"] = model.ItemArtwork{ItemKind: "ra", ItemID: "ra1", ImageType: model.ImageTypePrimary, Hash: "", AttemptedAt: old}
|
|
// Not stale: too recent.
|
|
artRepo.ItemData["ar-recent"] = model.ItemArtwork{ItemKind: "ar", ItemID: "ar2", ImageType: model.ImageTypePrimary, Hash: "", AttemptedAt: recent}
|
|
// Not absent: has a resolved hash.
|
|
artRepo.ItemData["al-resolved"] = model.ItemArtwork{ItemKind: "al", ItemID: "al2", ImageType: model.ImageTypePrimary, Hash: "somehash", AttemptedAt: old}
|
|
|
|
err := EnqueueStaleAbsentAll(ctx, ds)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
|
|
Expect(queueRepo.Data).To(HaveLen(4))
|
|
for _, it := range queueRepo.Data {
|
|
Expect(it.Priority).To(Equal(model.ArtworkPriorityRecheck))
|
|
}
|
|
Expect(findQueued(queueRepo.MockArtworkQueueRepo, "ar", "ar1")).ToNot(BeNil())
|
|
Expect(findQueued(queueRepo.MockArtworkQueueRepo, "al", "al1")).ToNot(BeNil())
|
|
Expect(findQueued(queueRepo.MockArtworkQueueRepo, "pl", "pl1")).ToNot(BeNil())
|
|
Expect(findQueued(queueRepo.MockArtworkQueueRepo, "ra", "ra1")).ToNot(BeNil())
|
|
Expect(findQueued(queueRepo.MockArtworkQueueRepo, "ar", "ar2")).To(BeNil())
|
|
Expect(findQueued(queueRepo.MockArtworkQueueRepo, "al", "al2")).To(BeNil())
|
|
})
|
|
})
|
|
})
|