Files
navidrome/server/nativeapi/queue_test.go
Deluan Quintão 8fcd8ba61a feat(server): add index-based play queue endpoints to native API (#4210)
* Add migration converting playqueue current to index

* refactor

Signed-off-by: Deluan <deluan@navidrome.org>

* fix(queue): ensure valid current index and improve test coverage

Signed-off-by: Deluan <deluan@navidrome.org>

---------

Signed-off-by: Deluan <deluan@navidrome.org>
2025-06-10 23:00:44 -04:00

165 lines
5.4 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/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 := queuePayload{Ids: []string{"s1", "s2"}, Current: 1, Position: 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 := queuePayload{Ids: []string{}, Current: 0, Position: 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 := queuePayload{Ids: []string{"s1", "s2"}, Current: -1, Position: 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 := queuePayload{Ids: []string{"s1", "s2"}, Current: 2, Position: 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 := queuePayload{Ids: []string{"s1"}, Current: 0, Position: 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))
})
})
})