mirror of
https://github.com/navidrome/navidrome.git
synced 2026-07-09 17:18:45 +00:00
Flip the EnableSharing default from false to true so new installations have the sharing feature available out of the box. Users can still disable it via the EnableSharing config option. The native API only registers the /share route when sharing is enabled, so the nativeapi tests that build the router without wiring a share service now explicitly disable sharing in their setup to avoid registering a route backed by a nil service.
239 lines
7.1 KiB
Go
239 lines
7.1 KiB
Go
package nativeapi
|
|
|
|
import (
|
|
"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 = enableArtworkUpload
|
|
handler := 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 = enableArtworkUpload
|
|
handler := 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.Handler
|
|
plsSvc *mockPlaylistsService
|
|
userRepo *tests.MockedUserRepo
|
|
w *httptest.ResponseRecorder
|
|
)
|
|
|
|
BeforeEach(func() {
|
|
DeferCleanup(configtest.SetupConfig())
|
|
conf.Server.EnableSharing = false
|
|
conf.Server.SessionTimeout = time.Minute
|
|
|
|
plsSvc = &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.PlaylistTrack
|
|
err := 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.PlaylistTrack
|
|
err := 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.PlaylistTrackRepository
|
|
tracks 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.Playlists
|
|
tracksRepo rest.Repository
|
|
removeImageFn func(ctx context.Context, id string) error
|
|
setImageFn 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
|
|
}
|