mirror of
https://github.com/navidrome/navidrome.git
synced 2026-08-01 07:21:17 +00:00
Merge branch 'master' into album-rating-in-smart-playlists
This commit is contained in:
commit
266e328731
@ -134,7 +134,7 @@ func isDottedAbbreviation(w string, subTokens []string) bool {
|
||||
// special characters to prevent query injection.
|
||||
func buildFTS5Query(userInput string) string {
|
||||
q := strings.TrimSpace(userInput)
|
||||
if q == "" {
|
||||
if q == "" || q == `""` {
|
||||
return ""
|
||||
}
|
||||
|
||||
@ -239,7 +239,7 @@ var ftsSearchColumns = map[string]string{
|
||||
func ftsSearchExpr(tableName string, s string) Sqlizer {
|
||||
q := buildFTS5Query(s)
|
||||
if q == "" {
|
||||
s = strings.TrimSpace(s)
|
||||
s = strings.TrimSpace(strings.ReplaceAll(s, `"`, ""))
|
||||
if s != "" {
|
||||
log.Trace("Search using LIKE fallback for non-tokenizable query", "table", tableName, "query", s)
|
||||
return likeSearchExpr(tableName, s)
|
||||
|
||||
@ -49,6 +49,7 @@ var _ = DescribeTable("buildFTS5Query",
|
||||
Entry("preserves quoted abbreviation verbatim", `"R.E.M."`, `"R.E.M."`),
|
||||
Entry("returns empty string for punctuation-only input", "!!!!!!!", ""),
|
||||
Entry("returns empty string for mixed punctuation", "!@#$%^&", ""),
|
||||
Entry("returns empty string for empty quoted phrase", `""`, ""),
|
||||
)
|
||||
|
||||
var _ = DescribeTable("normalizeForFTS",
|
||||
@ -204,6 +205,10 @@ var _ = Describe("ftsSearchExpr", func() {
|
||||
Expect(ftsSearchExpr("media_file", "")).To(BeNil())
|
||||
Expect(ftsSearchExpr("media_file", " ")).To(BeNil())
|
||||
})
|
||||
|
||||
It("returns nil for empty quoted phrase", func() {
|
||||
Expect(ftsSearchExpr("media_file", `""`)).To(BeNil())
|
||||
})
|
||||
})
|
||||
|
||||
var _ = Describe("FTS5 Integration Search", func() {
|
||||
|
||||
@ -52,6 +52,17 @@ type _t = map[string]any
|
||||
var template = storagetest.Template
|
||||
var track = storagetest.Track
|
||||
|
||||
// MusicBrainz ID constants for test data (valid UUID v4 values)
|
||||
const (
|
||||
mbidBeatlesArtist = "b10bbbfc-cf9e-42e0-be17-e2c3e1d2600d"
|
||||
mbidAbbeyRoadAlbum = "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d"
|
||||
mbidAbbeyRoadRelGroup = "d4c3b2a1-f6e5-4b7a-9d8c-1f0e3a2b5c4d"
|
||||
mbidComeTogether = "11111111-1111-4111-a111-111111111111" // mbz_release_track_id
|
||||
mbidComeTogetherRec = "22222222-2222-4222-a222-222222222222" // mbz_recording_id
|
||||
mbidSomething = "33333333-3333-4333-a333-333333333333" // mbz_release_track_id
|
||||
mbidSomethingRec = "44444444-4444-4444-a444-444444444444" // mbz_recording_id
|
||||
)
|
||||
|
||||
// Shared test state
|
||||
var (
|
||||
ctx context.Context
|
||||
@ -89,23 +100,37 @@ func createFS(files fstest.MapFS) storagetest.FakeFS {
|
||||
|
||||
// buildTestFS creates the full test filesystem matching the plan
|
||||
func buildTestFS() storagetest.FakeFS {
|
||||
abbeyRoad := template(_t{"albumartist": "The Beatles", "artist": "The Beatles", "album": "Abbey Road", "year": 1969, "genre": "Rock"})
|
||||
abbeyRoad := template(_t{
|
||||
"albumartist": "The Beatles",
|
||||
"artist": "The Beatles",
|
||||
"album": "Abbey Road",
|
||||
"year": 1969,
|
||||
"genre": "Rock",
|
||||
"musicbrainz_artistid": mbidBeatlesArtist,
|
||||
"musicbrainz_albumartistid": mbidBeatlesArtist,
|
||||
"musicbrainz_albumid": mbidAbbeyRoadAlbum,
|
||||
"musicbrainz_releasegroupid": mbidAbbeyRoadRelGroup,
|
||||
})
|
||||
help := template(_t{"albumartist": "The Beatles", "artist": "The Beatles", "album": "Help!", "year": 1965, "genre": "Rock"})
|
||||
ledZepIV := template(_t{"albumartist": "Led Zeppelin", "artist": "Led Zeppelin", "album": "IV", "year": 1971, "genre": "Rock"})
|
||||
kindOfBlue := template(_t{"albumartist": "Miles Davis", "artist": "Miles Davis", "album": "Kind of Blue", "year": 1959, "genre": "Jazz"})
|
||||
popTrack := template(_t{"albumartist": "Various", "artist": "Various", "album": "Pop", "year": 2020, "genre": "Pop"})
|
||||
|
||||
return createFS(fstest.MapFS{
|
||||
// Rock / The Beatles / Abbey Road
|
||||
"Rock/The Beatles/Abbey Road/01 - Come Together.mp3": abbeyRoad(track(1, "Come Together")),
|
||||
"Rock/The Beatles/Abbey Road/02 - Something.mp3": abbeyRoad(track(2, "Something")),
|
||||
// Rock / The Beatles / Help!
|
||||
// Rock / The Beatles / Abbey Road (with MBIDs)
|
||||
// Note: "musicbrainz_trackid" is an alias for the musicbrainz_recordingid tag (populates MbzRecordingID),
|
||||
// "musicbrainz_releasetrackid" is an alias for the musicbrainz_trackid tag (populates MbzReleaseTrackID).
|
||||
"Rock/The Beatles/Abbey Road/01 - Come Together.mp3": abbeyRoad(track(1, "Come Together",
|
||||
_t{"musicbrainz_releasetrackid": mbidComeTogether, "musicbrainz_trackid": mbidComeTogetherRec})),
|
||||
"Rock/The Beatles/Abbey Road/02 - Something.mp3": abbeyRoad(track(2, "Something",
|
||||
_t{"musicbrainz_releasetrackid": mbidSomething, "musicbrainz_trackid": mbidSomethingRec})),
|
||||
// Rock / The Beatles / Help! (no MBIDs)
|
||||
"Rock/The Beatles/Help!/01 - Help.mp3": help(track(1, "Help!")),
|
||||
// Rock / Led Zeppelin / IV
|
||||
// Rock / Led Zeppelin / IV (no MBIDs)
|
||||
"Rock/Led Zeppelin/IV/01 - Stairway To Heaven.mp3": ledZepIV(track(1, "Stairway To Heaven")),
|
||||
// Jazz / Miles Davis / Kind of Blue
|
||||
// Jazz / Miles Davis / Kind of Blue (no MBIDs)
|
||||
"Jazz/Miles Davis/Kind of Blue/01 - So What.mp3": kindOfBlue(track(1, "So What")),
|
||||
// Pop (standalone track)
|
||||
// Pop (standalone track, no MBIDs)
|
||||
"Pop/01 - Standalone Track.mp3": popTrack(track(1, "Standalone Track")),
|
||||
// _empty folder (directory with no audio)
|
||||
"_empty/.keep": &fstest.MapFile{Data: []byte{}, ModTime: time.Now()},
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
package e2e
|
||||
|
||||
import (
|
||||
"github.com/google/uuid"
|
||||
"github.com/navidrome/navidrome/server/subsonic/responses"
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
@ -107,6 +108,16 @@ var _ = Describe("Search Endpoints", func() {
|
||||
Expect(resp.SearchResult3.Artist[0].Id).ToNot(BeEmpty())
|
||||
})
|
||||
|
||||
It("returns all results when query is empty (OpenSubsonic)", func() {
|
||||
resp := doReq("search3", "query", "")
|
||||
|
||||
Expect(resp.Status).To(Equal(responses.StatusOK))
|
||||
Expect(resp.SearchResult3).ToNot(BeNil())
|
||||
Expect(resp.SearchResult3.Artist).To(HaveLen(4))
|
||||
Expect(resp.SearchResult3.Album).To(HaveLen(5))
|
||||
Expect(resp.SearchResult3.Song).To(HaveLen(6))
|
||||
})
|
||||
|
||||
It("finds across all entity types simultaneously", func() {
|
||||
// "Beatles" should match artist, albums, and songs by The Beatles
|
||||
resp := doReq("search3", "query", "Beatles")
|
||||
@ -136,5 +147,75 @@ var _ = Describe("Search Endpoints", func() {
|
||||
Expect(s.Title).ToNot(BeEmpty())
|
||||
}
|
||||
})
|
||||
|
||||
Describe("MBID search", func() {
|
||||
It("finds songs by mbz_recording_id", func() {
|
||||
resp := doReq("search3", "query", mbidComeTogetherRec)
|
||||
|
||||
Expect(resp.Status).To(Equal(responses.StatusOK))
|
||||
Expect(resp.SearchResult3).ToNot(BeNil())
|
||||
Expect(resp.SearchResult3.Song).To(HaveLen(1))
|
||||
Expect(resp.SearchResult3.Song[0].Title).To(Equal("Come Together"))
|
||||
})
|
||||
|
||||
It("finds songs by mbz_release_track_id", func() {
|
||||
resp := doReq("search3", "query", mbidSomething)
|
||||
|
||||
Expect(resp.Status).To(Equal(responses.StatusOK))
|
||||
Expect(resp.SearchResult3).ToNot(BeNil())
|
||||
Expect(resp.SearchResult3.Song).To(HaveLen(1))
|
||||
Expect(resp.SearchResult3.Song[0].Title).To(Equal("Something"))
|
||||
})
|
||||
|
||||
It("finds albums by mbz_album_id", func() {
|
||||
resp := doReq("search3", "query", mbidAbbeyRoadAlbum)
|
||||
|
||||
Expect(resp.Status).To(Equal(responses.StatusOK))
|
||||
Expect(resp.SearchResult3).ToNot(BeNil())
|
||||
Expect(resp.SearchResult3.Album).To(HaveLen(1))
|
||||
Expect(resp.SearchResult3.Album[0].Name).To(Equal("Abbey Road"))
|
||||
})
|
||||
|
||||
It("finds albums by mbz_release_group_id", func() {
|
||||
resp := doReq("search3", "query", mbidAbbeyRoadRelGroup)
|
||||
|
||||
Expect(resp.Status).To(Equal(responses.StatusOK))
|
||||
Expect(resp.SearchResult3).ToNot(BeNil())
|
||||
Expect(resp.SearchResult3.Album).To(HaveLen(1))
|
||||
Expect(resp.SearchResult3.Album[0].Name).To(Equal("Abbey Road"))
|
||||
})
|
||||
|
||||
It("finds artists by mbz_artist_id", func() {
|
||||
resp := doReq("search3", "query", mbidBeatlesArtist)
|
||||
|
||||
Expect(resp.Status).To(Equal(responses.StatusOK))
|
||||
Expect(resp.SearchResult3).ToNot(BeNil())
|
||||
Expect(resp.SearchResult3.Artist).To(HaveLen(1))
|
||||
Expect(resp.SearchResult3.Artist[0].Name).To(Equal("The Beatles"))
|
||||
})
|
||||
|
||||
It("returns empty results for non-matching UUID", func() {
|
||||
nonMatchingUUID := uuid.NewString()
|
||||
resp := doReq("search3", "query", nonMatchingUUID)
|
||||
|
||||
Expect(resp.Status).To(Equal(responses.StatusOK))
|
||||
Expect(resp.SearchResult3).ToNot(BeNil())
|
||||
Expect(resp.SearchResult3.Artist).To(BeEmpty())
|
||||
Expect(resp.SearchResult3.Album).To(BeEmpty())
|
||||
Expect(resp.SearchResult3.Song).To(BeEmpty())
|
||||
})
|
||||
|
||||
It("does not return songs for artist MBID", func() {
|
||||
// media_file MBID search only checks mbz_recording_id and mbz_release_track_id,
|
||||
// so an artist MBID should return only the artist, not songs
|
||||
resp := doReq("search3", "query", mbidBeatlesArtist)
|
||||
|
||||
Expect(resp.Status).To(Equal(responses.StatusOK))
|
||||
Expect(resp.SearchResult3).ToNot(BeNil())
|
||||
Expect(resp.SearchResult3.Artist).To(HaveLen(1))
|
||||
Expect(resp.SearchResult3.Artist[0].Name).To(Equal("The Beatles"))
|
||||
Expect(resp.SearchResult3.Song).To(BeEmpty())
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user