package nativeapiimport ("context""encoding/json""io""net/http""net/http/httptest""time""github.com/deluan/rest""github.com/navidrome/navidrome/conf""github.com/navidrome/navidrome/conf/configtest""github.com/navidrome/navidrome/consts""github.com/navidrome/navidrome/core/auth""github.com/navidrome/navidrome/core/playlists""github.com/navidrome/navidrome/model""github.com/navidrome/navidrome/model/request""github.com/navidrome/navidrome/server""github.com/navidrome/navidrome/tests". "github.com/onsi/ginkgo/v2". "github.com/onsi/gomega")var _ = Describe("Playlist Image Endpoints", func() {BeforeEach(func() {DeferCleanup(configtest.SetupConfig())})DescribeTable("uploadPlaylistImage guard",func(enableArtworkUpload, isAdmin bool, expectedStatus int) {conf.Server.EnableArtworkUpload = enableArtworkUploadhandler := uploadPlaylistImage(&mockPlaylistsService{})req := httptest.NewRequest("POST", "/playlist/pls-1/image", nil)ctx := request.WithUser(GinkgoT().Context(), model.User{ID: "user-1", IsAdmin: isAdmin})req = req.WithContext(ctx)w := httptest.NewRecorder()handler.ServeHTTP(w, req)Expect(w.Code).To(Equal(expectedStatus))},Entry("enabled, regular user passes guard", true, false, http.StatusBadRequest),Entry("enabled, admin passes guard", true, true, http.StatusBadRequest),Entry("disabled, admin passes guard", false, true, http.StatusBadRequest),Entry("disabled, regular user is forbidden", false, false, http.StatusForbidden),)DescribeTable("deletePlaylistImage guard",func(enableArtworkUpload, isAdmin bool, expectedStatus int) {conf.Server.EnableArtworkUpload = enableArtworkUploadhandler := deletePlaylistImage(&mockPlaylistsService{})req := httptest.NewRequest("DELETE", "/playlist/pls-1/image", nil)ctx := request.WithUser(GinkgoT().Context(), model.User{ID: "user-1", IsAdmin: isAdmin})req = req.WithContext(ctx)w := httptest.NewRecorder()handler.ServeHTTP(w, req)Expect(w.Code).To(Equal(expectedStatus))},Entry("enabled, regular user passes guard", true, false, http.StatusNotFound),Entry("enabled, admin passes guard", true, true, http.StatusNotFound),Entry("disabled, admin passes guard", false, true, http.StatusNotFound),Entry("disabled, regular user is forbidden", false, false, http.StatusForbidden),)})var _ = Describe("Playlist Tracks Endpoint", func() {var (router http.HandlerplsSvc *mockPlaylistsServiceuserRepo *tests.MockedUserRepow *httptest.ResponseRecorder)BeforeEach(func() {DeferCleanup(configtest.SetupConfig())conf.Server.SessionTimeout = time.MinuteplsSvc = &mockPlaylistsService{}userRepo = tests.CreateMockUserRepo()ds := &tests.MockDataStore{MockedUser: userRepo,MockedProperty: &tests.MockedPropertyRepo{},}auth.Init(ds)testUser := model.User{ID: "user-1",UserName: "testuser",Name: "Test User",IsAdmin: false,NewPassword: "testpass",}err := userRepo.Put(&testUser)Expect(err).ToNot(HaveOccurred())nativeRouter := New(ds, nil, plsSvc, nil, tests.NewMockLibraryService(), tests.NewMockUserService(), nil, nil, nil)router = server.JWTVerifier(nativeRouter)w = httptest.NewRecorder()})createAuthenticatedRequest := func(method, path string) *http.Request {req := httptest.NewRequest(method, path, nil)testUser := model.User{ID: "user-1", UserName: "testuser"}token, err := auth.CreateToken(&testUser)Expect(err).ToNot(HaveOccurred())req.Header.Set(consts.UIAuthorizationHeader, "Bearer "+token)return req}Describe("GET /playlist/{playlistId}/tracks", func() {It("returns 404 when playlist does not exist", func() {req := createAuthenticatedRequest("GET", "/playlist/non-existent/tracks")router.ServeHTTP(w, req)Expect(w.Code).To(Equal(http.StatusNotFound))})It("returns tracks when playlist exists", func() {plsSvc.tracksRepo = &mockPlaylistTrackRepo{tracks: model.PlaylistTracks{{ID: "1", MediaFileID: "mf-1", PlaylistID: "pls-1"},{ID: "2", MediaFileID: "mf-2", PlaylistID: "pls-1"},},}req := createAuthenticatedRequest("GET", "/playlist/pls-1/tracks")router.ServeHTTP(w, req)Expect(w.Code).To(Equal(http.StatusOK))var response []model.PlaylistTrackerr := json.Unmarshal(w.Body.Bytes(), &response)Expect(err).ToNot(HaveOccurred())Expect(response).To(HaveLen(2))Expect(response[0].ID).To(Equal("1"))Expect(response[1].ID).To(Equal("2"))})})Describe("GET /playlist/{playlistId}/tracks/{id}", func() {It("returns 404 when playlist does not exist", func() {req := createAuthenticatedRequest("GET", "/playlist/non-existent/tracks/1")router.ServeHTTP(w, req)Expect(w.Code).To(Equal(http.StatusNotFound))})It("returns the track when playlist exists", func() {plsSvc.tracksRepo = &mockPlaylistTrackRepo{tracks: model.PlaylistTracks{{ID: "1", MediaFileID: "mf-1", PlaylistID: "pls-1"},},}req := createAuthenticatedRequest("GET", "/playlist/pls-1/tracks/1")router.ServeHTTP(w, req)Expect(w.Code).To(Equal(http.StatusOK))var response model.PlaylistTrackerr := json.Unmarshal(w.Body.Bytes(), &response)Expect(err).ToNot(HaveOccurred())Expect(response.ID).To(Equal("1"))Expect(response.MediaFileID).To(Equal("mf-1"))})It("returns 404 when track does not exist in playlist", func() {plsSvc.tracksRepo = &mockPlaylistTrackRepo{tracks: model.PlaylistTracks{},}req := createAuthenticatedRequest("GET", "/playlist/pls-1/tracks/999")router.ServeHTTP(w, req)Expect(w.Code).To(Equal(http.StatusNotFound))})})})type mockPlaylistTrackRepo struct {model.PlaylistTrackRepositorytracks model.PlaylistTracks}func (m *mockPlaylistTrackRepo) Count(...rest.QueryOptions) (int64, error) {return int64(len(m.tracks)), nil}func (m *mockPlaylistTrackRepo) ReadAll(...rest.QueryOptions) (any, error) {return m.tracks, nil}func (m *mockPlaylistTrackRepo) EntityName() string {return "playlist_track"}func (m *mockPlaylistTrackRepo) NewInstance() any {return &model.PlaylistTrack{}}func (m *mockPlaylistTrackRepo) Read(id string) (any, error) {for _, t := range m.tracks {if t.ID == id {return &t, nil}}return nil, rest.ErrNotFound}type mockPlaylistsService struct {playlists.PlayliststracksRepo rest.RepositoryremoveImageFn func(ctx context.Context, id string) errorsetImageFn func(ctx context.Context, id string, reader io.Reader, ext string) error}func (m *mockPlaylistsService) RemoveImage(ctx context.Context, id string) error {if m.removeImageFn != nil {return m.removeImageFn(ctx, id)}return model.ErrNotFound}func (m *mockPlaylistsService) SetImage(ctx context.Context, id string, reader io.Reader, ext string) error {if m.setImageFn != nil {return m.setImageFn(ctx, id, reader, ext)}return model.ErrNotFound}func (m *mockPlaylistsService) TracksRepository(_ context.Context, _ string, _ bool) rest.Repository {return m.tracksRepo}