navidrome/server/jellyfin/users_test.go
Deluan fbce1d3500 feat(jellyfin): full multi-library support with per-user access scoping
Replace the single hardcoded "music" UserView with one CollectionFolder
view per library the user can access, and scope every /Items browse
query (albums, songs, artists, /Latest) to those libraries via
server/filter's ApplyLibraryFilter/ApplyArtistLibraryFilter.

ParentId is now disambiguated: a numeric value the user has access to is
treated as a library scope (browsing a UserView), otherwise it falls
through as an entity id (artist/album), which safely matches nothing
rather than leaking another library's content. getItem now 404s when
fetching an album or song outside the user's accessible libraries;
artists are skipped (they can span multiple libraries) with a TODO.

Genres remain unscoped since they're global tags, not per-library
entities.
2026-07-05 15:53:02 -04:00

75 lines
2.6 KiB
Go

package jellyfin
import (
"context"
"encoding/json"
"net/http"
"net/http/httptest"
"github.com/navidrome/navidrome/model"
"github.com/navidrome/navidrome/model/request"
"github.com/navidrome/navidrome/server/jellyfin/dto"
"github.com/navidrome/navidrome/tests"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)
var _ = Describe("Users", func() {
var api *Router
authedWithLibraries := func(r *http.Request, libs model.Libraries) *http.Request {
ctx := request.WithUser(context.Background(), model.User{ID: "u1", UserName: "alice", Libraries: libs})
return r.WithContext(ctx)
}
BeforeEach(func() { api = &Router{ds: &tests.MockDataStore{}} })
Describe("getUserViews", func() {
It("returns one view per accessible library", func() {
libs := model.Libraries{{ID: 1, Name: "Music"}, {ID: 2, Name: "Podcasts"}}
w := httptest.NewRecorder()
api.getUserViews(w, authedWithLibraries(httptest.NewRequest("GET", "/UserViews", nil), libs))
Expect(w.Code).To(Equal(http.StatusOK))
var res dto.QueryResult
Expect(json.Unmarshal(w.Body.Bytes(), &res)).To(Succeed())
Expect(res.Items).To(HaveLen(2))
Expect(res.TotalRecordCount).To(Equal(2))
Expect(res.Items[0].Id).To(Equal("1"))
Expect(res.Items[0].Name).To(Equal("Music"))
Expect(res.Items[0].Type).To(Equal("CollectionFolder"))
Expect(res.Items[0].CollectionType).To(Equal("music"))
Expect(res.Items[0].IsFolder).To(BeTrue())
Expect(res.Items[1].Id).To(Equal("2"))
Expect(res.Items[1].Name).To(Equal("Podcasts"))
})
It("returns a single view for a user with one library", func() {
libs := model.Libraries{{ID: 1, Name: "Music"}}
w := httptest.NewRecorder()
api.getUserViews(w, authedWithLibraries(httptest.NewRequest("GET", "/UserViews", nil), libs))
Expect(w.Code).To(Equal(http.StatusOK))
var res dto.QueryResult
Expect(json.Unmarshal(w.Body.Bytes(), &res)).To(Succeed())
Expect(res.Items).To(HaveLen(1))
Expect(res.Items[0].Id).To(Equal("1"))
})
It("returns no views for a user with no library access", func() {
w := httptest.NewRecorder()
api.getUserViews(w, authedWithLibraries(httptest.NewRequest("GET", "/UserViews", nil), nil))
Expect(w.Code).To(Equal(http.StatusOK))
var res dto.QueryResult
Expect(json.Unmarshal(w.Body.Bytes(), &res)).To(Succeed())
Expect(res.Items).To(HaveLen(0))
Expect(res.TotalRecordCount).To(Equal(0))
})
})
It("returns the current user", func() {
w := httptest.NewRecorder()
api.getCurrentUser(w, authedWithLibraries(httptest.NewRequest("GET", "/Users/Me", nil), nil))
var u dto.UserDto
Expect(json.Unmarshal(w.Body.Bytes(), &u)).To(Succeed())
Expect(u.Name).To(Equal("alice"))
})
})