package model_test import ( "os" "path/filepath" "github.com/navidrome/navidrome/model" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) var _ = Describe("LibraryMatcher", func() { // Paths are written Unix-style and converted, so they use the OS separator, as filepath.Abs output does find := func(libs model.Libraries, path string) int { for i := range libs { libs[i].Path = filepath.FromSlash(libs[i].Path) } lib, ok := model.NewLibraryMatcher(libs).FindLibrary(filepath.FromSlash(path)) if !ok { return 0 } return lib.ID } DescribeTable("matches the longest library path", func(libs model.Libraries, path string, expectedID int) { Expect(find(libs, path)).To(Equal(expectedID)) }, Entry("nested library", model.Libraries{{ID: 1, Path: "/music"}, {ID: 2, Path: "/music-classical"}, {ID: 3, Path: "/music-classical/opera"}}, "/music-classical/opera/subdir/track.mp3", 3), Entry("sibling with a shared prefix", model.Libraries{{ID: 1, Path: "/music"}, {ID: 2, Path: "/music-classical"}}, "/music-classical/track.mp3", 2), Entry("shorter library", model.Libraries{{ID: 1, Path: "/music"}, {ID: 2, Path: "/music-classical"}}, "/music/track.mp3", 1), Entry("exact library root", model.Libraries{{ID: 1, Path: "/music"}, {ID: 2, Path: "/music-classical"}}, "/music-classical", 2), Entry("deeply nested libraries", model.Libraries{{ID: 1, Path: "/media"}, {ID: 2, Path: "/media/audio"}, {ID: 3, Path: "/media/audio/classical"}, {ID: 4, Path: "/media/audio/classical/baroque"}}, "/media/audio/classical/mozart/track.mp3", 3), Entry("prefix that is not a path boundary", model.Libraries{{ID: 1, Path: "/a"}, {ID: 2, Path: "/ab"}, {ID: 3, Path: "/abc"}}, "/ab/file.mp3", 2), Entry("special characters match literally", model.Libraries{{ID: 1, Path: "/music[test]"}, {ID: 2, Path: "/music(backup)"}}, "/music[test]/track.mp3", 1), Entry("library path with a trailing slash", model.Libraries{{ID: 1, Path: "/music/"}}, "/music/track.mp3", 1), Entry("library at the filesystem root", model.Libraries{{ID: 1, Path: "/"}}, "/music/track.mp3", 1), Entry("nested library under a root library", model.Libraries{{ID: 1, Path: "/"}, {ID: 2, Path: "/music"}}, "/music/track.mp3", 2), ) It("does not match a path outside every library", func() { Expect(find(model.Libraries{{ID: 1, Path: "/music"}}, "/music-backup/track.mp3")).To(BeZero()) }) It("does not match anything without libraries", func() { Expect(find(nil, "/music/track.mp3")).To(BeZero()) }) It("does not reorder the caller's libraries", func() { libs := model.Libraries{{ID: 1, Path: "/a"}, {ID: 2, Path: "/abc"}} model.NewLibraryMatcher(libs) Expect(libs.IDs()).To(Equal([]int{1, 2})) }) }) var _ = Describe("LibraryRelativePath", func() { // Paths are built with filepath so the "absolute" cases stay absolute on every OS // (a Unix-style "/foo" is not absolute on Windows). libRoot, _ := filepath.Abs(filepath.Join("jukebox", "collection")) outside, _ := filepath.Abs(filepath.Join("somewhere", "else")) It("returns a relative path unchanged", func() { Expect(model.LibraryRelativePath(libRoot, "_Collection")).To(Equal("_Collection")) }) It("rebases an absolute target when the library root is relative", func() { cwd, err := os.Getwd() Expect(err).ToNot(HaveOccurred()) Expect(model.LibraryRelativePath(filepath.Join("music", "library"), filepath.Join(cwd, "music", "library", "rock"))).To(Equal("rock")) }) It("rebases an absolute path that equals the library root to '.'", func() { Expect(model.LibraryRelativePath(libRoot, libRoot)).To(Equal(".")) }) It("rebases an absolute path under the library root", func() { Expect(model.LibraryRelativePath(libRoot, filepath.Join(libRoot, "_Collection"))).To(Equal("_Collection")) }) It("handles a trailing slash on the library path", func() { Expect(model.LibraryRelativePath(libRoot+string(filepath.Separator), filepath.Join(libRoot, "_Collection"))).To(Equal("_Collection")) }) It("leaves an absolute path outside the library root unchanged", func() { Expect(model.LibraryRelativePath(libRoot, outside)).To(Equal(outside)) }) It("returns an empty path unchanged", func() { Expect(model.LibraryRelativePath(libRoot, "")).To(Equal("")) }) })