mirror of
https://github.com/navidrome/navidrome.git
synced 2025-07-14 15:41:18 +03:00
* Refactor queue payload handling * Refine queue update validation * refactor(queue): avoid loading tracks for validation * refactor/rename repository methods Signed-off-by: Deluan <deluan@navidrome.org> * more tests Signed-off-by: Deluan <deluan@navidrome.org> * refactor Signed-off-by: Deluan <deluan@navidrome.org> --------- Signed-off-by: Deluan <deluan@navidrome.org>
283 lines
9.9 KiB
Go
283 lines
9.9 KiB
Go
package nativeapi
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
|
|
"github.com/navidrome/navidrome/model"
|
|
"github.com/navidrome/navidrome/model/request"
|
|
"github.com/navidrome/navidrome/tests"
|
|
"github.com/navidrome/navidrome/utils/gg"
|
|
. "github.com/onsi/ginkgo/v2"
|
|
. "github.com/onsi/gomega"
|
|
)
|
|
|
|
var _ = Describe("Queue Endpoints", func() {
|
|
var (
|
|
ds *tests.MockDataStore
|
|
repo *tests.MockPlayQueueRepo
|
|
user model.User
|
|
userRepo *tests.MockedUserRepo
|
|
)
|
|
|
|
BeforeEach(func() {
|
|
repo = &tests.MockPlayQueueRepo{}
|
|
user = model.User{ID: "u1", UserName: "user"}
|
|
userRepo = tests.CreateMockUserRepo()
|
|
_ = userRepo.Put(&user)
|
|
ds = &tests.MockDataStore{MockedPlayQueue: repo, MockedUser: userRepo, MockedProperty: &tests.MockedPropertyRepo{}}
|
|
})
|
|
|
|
Describe("POST /queue", func() {
|
|
It("saves the queue", func() {
|
|
payload := updateQueuePayload{Ids: gg.P([]string{"s1", "s2"}), Current: gg.P(1), Position: gg.P(int64(10))}
|
|
body, _ := json.Marshal(payload)
|
|
req := httptest.NewRequest("POST", "/queue", bytes.NewReader(body))
|
|
ctx := request.WithUser(req.Context(), user)
|
|
ctx = request.WithClient(ctx, "TestClient")
|
|
req = req.WithContext(ctx)
|
|
w := httptest.NewRecorder()
|
|
|
|
saveQueue(ds)(w, req)
|
|
Expect(w.Code).To(Equal(http.StatusNoContent))
|
|
Expect(repo.Queue).ToNot(BeNil())
|
|
Expect(repo.Queue.Current).To(Equal(1))
|
|
Expect(repo.Queue.Items).To(HaveLen(2))
|
|
Expect(repo.Queue.Items[1].ID).To(Equal("s2"))
|
|
Expect(repo.Queue.ChangedBy).To(Equal("TestClient"))
|
|
})
|
|
|
|
It("saves an empty queue", func() {
|
|
payload := updateQueuePayload{Ids: gg.P([]string{}), Current: gg.P(0), Position: gg.P(int64(0))}
|
|
body, _ := json.Marshal(payload)
|
|
req := httptest.NewRequest("POST", "/queue", bytes.NewReader(body))
|
|
req = req.WithContext(request.WithUser(req.Context(), user))
|
|
w := httptest.NewRecorder()
|
|
|
|
saveQueue(ds)(w, req)
|
|
Expect(w.Code).To(Equal(http.StatusNoContent))
|
|
Expect(repo.Queue).ToNot(BeNil())
|
|
Expect(repo.Queue.Items).To(HaveLen(0))
|
|
})
|
|
|
|
It("returns bad request for invalid current index (negative)", func() {
|
|
payload := updateQueuePayload{Ids: gg.P([]string{"s1", "s2"}), Current: gg.P(-1), Position: gg.P(int64(10))}
|
|
body, _ := json.Marshal(payload)
|
|
req := httptest.NewRequest("POST", "/queue", bytes.NewReader(body))
|
|
req = req.WithContext(request.WithUser(req.Context(), user))
|
|
w := httptest.NewRecorder()
|
|
|
|
saveQueue(ds)(w, req)
|
|
Expect(w.Code).To(Equal(http.StatusBadRequest))
|
|
Expect(w.Body.String()).To(ContainSubstring("current index out of bounds"))
|
|
})
|
|
|
|
It("returns bad request for invalid current index (too large)", func() {
|
|
payload := updateQueuePayload{Ids: gg.P([]string{"s1", "s2"}), Current: gg.P(2), Position: gg.P(int64(10))}
|
|
body, _ := json.Marshal(payload)
|
|
req := httptest.NewRequest("POST", "/queue", bytes.NewReader(body))
|
|
req = req.WithContext(request.WithUser(req.Context(), user))
|
|
w := httptest.NewRecorder()
|
|
|
|
saveQueue(ds)(w, req)
|
|
Expect(w.Code).To(Equal(http.StatusBadRequest))
|
|
Expect(w.Body.String()).To(ContainSubstring("current index out of bounds"))
|
|
})
|
|
|
|
It("returns bad request for malformed JSON", func() {
|
|
req := httptest.NewRequest("POST", "/queue", bytes.NewReader([]byte("invalid json")))
|
|
req = req.WithContext(request.WithUser(req.Context(), user))
|
|
w := httptest.NewRecorder()
|
|
|
|
saveQueue(ds)(w, req)
|
|
Expect(w.Code).To(Equal(http.StatusBadRequest))
|
|
})
|
|
|
|
It("returns internal server error when store fails", func() {
|
|
repo.Err = true
|
|
payload := updateQueuePayload{Ids: gg.P([]string{"s1"}), Current: gg.P(0), Position: gg.P(int64(10))}
|
|
body, _ := json.Marshal(payload)
|
|
req := httptest.NewRequest("POST", "/queue", bytes.NewReader(body))
|
|
req = req.WithContext(request.WithUser(req.Context(), user))
|
|
w := httptest.NewRecorder()
|
|
|
|
saveQueue(ds)(w, req)
|
|
Expect(w.Code).To(Equal(http.StatusInternalServerError))
|
|
})
|
|
})
|
|
|
|
Describe("GET /queue", func() {
|
|
It("returns the queue", func() {
|
|
queue := &model.PlayQueue{
|
|
UserID: user.ID,
|
|
Current: 1,
|
|
Position: 55,
|
|
Items: model.MediaFiles{
|
|
{ID: "track1", Title: "Song 1"},
|
|
{ID: "track2", Title: "Song 2"},
|
|
{ID: "track3", Title: "Song 3"},
|
|
},
|
|
}
|
|
repo.Queue = queue
|
|
req := httptest.NewRequest("GET", "/queue", nil)
|
|
req = req.WithContext(request.WithUser(req.Context(), user))
|
|
w := httptest.NewRecorder()
|
|
|
|
getQueue(ds)(w, req)
|
|
Expect(w.Code).To(Equal(http.StatusOK))
|
|
Expect(w.Header().Get("Content-Type")).To(Equal("application/json"))
|
|
var resp model.PlayQueue
|
|
Expect(json.Unmarshal(w.Body.Bytes(), &resp)).To(Succeed())
|
|
Expect(resp.Current).To(Equal(1))
|
|
Expect(resp.Position).To(Equal(int64(55)))
|
|
Expect(resp.Items).To(HaveLen(3))
|
|
Expect(resp.Items[0].ID).To(Equal("track1"))
|
|
Expect(resp.Items[1].ID).To(Equal("track2"))
|
|
Expect(resp.Items[2].ID).To(Equal("track3"))
|
|
})
|
|
|
|
It("returns empty queue when user has no queue", func() {
|
|
req := httptest.NewRequest("GET", "/queue", nil)
|
|
req = req.WithContext(request.WithUser(req.Context(), user))
|
|
w := httptest.NewRecorder()
|
|
|
|
getQueue(ds)(w, req)
|
|
Expect(w.Code).To(Equal(http.StatusOK))
|
|
var resp model.PlayQueue
|
|
Expect(json.Unmarshal(w.Body.Bytes(), &resp)).To(Succeed())
|
|
Expect(resp.Items).To(BeEmpty())
|
|
Expect(resp.Current).To(Equal(0))
|
|
Expect(resp.Position).To(Equal(int64(0)))
|
|
})
|
|
|
|
It("returns internal server error when retrieve fails", func() {
|
|
repo.Err = true
|
|
req := httptest.NewRequest("GET", "/queue", nil)
|
|
req = req.WithContext(request.WithUser(req.Context(), user))
|
|
w := httptest.NewRecorder()
|
|
|
|
getQueue(ds)(w, req)
|
|
Expect(w.Code).To(Equal(http.StatusInternalServerError))
|
|
})
|
|
})
|
|
|
|
Describe("PUT /queue", func() {
|
|
It("updates the queue fields", func() {
|
|
repo.Queue = &model.PlayQueue{UserID: user.ID, Items: model.MediaFiles{{ID: "s1"}, {ID: "s2"}, {ID: "s3"}}}
|
|
payload := updateQueuePayload{Current: gg.P(2), Position: gg.P(int64(20))}
|
|
body, _ := json.Marshal(payload)
|
|
req := httptest.NewRequest("PUT", "/queue", bytes.NewReader(body))
|
|
ctx := request.WithUser(req.Context(), user)
|
|
ctx = request.WithClient(ctx, "TestClient")
|
|
req = req.WithContext(ctx)
|
|
w := httptest.NewRecorder()
|
|
|
|
updateQueue(ds)(w, req)
|
|
Expect(w.Code).To(Equal(http.StatusNoContent))
|
|
Expect(repo.Queue).ToNot(BeNil())
|
|
Expect(repo.Queue.Current).To(Equal(2))
|
|
Expect(repo.Queue.Position).To(Equal(int64(20)))
|
|
Expect(repo.Queue.ChangedBy).To(Equal("TestClient"))
|
|
})
|
|
|
|
It("updates only ids", func() {
|
|
repo.Queue = &model.PlayQueue{UserID: user.ID, Current: 1}
|
|
payload := updateQueuePayload{Ids: gg.P([]string{"s1", "s2"})}
|
|
body, _ := json.Marshal(payload)
|
|
req := httptest.NewRequest("PUT", "/queue", bytes.NewReader(body))
|
|
req = req.WithContext(request.WithUser(req.Context(), user))
|
|
w := httptest.NewRecorder()
|
|
|
|
updateQueue(ds)(w, req)
|
|
Expect(w.Code).To(Equal(http.StatusNoContent))
|
|
Expect(repo.Queue.Items).To(HaveLen(2))
|
|
Expect(repo.LastCols).To(ConsistOf("items"))
|
|
})
|
|
|
|
It("updates ids and current", func() {
|
|
repo.Queue = &model.PlayQueue{UserID: user.ID}
|
|
payload := updateQueuePayload{Ids: gg.P([]string{"s1", "s2"}), Current: gg.P(1)}
|
|
body, _ := json.Marshal(payload)
|
|
req := httptest.NewRequest("PUT", "/queue", bytes.NewReader(body))
|
|
req = req.WithContext(request.WithUser(req.Context(), user))
|
|
w := httptest.NewRecorder()
|
|
|
|
updateQueue(ds)(w, req)
|
|
Expect(w.Code).To(Equal(http.StatusNoContent))
|
|
Expect(repo.Queue.Items).To(HaveLen(2))
|
|
Expect(repo.Queue.Current).To(Equal(1))
|
|
Expect(repo.LastCols).To(ConsistOf("items", "current"))
|
|
})
|
|
|
|
It("returns bad request when new ids invalidate current", func() {
|
|
repo.Queue = &model.PlayQueue{UserID: user.ID, Current: 2}
|
|
payload := updateQueuePayload{Ids: gg.P([]string{"s1", "s2"})}
|
|
body, _ := json.Marshal(payload)
|
|
req := httptest.NewRequest("PUT", "/queue", bytes.NewReader(body))
|
|
req = req.WithContext(request.WithUser(req.Context(), user))
|
|
w := httptest.NewRecorder()
|
|
|
|
updateQueue(ds)(w, req)
|
|
Expect(w.Code).To(Equal(http.StatusBadRequest))
|
|
})
|
|
|
|
It("returns bad request when current out of bounds", func() {
|
|
repo.Queue = &model.PlayQueue{UserID: user.ID, Items: model.MediaFiles{{ID: "s1"}}}
|
|
payload := updateQueuePayload{Current: gg.P(3)}
|
|
body, _ := json.Marshal(payload)
|
|
req := httptest.NewRequest("PUT", "/queue", bytes.NewReader(body))
|
|
req = req.WithContext(request.WithUser(req.Context(), user))
|
|
w := httptest.NewRecorder()
|
|
|
|
updateQueue(ds)(w, req)
|
|
Expect(w.Code).To(Equal(http.StatusBadRequest))
|
|
})
|
|
|
|
It("returns bad request for malformed JSON", func() {
|
|
req := httptest.NewRequest("PUT", "/queue", bytes.NewReader([]byte("{")))
|
|
req = req.WithContext(request.WithUser(req.Context(), user))
|
|
w := httptest.NewRecorder()
|
|
|
|
updateQueue(ds)(w, req)
|
|
Expect(w.Code).To(Equal(http.StatusBadRequest))
|
|
})
|
|
|
|
It("returns internal server error when store fails", func() {
|
|
repo.Err = true
|
|
payload := updateQueuePayload{Position: gg.P(int64(10))}
|
|
body, _ := json.Marshal(payload)
|
|
req := httptest.NewRequest("PUT", "/queue", bytes.NewReader(body))
|
|
req = req.WithContext(request.WithUser(req.Context(), user))
|
|
w := httptest.NewRecorder()
|
|
|
|
updateQueue(ds)(w, req)
|
|
Expect(w.Code).To(Equal(http.StatusInternalServerError))
|
|
})
|
|
})
|
|
|
|
Describe("DELETE /queue", func() {
|
|
It("clears the queue", func() {
|
|
repo.Queue = &model.PlayQueue{UserID: user.ID, Items: model.MediaFiles{{ID: "s1"}}}
|
|
req := httptest.NewRequest("DELETE", "/queue", nil)
|
|
req = req.WithContext(request.WithUser(req.Context(), user))
|
|
w := httptest.NewRecorder()
|
|
|
|
clearQueue(ds)(w, req)
|
|
Expect(w.Code).To(Equal(http.StatusNoContent))
|
|
Expect(repo.Queue).To(BeNil())
|
|
})
|
|
|
|
It("returns internal server error when clear fails", func() {
|
|
repo.Err = true
|
|
req := httptest.NewRequest("DELETE", "/queue", nil)
|
|
req = req.WithContext(request.WithUser(req.Context(), user))
|
|
w := httptest.NewRecorder()
|
|
|
|
clearQueue(ds)(w, req)
|
|
Expect(w.Code).To(Equal(http.StatusInternalServerError))
|
|
})
|
|
})
|
|
})
|