mirror of
https://github.com/navidrome/navidrome.git
synced 2026-11-12 21:28:37 +01:00
* feat(matcher): add Song.Artists (agents.Artist) and field-wise song dedup
* refactor(matcher): make song equality an agents.Song.Equals method via hashstructure
Move the sameSong free function from core/matcher into an Equals method on
agents.Song, following the model.MediaFile/Album.Equals convention. Uses strict
hashstructure hashing (nil opts, no IgnoreZeroValue) to preserve the original
whole-value equality contract. Tests moved to core/agents.
* feat(matcher): match by multiple artists with overlap ranking and artist-ID fast-path
* refactor(matcher): rank artist overlap and specificity above the preferred-track flag
Identity signals (specificityLevel, artistOverlap) now outrank the taste
signal (preferredMatch) in betterThan. A starred/4-star track that is a worse
identity match no longer beats a more specific or higher-overlap track.
PreferStarred still breaks ties when specificity and overlap are equal.
* fix(matcher): score artist-MBID specificity against all credited artists, not just the last
sanitizedTrack.artistMBID (string) replaced with artistMBIDs (map[string]struct{}) so
bucketTracks collects all credited owned MBIDs per query instead of last-write-wins.
computeSpecificityLevel tests set membership, letting each of a collaboration's
MBID-bearing artists reach the proper specificity level (4/5) independently.
* feat(plugins): carry multiple artists (with IDs) through SongRef conversions
* feat(plugins): regenerate schemas and PDK wrappers for multi-artist SongRef
* refactor(matcher): tidy bucketTracks accumulator and artist resolution
Replace bucketTracks' two parallel per-track maps (overlapByQuery/mbidsByQuery)
with a named queryAccum struct (F2). Collapse resolveArtists' four hand-mutated
parallel maps into a pendingArtist slice with derived nameToQueries/mbidToQueries
maps (F1). Replace the own() method on resolvedArtists with a package-level
addToSet helper that drops the method/receiver indirection (F3).
* docs(matcher): trim comments that restate the code
* fix(plugins): use Vec::is_empty for slice fields in generated Rust PDK
* fix(matcher): treat a resolved artist ID as an identity match for specificity
* docs(matcher): reflect artist-ID identity in the specificity ladder
47 lines
1.5 KiB
Go
47 lines
1.5 KiB
Go
package agents
|
|
|
|
import (
|
|
. "github.com/onsi/ginkgo/v2"
|
|
. "github.com/onsi/gomega"
|
|
)
|
|
|
|
var _ = Describe("Song.Equals", func() {
|
|
base := Song{ID: "1", Name: "S", Artist: "A", Artists: []Artist{{ID: "x", Name: "A"}}}
|
|
It("true for identical songs incl Artists", func() {
|
|
Expect(base.Equals(base)).To(BeTrue())
|
|
})
|
|
It("false when Artists differ", func() {
|
|
other := base
|
|
other.Artists = []Artist{{ID: "y", Name: "B"}}
|
|
Expect(base.Equals(other)).To(BeFalse())
|
|
})
|
|
It("false when a scalar differs", func() {
|
|
other := base
|
|
other.Name = "T"
|
|
Expect(base.Equals(other)).To(BeFalse())
|
|
})
|
|
It("true when both have empty Artists and equal scalars", func() {
|
|
a := Song{ID: "1", Name: "S", Artist: "A"}
|
|
Expect(a.Equals(a)).To(BeTrue())
|
|
})
|
|
})
|
|
|
|
var _ = Describe("Song.ArtistList", func() {
|
|
It("returns the Artists slice when present", func() {
|
|
s := Song{Artist: "Primary", ArtistMBID: "mbid-primary", Artists: []Artist{
|
|
{ID: "id-drake", Name: "Drake", MBID: "mbid-drake"},
|
|
{Name: "Future", MBID: "mbid-future"},
|
|
}}
|
|
Expect(s.ArtistList()).To(Equal([]Artist{
|
|
{ID: "id-drake", Name: "Drake", MBID: "mbid-drake"},
|
|
{Name: "Future", MBID: "mbid-future"},
|
|
}))
|
|
})
|
|
It("falls back to the single Artist field with empty ID", func() {
|
|
s := Song{Artist: "Drake", ArtistMBID: "mbid-drake"}
|
|
Expect(s.ArtistList()).To(Equal([]Artist{{Name: "Drake", MBID: "mbid-drake"}}))
|
|
})
|
|
It("returns empty when no artist is set", func() {
|
|
Expect(Song{}.ArtistList()).To(BeEmpty())
|
|
})
|
|
})
|