feat: new "Subsonic Minimal Clients" configuration option (#4850)
* Add `.editorconfig` file Hints to users how to properly indent Go files (my setup was defaulting to 2 spaces). * Add Subsonic API minimal config option This will allow users to specify clients which can operate with or need the minimum required fields as per the [SubSonic API spec](https://subsonic.org/pages/api.jsp). * Return only required fields for Child Objects For a minimal client, only return the required fields for Child Objects. * Return only required fields for Playlist objects * refactor: simplify client list checks and improve playlist response handling Signed-off-by: Deluan <deluan@navidrome.org> * test: add unit tests for client list checks and playlist building logic Signed-off-by: Deluan <deluan@navidrome.org> * fix: revert Child.IsVideo and Playlist.Public fields from pointer to boolean, and add omitempty to XML tag Signed-off-by: Deluan <deluan@navidrome.org> --------- Signed-off-by: Deluan <deluan@navidrome.org> Co-authored-by: Deluan Quintão <deluan@navidrome.org>
This commit is contained in:
@@ -169,6 +169,190 @@ var _ = Describe("helpers", func() {
|
||||
})
|
||||
})
|
||||
|
||||
DescribeTable("isClientInList",
|
||||
func(list, client string, expected bool) {
|
||||
Expect(isClientInList(list, client)).To(Equal(expected))
|
||||
},
|
||||
Entry("returns false when clientList is empty", "", "some-client", false),
|
||||
Entry("returns false when client is empty", "client1,client2", "", false),
|
||||
Entry("returns false when both are empty", "", "", false),
|
||||
Entry("returns true when client matches single entry", "my-client", "my-client", true),
|
||||
Entry("returns true when client matches first in list", "client1,client2,client3", "client1", true),
|
||||
Entry("returns true when client matches middle in list", "client1,client2,client3", "client2", true),
|
||||
Entry("returns true when client matches last in list", "client1,client2,client3", "client3", true),
|
||||
Entry("returns false when client does not match", "client1,client2", "client3", false),
|
||||
Entry("trims whitespace from client list entries", "client1, client2 , client3", "client2", true),
|
||||
Entry("does not trim the client parameter", "client1,client2", " client1", false),
|
||||
)
|
||||
|
||||
Describe("childFromMediaFile", func() {
|
||||
var mf model.MediaFile
|
||||
var ctx context.Context
|
||||
|
||||
BeforeEach(func() {
|
||||
mf = model.MediaFile{
|
||||
ID: "mf-1",
|
||||
Title: "Test Song",
|
||||
Album: "Test Album",
|
||||
AlbumID: "album-1",
|
||||
Artist: "Test Artist",
|
||||
ArtistID: "artist-1",
|
||||
Year: 2023,
|
||||
Genre: "Rock",
|
||||
TrackNumber: 5,
|
||||
Duration: 180.5,
|
||||
Size: 5000000,
|
||||
Suffix: "mp3",
|
||||
BitRate: 320,
|
||||
}
|
||||
ctx = context.Background()
|
||||
})
|
||||
|
||||
Context("with minimal client", func() {
|
||||
BeforeEach(func() {
|
||||
conf.Server.Subsonic.MinimalClients = "minimal-client"
|
||||
player := model.Player{Client: "minimal-client"}
|
||||
ctx = request.WithPlayer(ctx, player)
|
||||
})
|
||||
|
||||
It("returns only basic fields", func() {
|
||||
child := childFromMediaFile(ctx, mf)
|
||||
Expect(child.Id).To(Equal("mf-1"))
|
||||
Expect(child.Title).To(Equal("Test Song"))
|
||||
Expect(child.IsDir).To(BeFalse())
|
||||
|
||||
// These should not be set
|
||||
Expect(child.Album).To(BeEmpty())
|
||||
Expect(child.Artist).To(BeEmpty())
|
||||
Expect(child.Parent).To(BeEmpty())
|
||||
Expect(child.Year).To(BeZero())
|
||||
Expect(child.Genre).To(BeEmpty())
|
||||
Expect(child.Track).To(BeZero())
|
||||
Expect(child.Duration).To(BeZero())
|
||||
Expect(child.Size).To(BeZero())
|
||||
Expect(child.Suffix).To(BeEmpty())
|
||||
Expect(child.BitRate).To(BeZero())
|
||||
Expect(child.CoverArt).To(BeEmpty())
|
||||
Expect(child.ContentType).To(BeEmpty())
|
||||
Expect(child.Path).To(BeEmpty())
|
||||
})
|
||||
|
||||
It("does not include OpenSubsonic extension", func() {
|
||||
child := childFromMediaFile(ctx, mf)
|
||||
Expect(child.OpenSubsonicChild).To(BeNil())
|
||||
})
|
||||
})
|
||||
|
||||
Context("with non-minimal client", func() {
|
||||
BeforeEach(func() {
|
||||
conf.Server.Subsonic.MinimalClients = "minimal-client"
|
||||
player := model.Player{Client: "regular-client"}
|
||||
ctx = request.WithPlayer(ctx, player)
|
||||
})
|
||||
|
||||
It("returns all fields", func() {
|
||||
child := childFromMediaFile(ctx, mf)
|
||||
Expect(child.Id).To(Equal("mf-1"))
|
||||
Expect(child.Title).To(Equal("Test Song"))
|
||||
Expect(child.IsDir).To(BeFalse())
|
||||
Expect(child.Album).To(Equal("Test Album"))
|
||||
Expect(child.Artist).To(Equal("Test Artist"))
|
||||
Expect(child.Parent).To(Equal("album-1"))
|
||||
Expect(child.Year).To(Equal(int32(2023)))
|
||||
Expect(child.Genre).To(Equal("Rock"))
|
||||
Expect(child.Track).To(Equal(int32(5)))
|
||||
Expect(child.Duration).To(Equal(int32(180)))
|
||||
Expect(child.Size).To(Equal(int64(5000000)))
|
||||
Expect(child.Suffix).To(Equal("mp3"))
|
||||
Expect(child.BitRate).To(Equal(int32(320)))
|
||||
})
|
||||
})
|
||||
|
||||
Context("when minimal clients list is empty", func() {
|
||||
BeforeEach(func() {
|
||||
conf.Server.Subsonic.MinimalClients = ""
|
||||
player := model.Player{Client: "any-client"}
|
||||
ctx = request.WithPlayer(ctx, player)
|
||||
})
|
||||
|
||||
It("returns all fields", func() {
|
||||
child := childFromMediaFile(ctx, mf)
|
||||
Expect(child.Album).To(Equal("Test Album"))
|
||||
Expect(child.Artist).To(Equal("Test Artist"))
|
||||
})
|
||||
})
|
||||
|
||||
Context("when no player in context", func() {
|
||||
It("returns all fields", func() {
|
||||
child := childFromMediaFile(ctx, mf)
|
||||
Expect(child.Album).To(Equal("Test Album"))
|
||||
Expect(child.Artist).To(Equal("Test Artist"))
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Describe("osChildFromMediaFile", func() {
|
||||
var mf model.MediaFile
|
||||
var ctx context.Context
|
||||
|
||||
BeforeEach(func() {
|
||||
mf = model.MediaFile{
|
||||
ID: "mf-1",
|
||||
Title: "Test Song",
|
||||
Artist: "Test Artist",
|
||||
Comment: "Test Comment",
|
||||
}
|
||||
ctx = context.Background()
|
||||
})
|
||||
|
||||
Context("with minimal client", func() {
|
||||
BeforeEach(func() {
|
||||
conf.Server.Subsonic.MinimalClients = "minimal-client"
|
||||
player := model.Player{Client: "minimal-client"}
|
||||
ctx = request.WithPlayer(ctx, player)
|
||||
})
|
||||
|
||||
It("returns nil", func() {
|
||||
osChild := osChildFromMediaFile(ctx, mf)
|
||||
Expect(osChild).To(BeNil())
|
||||
})
|
||||
})
|
||||
|
||||
Context("with non-minimal client", func() {
|
||||
BeforeEach(func() {
|
||||
conf.Server.Subsonic.MinimalClients = "minimal-client"
|
||||
player := model.Player{Client: "regular-client"}
|
||||
ctx = request.WithPlayer(ctx, player)
|
||||
})
|
||||
|
||||
It("returns OpenSubsonic child fields", func() {
|
||||
osChild := osChildFromMediaFile(ctx, mf)
|
||||
Expect(osChild).ToNot(BeNil())
|
||||
Expect(osChild.Comment).To(Equal("Test Comment"))
|
||||
})
|
||||
})
|
||||
|
||||
Context("when minimal clients list is empty", func() {
|
||||
BeforeEach(func() {
|
||||
conf.Server.Subsonic.MinimalClients = ""
|
||||
player := model.Player{Client: "any-client"}
|
||||
ctx = request.WithPlayer(ctx, player)
|
||||
})
|
||||
|
||||
It("returns OpenSubsonic child fields", func() {
|
||||
osChild := osChildFromMediaFile(ctx, mf)
|
||||
Expect(osChild).ToNot(BeNil())
|
||||
})
|
||||
})
|
||||
|
||||
Context("when no player in context", func() {
|
||||
It("returns OpenSubsonic child fields", func() {
|
||||
osChild := osChildFromMediaFile(ctx, mf)
|
||||
Expect(osChild).ToNot(BeNil())
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Describe("selectedMusicFolderIds", func() {
|
||||
var user model.User
|
||||
var ctx context.Context
|
||||
|
||||
Reference in New Issue
Block a user