test(model): fix path-separator specs in the tests instead of production

Folder IDs are persisted, so the Windows IDs stay as they are and the
spec pins them. NewFolder gets the forward-slash paths the scanner
passes, and the M3U expectations use the OS separator.

Signed-off-by: junkerderprovinz <jdp@braethoria.com>
This commit is contained in:
junkerderprovinz 2026-10-09 20:49:40 +02:00
parent bc4c72eb42
commit 5d71b6e0d5
5 changed files with 19 additions and 22 deletions

View File

@ -43,21 +43,15 @@ func (f Folder) String() string {
// FolderID generates a unique ID for a folder in a library.
// The ID is generated based on the library ID and the folder path relative to the library root.
// Any leading or trailing slashes are removed from the folder path.
func FolderID(lib Library, folderPath string) string {
folderPath = strings.TrimPrefix(folderPath, lib.Path)
folderPath = strings.TrimPrefix(folderPath, string(os.PathSeparator))
// Folder IDs must be stable across operating systems, so normalize to
// forward slashes (path.Clean, not filepath.Clean) before hashing;
// otherwise a nested path on Windows would hash with back-slashes.
folderPath = path.Clean(filepath.ToSlash(folderPath))
key := fmt.Sprintf("%d:%s", lib.ID, folderPath)
func FolderID(lib Library, path string) string {
path = strings.TrimPrefix(path, lib.Path)
path = strings.TrimPrefix(path, string(os.PathSeparator))
path = filepath.Clean(path)
key := fmt.Sprintf("%d:%s", lib.ID, path)
return id.NewHash(key)
}
func NewFolder(lib Library, folderPath string) *Folder {
// path.Split/path.Clean below only treat "/" as a separator, so normalize
// OS separators first to derive dir/name correctly on Windows.
folderPath = filepath.ToSlash(folderPath)
newID := FolderID(lib, folderPath)
dir, name := path.Split(folderPath)
dir = path.Clean(dir)

View File

@ -67,7 +67,9 @@ var _ = Describe("Folder", func() {
When("the folder has multiple subdirs", func() {
It("should return the correct folder ID", func() {
folderPath := filepath.FromSlash("/music/rock/metal")
expectedID := id.NewHash("1:rock/metal")
// IDs are persisted and hashed with the OS separator, so changing
// this on Windows would need a migration.
expectedID := id.NewHash("1:" + filepath.FromSlash("rock/metal"))
Expect(model.FolderID(lib, folderPath)).To(Equal(expectedID))
})
})
@ -75,7 +77,8 @@ var _ = Describe("Folder", func() {
Describe("NewFolder", func() {
It("should create a new SubFolder with the correct attributes", func() {
folderPath := filepath.FromSlash("rock/metal")
// The scanner walks an fs.FS, so folder paths always use forward slashes
folderPath := "rock/metal"
folder := model.NewFolder(lib, folderPath)
Expect(folder.LibraryID).To(Equal(lib.ID))

View File

@ -527,13 +527,11 @@ func (mfs MediaFiles) ToM3U8(title string, absolutePaths bool) string {
buf.WriteString(fmt.Sprintf("#PLAYLIST:%s\n", title))
for _, t := range mfs {
buf.WriteString(fmt.Sprintf("#EXTINF:%.f,%s - %s\n", t.Duration, t.Artist, t.Title))
p := t.Path
if absolutePaths {
p = t.AbsolutePath()
buf.WriteString(t.AbsolutePath() + "\n")
} else {
buf.WriteString(t.Path + "\n")
}
// Emit forward slashes so the exported .m3u8 is consistent across OSes
// (AbsolutePath returns native separators on Windows).
buf.WriteString(filepath.ToSlash(p) + "\n")
}
return buf.String()
}

View File

@ -1,6 +1,7 @@
package model_test
import (
"path/filepath"
"time"
"github.com/navidrome/navidrome/conf"
@ -483,9 +484,10 @@ var _ = Describe("MediaFiles", func() {
false,
"#EXTM3U\n#PLAYLIST:Multi Track\n#EXTINF:120,Artist A - Song One\na/song1.mp3\n#EXTINF:241,Artist B - Song Two\nb/song2.mp3\n#EXTINF:90,Artist with Ümläuts - Song with \"quotes\" & ampersands\nspecial/file.mp3\n",
),
// AbsolutePath joins with the OS separator
Entry("absolute paths",
true,
"#EXTM3U\n#PLAYLIST:Multi Track\n#EXTINF:120,Artist A - Song One\n/music/a/song1.mp3\n#EXTINF:241,Artist B - Song Two\n/music/b/song2.mp3\n#EXTINF:90,Artist with Ümläuts - Song with \"quotes\" & ampersands\n/música/special/file.mp3\n",
filepath.FromSlash("#EXTM3U\n#PLAYLIST:Multi Track\n#EXTINF:120,Artist A - Song One\n/music/a/song1.mp3\n#EXTINF:241,Artist B - Song Two\n/music/b/song2.mp3\n#EXTINF:90,Artist with Ümläuts - Song with \"quotes\" & ampersands\n/música/special/file.mp3\n"),
),
Entry("special characters",
false,
@ -506,8 +508,8 @@ var _ = Describe("MediaFiles", func() {
Expect(relativeResult).To(ContainSubstring("deep/nested/song.mp3\n"))
absoluteResult := mfs.ToM3U8("Test", true)
Expect(absoluteResult).To(ContainSubstring("/lib/song.mp3\n"))
Expect(absoluteResult).To(ContainSubstring("/lib/deep/nested/song.mp3\n"))
Expect(absoluteResult).To(ContainSubstring(filepath.FromSlash("/lib/song.mp3\n")))
Expect(absoluteResult).To(ContainSubstring(filepath.FromSlash("/lib/deep/nested/song.mp3\n")))
})
})
})

View File

@ -44,7 +44,7 @@ var _ = Describe("Playlist", func() {
#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))
Expect(pls.ToM3U8()).To(Equal(filepath.FromSlash(expected)))
})
})