mirror of
https://github.com/navidrome/navidrome.git
synced 2026-08-31 07:30:32 +00:00
* refactor(req): drop redundant error return from Strings
The error from Strings carried no information beyond emptiness — it fired
exactly when the param was absent — and nearly every caller discarded it with
a blank identifier. Strings now just returns the values (empty when absent),
making the common optional-list reads one clean expression.
The few required-param callers (scrobble, createShare) check for emptiness and
return the same Subsonic error code 10 as before; their e2e tests now pin that
code. Ints and Times keep their contracts by synthesizing ErrMissingParam
themselves, so selectedMusicFolderIds is untouched. The jellyfin parseFields
helper is inlined away, since ParseFields(p.Strings("fields")...) now
compiles directly.
* docs(req): clarify Strings returns nil when param is absent
273 lines
7.5 KiB
Go
273 lines
7.5 KiB
Go
package e2e
|
|
|
|
import (
|
|
"github.com/navidrome/navidrome/model"
|
|
"github.com/navidrome/navidrome/server/subsonic/responses"
|
|
. "github.com/onsi/ginkgo/v2"
|
|
. "github.com/onsi/gomega"
|
|
)
|
|
|
|
var _ = Describe("Media Annotation Endpoints", Ordered, func() {
|
|
BeforeAll(func() {
|
|
setupTestDB()
|
|
})
|
|
|
|
Describe("Star/Unstar", Ordered, func() {
|
|
var songID, albumID, artistID string
|
|
|
|
BeforeAll(func() {
|
|
// Look up a song from the scanned data
|
|
songs, err := ds.MediaFile(ctx).GetAll(model.QueryOptions{Max: 1, Sort: "title"})
|
|
Expect(err).ToNot(HaveOccurred())
|
|
Expect(songs).ToNot(BeEmpty())
|
|
songID = songs[0].ID
|
|
|
|
// Look up an album
|
|
albums, err := ds.Album(ctx).GetAll(model.QueryOptions{Max: 1, Sort: "name"})
|
|
Expect(err).ToNot(HaveOccurred())
|
|
Expect(albums).ToNot(BeEmpty())
|
|
albumID = albums[0].ID
|
|
|
|
// Look up an artist
|
|
artists, err := ds.Artist(ctx).GetAll(model.QueryOptions{Max: 1, Sort: "name"})
|
|
Expect(err).ToNot(HaveOccurred())
|
|
Expect(artists).ToNot(BeEmpty())
|
|
artistID = artists[0].ID
|
|
})
|
|
|
|
It("stars a song by id", func() {
|
|
resp := doReq("star", "id", songID)
|
|
|
|
Expect(resp.Status).To(Equal(responses.StatusOK))
|
|
})
|
|
|
|
It("starred song appears in getStarred response", func() {
|
|
resp := doReq("getStarred")
|
|
|
|
Expect(resp.Status).To(Equal(responses.StatusOK))
|
|
Expect(resp.Starred).ToNot(BeNil())
|
|
Expect(resp.Starred.Song).To(HaveLen(1))
|
|
Expect(resp.Starred.Song[0].Id).To(Equal(songID))
|
|
})
|
|
|
|
It("unstars a previously starred song", func() {
|
|
resp := doReq("unstar", "id", songID)
|
|
|
|
Expect(resp.Status).To(Equal(responses.StatusOK))
|
|
|
|
// Verify song no longer appears in starred
|
|
resp = doReq("getStarred")
|
|
|
|
Expect(resp.Starred.Song).To(BeEmpty())
|
|
})
|
|
|
|
It("stars an album by albumId", func() {
|
|
resp := doReq("star", "albumId", albumID)
|
|
|
|
Expect(resp.Status).To(Equal(responses.StatusOK))
|
|
|
|
// Verify album appears in starred
|
|
resp = doReq("getStarred")
|
|
|
|
Expect(resp.Starred.Album).To(HaveLen(1))
|
|
Expect(resp.Starred.Album[0].Id).To(Equal(albumID))
|
|
})
|
|
|
|
It("stars an artist by artistId", func() {
|
|
resp := doReq("star", "artistId", artistID)
|
|
|
|
Expect(resp.Status).To(Equal(responses.StatusOK))
|
|
|
|
// Verify artist appears in starred
|
|
resp = doReq("getStarred")
|
|
|
|
Expect(resp.Starred.Artist).To(HaveLen(1))
|
|
Expect(resp.Starred.Artist[0].Id).To(Equal(artistID))
|
|
})
|
|
|
|
It("returns error when no id provided", func() {
|
|
resp := doReq("star")
|
|
|
|
Expect(resp.Status).To(Equal(responses.StatusFailed))
|
|
Expect(resp.Error).ToNot(BeNil())
|
|
})
|
|
})
|
|
|
|
Describe("SetRating", Ordered, func() {
|
|
var songID, albumID string
|
|
|
|
BeforeAll(func() {
|
|
songs, err := ds.MediaFile(ctx).GetAll(model.QueryOptions{Max: 1, Sort: "title"})
|
|
Expect(err).ToNot(HaveOccurred())
|
|
Expect(songs).ToNot(BeEmpty())
|
|
songID = songs[0].ID
|
|
|
|
albums, err := ds.Album(ctx).GetAll(model.QueryOptions{Max: 1, Sort: "name"})
|
|
Expect(err).ToNot(HaveOccurred())
|
|
Expect(albums).ToNot(BeEmpty())
|
|
albumID = albums[0].ID
|
|
})
|
|
|
|
It("sets rating on a song", func() {
|
|
resp := doReq("setRating", "id", songID, "rating", "4")
|
|
|
|
Expect(resp.Status).To(Equal(responses.StatusOK))
|
|
})
|
|
|
|
It("rated song has correct userRating in getSong", func() {
|
|
resp := doReq("getSong", "id", songID)
|
|
|
|
Expect(resp.Status).To(Equal(responses.StatusOK))
|
|
Expect(resp.Song).ToNot(BeNil())
|
|
Expect(resp.Song.UserRating).To(Equal(int32(4)))
|
|
})
|
|
|
|
It("sets rating on an album", func() {
|
|
resp := doReq("setRating", "id", albumID, "rating", "3")
|
|
|
|
Expect(resp.Status).To(Equal(responses.StatusOK))
|
|
})
|
|
|
|
It("returns error for missing parameters", func() {
|
|
// Missing both id and rating
|
|
resp := doReq("setRating")
|
|
Expect(resp.Status).To(Equal(responses.StatusFailed))
|
|
|
|
// Missing rating
|
|
resp = doReq("setRating", "id", songID)
|
|
Expect(resp.Status).To(Equal(responses.StatusFailed))
|
|
})
|
|
})
|
|
|
|
Describe("Scrobble", func() {
|
|
It("submits a scrobble for a song", func() {
|
|
songs, err := ds.MediaFile(ctx).GetAll(model.QueryOptions{Max: 1, Sort: "title"})
|
|
Expect(err).ToNot(HaveOccurred())
|
|
Expect(songs).ToNot(BeEmpty())
|
|
|
|
resp := doReq("scrobble", "id", songs[0].ID, "submission", "true")
|
|
|
|
Expect(resp.Status).To(Equal(responses.StatusOK))
|
|
})
|
|
|
|
It("returns error when id is missing", func() {
|
|
resp := doReq("scrobble")
|
|
|
|
Expect(resp.Status).To(Equal(responses.StatusFailed))
|
|
Expect(resp.Error).ToNot(BeNil())
|
|
Expect(resp.Error.Code).To(Equal(responses.ErrorMissingParameter))
|
|
})
|
|
})
|
|
|
|
Describe("ReportPlayback", Ordered, func() {
|
|
var songID string
|
|
|
|
BeforeAll(func() {
|
|
songs, err := ds.MediaFile(ctx).GetAll(model.QueryOptions{Max: 1, Sort: "title"})
|
|
Expect(err).ToNot(HaveOccurred())
|
|
Expect(songs).ToNot(BeEmpty())
|
|
songID = songs[0].ID
|
|
})
|
|
|
|
It("returns error when required params are missing", func() {
|
|
resp := doReq("reportPlayback")
|
|
Expect(resp.Status).To(Equal(responses.StatusFailed))
|
|
})
|
|
|
|
It("returns error for invalid state", func() {
|
|
resp := doReq("reportPlayback",
|
|
"mediaId", songID,
|
|
"mediaType", "song",
|
|
"positionMs", "0",
|
|
"state", "invalid",
|
|
)
|
|
Expect(resp.Status).To(Equal(responses.StatusFailed))
|
|
})
|
|
|
|
It("starting report creates a getNowPlaying entry", func() {
|
|
resp := doReq("reportPlayback",
|
|
"mediaId", songID,
|
|
"mediaType", "song",
|
|
"positionMs", "0",
|
|
"state", "starting",
|
|
)
|
|
Expect(resp.Status).To(Equal(responses.StatusOK))
|
|
|
|
np := doReq("getNowPlaying")
|
|
Expect(np.Status).To(Equal(responses.StatusOK))
|
|
Expect(np.NowPlaying.Entry).To(HaveLen(1))
|
|
Expect(np.NowPlaying.Entry[0].Id).To(Equal(songID))
|
|
Expect(np.NowPlaying.Entry[0].State).To(Equal("starting"))
|
|
})
|
|
|
|
It("playing report updates getNowPlaying state and position", func() {
|
|
resp := doReq("reportPlayback",
|
|
"mediaId", songID,
|
|
"mediaType", "song",
|
|
"positionMs", "30000",
|
|
"state", "playing",
|
|
)
|
|
Expect(resp.Status).To(Equal(responses.StatusOK))
|
|
|
|
np := doReq("getNowPlaying")
|
|
Expect(np.NowPlaying.Entry).To(HaveLen(1))
|
|
Expect(np.NowPlaying.Entry[0].State).To(Equal("playing"))
|
|
Expect(np.NowPlaying.Entry[0].PositionMs).To(BeNumerically(">=", int64(30000)))
|
|
})
|
|
|
|
It("paused report freezes position in getNowPlaying", func() {
|
|
resp := doReq("reportPlayback",
|
|
"mediaId", songID,
|
|
"mediaType", "song",
|
|
"positionMs", "30000",
|
|
"state", "paused",
|
|
)
|
|
Expect(resp.Status).To(Equal(responses.StatusOK))
|
|
|
|
np := doReq("getNowPlaying")
|
|
Expect(np.NowPlaying.Entry).To(HaveLen(1))
|
|
Expect(np.NowPlaying.Entry[0].State).To(Equal("paused"))
|
|
Expect(np.NowPlaying.Entry[0].PositionMs).To(Equal(int64(30000)))
|
|
})
|
|
|
|
It("stopped report removes entry from getNowPlaying", func() {
|
|
resp := doReq("reportPlayback",
|
|
"mediaId", songID,
|
|
"mediaType", "song",
|
|
"positionMs", "90000",
|
|
"state", "stopped",
|
|
)
|
|
Expect(resp.Status).To(Equal(responses.StatusOK))
|
|
|
|
np := doReq("getNowPlaying")
|
|
Expect(np.NowPlaying.Entry).To(BeEmpty())
|
|
})
|
|
|
|
It("accepts mediaType=podcast without error", func() {
|
|
resp := doReq("reportPlayback",
|
|
"mediaId", songID,
|
|
"mediaType", "podcast",
|
|
"positionMs", "0",
|
|
"state", "starting",
|
|
)
|
|
Expect(resp.Status).To(Equal(responses.StatusOK))
|
|
})
|
|
|
|
It("accepts optional playbackRate and ignoreScrobble", func() {
|
|
resp := doReq("reportPlayback",
|
|
"mediaId", songID,
|
|
"mediaType", "song",
|
|
"positionMs", "5000",
|
|
"state", "playing",
|
|
"playbackRate", "1.5",
|
|
"ignoreScrobble", "true",
|
|
)
|
|
Expect(resp.Status).To(Equal(responses.StatusOK))
|
|
|
|
np := doReq("getNowPlaying")
|
|
Expect(np.NowPlaying.Entry).To(HaveLen(1))
|
|
Expect(np.NowPlaying.Entry[0].PlaybackRate).To(Equal(1.5))
|
|
})
|
|
})
|
|
})
|