🏡 index : quine-global/quintodrome.git

package e2e

import (
	"fmt"

	"github.com/navidrome/navidrome/server/jellyfin/dto"
	. "github.com/onsi/ginkgo/v2"
	. "github.com/onsi/gomega"
)

// Search with a ParentId library scope is how Finamp drives its search screen. Artists are the
// tricky case: they have no library_id column, so the repo's Search does its own scope handling.
var _ = Describe("Search", func() {
	BeforeEach(func() { setupTestDB() })

	lib1 := func() string { return dto.EncodeLibraryID(1) }

	Describe("artists", func() {
		It("searches all album artists", func() {
			q := queryResult(get("/Artists/AlbumArtists?SearchTerm=Beatles"))
			Expect(names(q.Items)).To(ConsistOf("The Beatles"))
		})

		It("searches album artists scoped to a library (ParentId)", func() {
			q := queryResult(get("/Artists/AlbumArtists?ParentId=" + lib1() + "&SearchTerm=Beatles&Recursive=true&SortBy=SortName"))
			Expect(names(q.Items)).To(ConsistOf("The Beatles"))
		})

		It("returns an empty result for a non-matching term", func() {
			q := queryResult(get("/Artists?ParentId=" + lib1() + "&SearchTerm=nonexistentxyz"))
			Expect(q.Items).To(BeEmpty())
		})
	})

	Describe("albums and songs", func() {
		It("searches albums scoped to a library", func() {
			q := queryResult(get("/Items?IncludeItemTypes=MusicAlbum&Recursive=true&ParentId=" + lib1() + "&SearchTerm=Abbey"))
			Expect(names(q.Items)).To(ContainElement("Abbey Road"))
		})

		It("searches songs scoped to a library", func() {
			q := queryResult(get("/Items?IncludeItemTypes=Audio&Recursive=true&ParentId=" + lib1() + "&SearchTerm=Stairway"))
			Expect(names(q.Items)).To(ContainElement("Stairway To Heaven"))
		})
	})

	Describe("pagination totals", func() {
		It("reports the search match count, not the unfiltered library count", func() {
			q := queryResult(get("/Items?IncludeItemTypes=MusicAlbum&Recursive=true&SearchTerm=Abbey&Limit=50"))
			Expect(q.Items).To(HaveLen(1))
			Expect(q.TotalRecordCount).To(Equal(1)) // not the 5-album library total
		})

		It("reaches the true total when paging song search results", func() {
			// "So" prefix-matches several songs (titles and Solo Artist's tracks); learn the true
			// count from an unpaged query, then walk one-item pages: the reported total must keep
			// the client paging until the last match and stop it exactly there.
			all := queryResult(get("/Items?IncludeItemTypes=Audio&Recursive=true&SearchTerm=So"))
			total := all.TotalRecordCount
			Expect(total).To(Equal(len(all.Items)))
			Expect(total).To(BeNumerically(">=", 2))

			var collected []string
			for start := range total {
				page := queryResult(get(fmt.Sprintf("/Items?IncludeItemTypes=Audio&Recursive=true&SearchTerm=So&Limit=1&StartIndex=%d", start)))
				Expect(page.Items).To(HaveLen(1))
				if start+1 < total {
					Expect(page.TotalRecordCount).To(BeNumerically(">", start+1)) // more remain: keep paging
				} else {
					Expect(page.TotalRecordCount).To(Equal(total)) // last page: exact, so the client stops
				}
				collected = append(collected, page.Items[0].Name)
			}
			Expect(collected).To(ConsistOf(names(all.Items)))
		})
	})
})