mirror of
https://github.com/navidrome/navidrome.git
synced 2026-10-08 02:17:25 +02:00
188 lines
7.2 KiB
Go
188 lines
7.2 KiB
Go
package model_test
|
|
|
|
import (
|
|
"path/filepath"
|
|
"time"
|
|
|
|
"github.com/navidrome/navidrome/conf"
|
|
"github.com/navidrome/navidrome/conf/configtest"
|
|
"github.com/navidrome/navidrome/model"
|
|
"github.com/navidrome/navidrome/model/criteria"
|
|
. "github.com/onsi/ginkgo/v2"
|
|
. "github.com/onsi/gomega"
|
|
)
|
|
|
|
var _ = Describe("Playlist", func() {
|
|
Describe("ToM3U8()", func() {
|
|
var pls model.Playlist
|
|
BeforeEach(func() {
|
|
pls = model.Playlist{Name: "Mellow sunset"}
|
|
pls.Tracks = model.PlaylistTracks{
|
|
{MediaFile: model.MediaFile{Artist: "Morcheeba feat. Kurt Wagner", Title: "What New York Couples Fight About",
|
|
Duration: 377.84,
|
|
LibraryPath: "/music/library", Path: "Morcheeba/Charango/01-06 What New York Couples Fight About.mp3"}},
|
|
{MediaFile: model.MediaFile{Artist: "A Tribe Called Quest", Title: "Description of a Fool (Groove Armada's Acoustic mix)",
|
|
Duration: 374.49,
|
|
LibraryPath: "/music/library", Path: "Groove Armada/Back to Mine_ Groove Armada/01-01 Description of a Fool (Groove Armada's Acoustic mix).mp3"}},
|
|
{MediaFile: model.MediaFile{Artist: "Lou Reed", Title: "Walk on the Wild Side",
|
|
Duration: 253.1,
|
|
LibraryPath: "/music/library", Path: "Lou Reed/Walk on the Wild Side_ The Best of Lou Reed/01-06 Walk on the Wild Side.m4a"}},
|
|
{MediaFile: model.MediaFile{Artist: "Legião Urbana", Title: "On the Way Home",
|
|
Duration: 163.89,
|
|
LibraryPath: "/music/library", Path: "Legião Urbana/Música p_ acampamentos/02-05 On the Way Home.mp3"}},
|
|
}
|
|
})
|
|
It("generates the correct M3U format", func() {
|
|
expected := `#EXTM3U
|
|
#PLAYLIST:Mellow sunset
|
|
#EXTINF:378,Morcheeba feat. Kurt Wagner - What New York Couples Fight About
|
|
/music/library/Morcheeba/Charango/01-06 What New York Couples Fight About.mp3
|
|
#EXTINF:374,A Tribe Called Quest - Description of a Fool (Groove Armada's Acoustic mix)
|
|
/music/library/Groove Armada/Back to Mine_ Groove Armada/01-01 Description of a Fool (Groove Armada's Acoustic mix).mp3
|
|
#EXTINF:253,Lou Reed - Walk on the Wild Side
|
|
/music/library/Lou Reed/Walk on the Wild Side_ The Best of Lou Reed/01-06 Walk on the Wild Side.m4a
|
|
#EXTINF:164,Legião Urbana - On the Way Home
|
|
/music/library/Legião Urbana/Música p_ acampamentos/02-05 On the Way Home.mp3
|
|
`
|
|
Expect(pls.ToM3U8()).To(Equal(expected))
|
|
})
|
|
})
|
|
|
|
Describe("RefreshDelay", func() {
|
|
BeforeEach(func() {
|
|
DeferCleanup(configtest.SetupConfig())
|
|
conf.Server.SmartPlaylistRefreshDelay = 5 * time.Second
|
|
})
|
|
|
|
It("returns the global config value when rules have no refreshDelay", func() {
|
|
pls := model.Playlist{Rules: &criteria.Criteria{Expression: criteria.All{criteria.Is{"loved": true}}}}
|
|
Expect(pls.RefreshDelay()).To(Equal(5 * time.Second))
|
|
})
|
|
|
|
It("returns the per-playlist value when set", func() {
|
|
pls := model.Playlist{Rules: &criteria.Criteria{
|
|
Expression: criteria.All{criteria.Is{"loved": true}},
|
|
RefreshDelay: 24 * time.Hour,
|
|
}}
|
|
Expect(pls.RefreshDelay()).To(Equal(24 * time.Hour))
|
|
})
|
|
|
|
It("returns the global value for non-smart playlists", func() {
|
|
pls := model.Playlist{}
|
|
Expect(pls.RefreshDelay()).To(Equal(5 * time.Second))
|
|
})
|
|
})
|
|
|
|
Describe("TracksEditable", func() {
|
|
It("is true for a plain playlist", func() {
|
|
Expect(model.Playlist{}.TracksEditable()).To(BeTrue())
|
|
})
|
|
|
|
It("is false for a smart playlist", func() {
|
|
pls := model.Playlist{Rules: &criteria.Criteria{Expression: criteria.Is{"loved": true}}}
|
|
Expect(pls.TracksEditable()).To(BeFalse())
|
|
})
|
|
|
|
It("is false for a synced playlist", func() {
|
|
Expect(model.Playlist{Sync: true}.TracksEditable()).To(BeFalse())
|
|
})
|
|
})
|
|
|
|
Describe("NormalizedRules()", func() {
|
|
// absPath builds an OS-native absolute path so these specs also run on Windows.
|
|
absPath := func(parts ...string) string {
|
|
abs, err := filepath.Abs(filepath.Join(parts...))
|
|
Expect(err).ToNot(HaveOccurred())
|
|
return abs
|
|
}
|
|
normalize := func(pls model.Playlist) criteria.Expression {
|
|
return pls.NormalizedRules().Expression
|
|
}
|
|
|
|
It("resolves relative references against the playlist folder", func() {
|
|
pls := model.Playlist{
|
|
Path: absPath("test", "nested", "my-playlist.nsp"),
|
|
Rules: &criteria.Criteria{Expression: criteria.All{
|
|
criteria.InPlaylist{"path": "../up.m3u"},
|
|
criteria.NotInPlaylist{"path": "./sibling.nsp"},
|
|
criteria.Any{criteria.InPlaylist{"path": "sub/deep.nsp"}},
|
|
}},
|
|
}
|
|
Expect(normalize(pls)).To(BeEquivalentTo(criteria.All{
|
|
criteria.InPlaylist{"path": absPath("test", "up.m3u")},
|
|
criteria.NotInPlaylist{"path": absPath("test", "nested", "sibling.nsp")},
|
|
criteria.Any{criteria.InPlaylist{"path": absPath("test", "nested", "sub", "deep.nsp")}},
|
|
}))
|
|
})
|
|
|
|
It("cleans absolute references", func() {
|
|
dirty := absPath("music") + string(filepath.Separator) + "." + string(filepath.Separator) + "child.nsp"
|
|
pls := model.Playlist{
|
|
Path: absPath("test", "my-playlist.nsp"),
|
|
Rules: &criteria.Criteria{Expression: criteria.All{criteria.NotInPlaylist{"path": dirty}}},
|
|
}
|
|
Expect(normalize(pls)).To(BeEquivalentTo(criteria.All{
|
|
criteria.NotInPlaylist{"path": absPath("music", "child.nsp")},
|
|
}))
|
|
})
|
|
|
|
It("treats a leading slash as absolute on every OS", func() {
|
|
pls := model.Playlist{
|
|
Path: absPath("test", "my-playlist.nsp"),
|
|
Rules: &criteria.Criteria{Expression: criteria.All{criteria.InPlaylist{"path": "/other/./root.m3u"}}},
|
|
}
|
|
Expect(normalize(pls)).To(BeEquivalentTo(criteria.All{
|
|
criteria.InPlaylist{"path": filepath.FromSlash("/other/root.m3u")},
|
|
}))
|
|
})
|
|
|
|
It("leaves empty paths and id references untouched", func() {
|
|
pls := model.Playlist{
|
|
Path: absPath("test", "my-playlist.nsp"),
|
|
Rules: &criteria.Criteria{Expression: criteria.All{
|
|
criteria.InPlaylist{"path": ""},
|
|
criteria.InPlaylist{"id": "94d8ba52-7aca-40e2-af82-4cb09c43d710"},
|
|
criteria.Eq{"artist": "Bob Dealin"},
|
|
}},
|
|
}
|
|
Expect(normalize(pls)).To(BeEquivalentTo(criteria.All{
|
|
criteria.InPlaylist{"path": ""},
|
|
criteria.InPlaylist{"id": "94d8ba52-7aca-40e2-af82-4cb09c43d710"},
|
|
criteria.Eq{"artist": "Bob Dealin"},
|
|
}))
|
|
})
|
|
|
|
It("skips relative references when the playlist has no path", func() {
|
|
pls := model.Playlist{
|
|
Rules: &criteria.Criteria{Expression: criteria.All{criteria.InPlaylist{"path": "../up.m3u"}}},
|
|
}
|
|
Expect(normalize(pls)).To(BeEquivalentTo(criteria.All{
|
|
criteria.InPlaylist{"path": filepath.FromSlash("../up.m3u")},
|
|
}))
|
|
})
|
|
|
|
It("preserves every other criteria field", func() {
|
|
rules := criteria.Criteria{
|
|
Expression: criteria.All{criteria.InPlaylist{"path": "child.nsp"}},
|
|
Sort: "title",
|
|
Order: "desc",
|
|
Limit: 10,
|
|
LimitPercent: 25,
|
|
Offset: 5,
|
|
RefreshDelay: 3 * time.Hour,
|
|
}
|
|
pls := model.Playlist{Path: absPath("test", "my-playlist.nsp"), Rules: &rules}
|
|
|
|
normalized := *pls.NormalizedRules()
|
|
normalized.Expression = rules.Expression
|
|
Expect(normalized).To(Equal(rules))
|
|
})
|
|
|
|
It("does not mutate the original playlist rules", func() {
|
|
original := criteria.All{criteria.InPlaylist{"path": "child.nsp"}}
|
|
pls := model.Playlist{Path: absPath("test", "my-playlist.nsp"), Rules: &criteria.Criteria{Expression: original}}
|
|
_ = pls.NormalizedRules()
|
|
Expect(original[0]).To(BeEquivalentTo(criteria.InPlaylist{"path": "child.nsp"}))
|
|
})
|
|
})
|
|
})
|