mirror of
https://github.com/navidrome/navidrome.git
synced 2026-11-06 02:08:47 +01:00
96 lines
4.1 KiB
Go
96 lines
4.1 KiB
Go
|
|
package agents
|
||
|
|
|
||
|
|
import (
|
||
|
|
"context"
|
||
|
|
|
||
|
|
"github.com/navidrome/navidrome/model"
|
||
|
|
"github.com/navidrome/navidrome/tests"
|
||
|
|
"github.com/navidrome/navidrome/utils/slice"
|
||
|
|
. "github.com/onsi/ginkgo/v2"
|
||
|
|
. "github.com/onsi/gomega"
|
||
|
|
)
|
||
|
|
|
||
|
|
var _ = Describe("localAgent GetSimilarSongsByTrack", func() {
|
||
|
|
var ds *tests.MockDataStore
|
||
|
|
var mfRepo *tests.MockMediaFileRepo
|
||
|
|
var agent *localAgent
|
||
|
|
var ctx context.Context
|
||
|
|
|
||
|
|
BeforeEach(func() {
|
||
|
|
ctx = context.Background()
|
||
|
|
mfRepo = &tests.MockMediaFileRepo{}
|
||
|
|
ds = &tests.MockDataStore{MockedMediaFile: mfRepo}
|
||
|
|
agent = &localAgent{ds: ds}
|
||
|
|
})
|
||
|
|
|
||
|
|
It("excludes the seed track from its own similars", func() {
|
||
|
|
seed := model.MediaFile{ID: "seed-1", Title: "Seed", Tags: model.Tags{model.TagGenre: []string{"Rock"}}}
|
||
|
|
related := model.MediaFile{ID: "rel-1", Title: "Related", Tags: model.Tags{model.TagGenre: []string{"Rock"}}}
|
||
|
|
// SetData keys by ID; a duplicate "seed-1" entry would clobber the real seed.
|
||
|
|
mfRepo.SetData(model.MediaFiles{seed, related})
|
||
|
|
|
||
|
|
songs, err := agent.GetSimilarSongsByTrack(ctx, "seed-1", "Seed", "", "", 10)
|
||
|
|
|
||
|
|
Expect(err).ToNot(HaveOccurred())
|
||
|
|
names := slice.Map(songs, func(s Song) string { return s.Name })
|
||
|
|
Expect(names).ToNot(ContainElement("Seed"))
|
||
|
|
})
|
||
|
|
|
||
|
|
// The mock ignores QueryOptions.Filters, so assert the predicate itself: otherwise this spec
|
||
|
|
// would pass just as well with no genre filter at all.
|
||
|
|
It("queries the indexed genre join for the seed's own genres, skipping missing files", func() {
|
||
|
|
rock := model.NewTag(model.TagGenre, "Rock")
|
||
|
|
seed := model.MediaFile{ID: "seed-4", Title: "Seed", Tags: model.Tags{model.TagGenre: []string{"Rock"}}}
|
||
|
|
mfRepo.SetData(model.MediaFiles{seed})
|
||
|
|
|
||
|
|
_, err := agent.GetSimilarSongsByTrack(ctx, "seed-4", "Seed", "", "", 10)
|
||
|
|
Expect(err).ToNot(HaveOccurred())
|
||
|
|
|
||
|
|
sql, args, sqlErr := mfRepo.Options.Filters.ToSql()
|
||
|
|
Expect(sqlErr).ToNot(HaveOccurred())
|
||
|
|
Expect(sql).To(ContainSubstring("media_file_tags"), "must use the indexed join, not a json_tree scan")
|
||
|
|
Expect(sql).To(ContainSubstring("missing"))
|
||
|
|
Expect(args).To(ContainElement(false), "must exclude missing files, not select them")
|
||
|
|
Expect(args).To(ContainElement(rock.ID), "must filter on the seed's own genre tag id")
|
||
|
|
Expect(args).ToNot(ContainElement(model.NewTag(model.TagGenre, "Jazz").ID))
|
||
|
|
})
|
||
|
|
|
||
|
|
It("returns the library id so the matcher can resolve the song", func() {
|
||
|
|
seed := model.MediaFile{ID: "seed-3", Title: "Seed", Tags: model.Tags{model.TagGenre: []string{"Rock"}}}
|
||
|
|
// Without the id the matcher falls through to its MBID/title phases and resolves nothing,
|
||
|
|
// so the local fallback silently returns an empty mix.
|
||
|
|
related := model.MediaFile{ID: "rel-3", Title: "Related", Tags: model.Tags{model.TagGenre: []string{"Rock"}}}
|
||
|
|
mfRepo.SetData(model.MediaFiles{seed, related})
|
||
|
|
|
||
|
|
songs, err := agent.GetSimilarSongsByTrack(ctx, "seed-3", "Seed", "", "", 10)
|
||
|
|
|
||
|
|
Expect(err).ToNot(HaveOccurred())
|
||
|
|
Expect(songs).To(ContainElement(Song{ID: "rel-3", Name: "Related"}))
|
||
|
|
})
|
||
|
|
|
||
|
|
It("asks for one extra candidate so dropping the seed still fills the count", func() {
|
||
|
|
// The mock returns rows sorted by id, so the seed comes first and would consume the only
|
||
|
|
// slot if the query did not over-fetch.
|
||
|
|
seed := model.MediaFile{ID: "a-seed", Title: "Seed", Tags: model.Tags{model.TagGenre: []string{"Rock"}}}
|
||
|
|
related := model.MediaFile{ID: "b-rel", Title: "Related", Tags: model.Tags{model.TagGenre: []string{"Rock"}}}
|
||
|
|
mfRepo.SetData(model.MediaFiles{seed, related})
|
||
|
|
|
||
|
|
songs, err := agent.GetSimilarSongsByTrack(ctx, "a-seed", "Seed", "", "", 1)
|
||
|
|
|
||
|
|
Expect(err).ToNot(HaveOccurred())
|
||
|
|
Expect(songs).To(HaveLen(1))
|
||
|
|
Expect(songs[0].Name).To(Equal("Related"))
|
||
|
|
})
|
||
|
|
|
||
|
|
It("returns nil when the seed track has no genres", func() {
|
||
|
|
seed := model.MediaFile{ID: "seed-2", Title: "NoGenre"}
|
||
|
|
mfRepo.SetData(model.MediaFiles{seed})
|
||
|
|
|
||
|
|
songs, err := agent.GetSimilarSongsByTrack(ctx, "seed-2", "NoGenre", "", "", 10)
|
||
|
|
|
||
|
|
Expect(err).ToNot(HaveOccurred())
|
||
|
|
Expect(songs).To(BeEmpty())
|
||
|
|
// Without the early return an empty tag filter would scan the whole library.
|
||
|
|
Expect(mfRepo.Options).To(Equal(model.QueryOptions{}), "must not query at all")
|
||
|
|
})
|
||
|
|
})
|