mirror of
https://github.com/navidrome/navidrome.git
synced 2026-08-31 07:30:32 +00:00
* test: unskip AbsolutePath and i18n path-separator tests on Windows (#5381) Signed-off-by: junkerderprovinz <jdp@braethoria.com> * test: unskip metadata folder-PID test on Windows via path.Dir (#5381) Signed-off-by: junkerderprovinz <jdp@braethoria.com> * test(storage): make relative-folder assertion cross-platform and unskip on Windows (#5381) Signed-off-by: junkerderprovinz <jdp@braethoria.com> * fix(persistence): normalize folder-update-info paths with forward slashes on Windows (#5381) Signed-off-by: junkerderprovinz <jdp@braethoria.com> * review: drop folder-PID change, trim storage_test comment (#5381) Revert model/metadata/persistent_ids.go to master: switching the `folder` PID attribute from filepath.Dir to path.Dir would change the persistent IDs of existing Windows libraries and needs a migration path, so it is out of scope for this PR. The matching test unskip is reverted with it, leaving #TBD-path-sep-metadata open in #5381. Trim the core/storage/storage_test.go comment to two lines. Signed-off-by: junkerderprovinz <jdp@braethoria.com> --------- Signed-off-by: junkerderprovinz <jdp@braethoria.com> Co-authored-by: Deluan Quintão <deluan@navidrome.org>
136 lines
4.4 KiB
Go
136 lines
4.4 KiB
Go
package storage
|
|
|
|
import (
|
|
"net/url"
|
|
"os"
|
|
"path/filepath"
|
|
"runtime"
|
|
"testing"
|
|
|
|
. "github.com/onsi/ginkgo/v2"
|
|
. "github.com/onsi/gomega"
|
|
)
|
|
|
|
func TestApp(t *testing.T) {
|
|
RegisterFailHandler(Fail)
|
|
RunSpecs(t, "Storage Test Suite")
|
|
}
|
|
|
|
var _ = Describe("Storage", func() {
|
|
When("schema is not registered", func() {
|
|
BeforeEach(func() {
|
|
registry = map[string]constructor{}
|
|
})
|
|
|
|
It("should return error", func() {
|
|
_, err := For("file:///tmp")
|
|
Expect(err).To(HaveOccurred())
|
|
})
|
|
})
|
|
When("schema is registered", func() {
|
|
BeforeEach(func() {
|
|
registry = map[string]constructor{}
|
|
Register("file", func(url url.URL) Storage { return &fakeLocalStorage{u: url} })
|
|
Register("s3", func(url url.URL) Storage { return &fakeS3Storage{u: url} })
|
|
})
|
|
|
|
It("should return correct implementation", func() {
|
|
s, err := For("file:///tmp")
|
|
Expect(err).ToNot(HaveOccurred())
|
|
Expect(s).To(BeAssignableToTypeOf(&fakeLocalStorage{}))
|
|
Expect(s.(*fakeLocalStorage).u.Scheme).To(Equal("file"))
|
|
Expect(s.(*fakeLocalStorage).u.Path).To(Equal("/tmp"))
|
|
|
|
s, err = For("s3:///bucket")
|
|
Expect(err).ToNot(HaveOccurred())
|
|
Expect(s).To(BeAssignableToTypeOf(&fakeS3Storage{}))
|
|
Expect(s.(*fakeS3Storage).u.Scheme).To(Equal("s3"))
|
|
Expect(s.(*fakeS3Storage).u.Path).To(Equal("/bucket"))
|
|
})
|
|
It("should return a file implementation when schema is not specified", func() {
|
|
s, err := For("/tmp")
|
|
Expect(err).ToNot(HaveOccurred())
|
|
Expect(s).To(BeAssignableToTypeOf(&fakeLocalStorage{}))
|
|
Expect(s.(*fakeLocalStorage).u.Scheme).To(Equal("file"))
|
|
Expect(s.(*fakeLocalStorage).u.Path).To(Equal("/tmp"))
|
|
})
|
|
It("should return a file implementation for a relative folder", func() {
|
|
s, err := For("tmp")
|
|
Expect(err).ToNot(HaveOccurred())
|
|
cwd, _ := os.Getwd()
|
|
Expect(s).To(BeAssignableToTypeOf(&fakeLocalStorage{}))
|
|
u := s.(*fakeLocalStorage).u
|
|
Expect(u.Scheme).To(Equal("file"))
|
|
// On Windows the drive letter lands in u.Host, so re-join it with
|
|
// u.Path (as newLocalStorage does) to keep the assertion OS-independent.
|
|
got := filepath.Join(u.Host, filepath.FromSlash(u.Path))
|
|
Expect(got).To(Equal(filepath.Join(cwd, "tmp")))
|
|
})
|
|
It("should return error if schema is unregistered", func() {
|
|
_, err := For("webdav:///tmp")
|
|
Expect(err).To(HaveOccurred())
|
|
})
|
|
DescribeTable("should handle paths with special characters correctly",
|
|
func(inputPath string) {
|
|
s, err := For(inputPath)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
Expect(s).To(BeAssignableToTypeOf(&fakeLocalStorage{}))
|
|
Expect(s.(*fakeLocalStorage).u.Scheme).To(Equal("file"))
|
|
// The path should be exactly the same as the input - after URL parsing it gets decoded back
|
|
Expect(s.(*fakeLocalStorage).u.Path).To(Equal(inputPath))
|
|
},
|
|
Entry("hash symbols", "/tmp/test#folder/file.mp3"),
|
|
Entry("spaces", "/tmp/test folder/file with spaces.mp3"),
|
|
Entry("question marks", "/tmp/test?query/file.mp3"),
|
|
Entry("ampersands", "/tmp/test&/file.mp3"),
|
|
Entry("multiple special chars", "/tmp/Song #1 & More?.mp3"),
|
|
)
|
|
})
|
|
|
|
Describe("LocalPathToURL", func() {
|
|
It("builds a file:// URL from an absolute path", func() {
|
|
u, err := LocalPathToURL("/tmp/music")
|
|
Expect(err).ToNot(HaveOccurred())
|
|
Expect(u.Scheme).To(Equal("file"))
|
|
Expect(u.Path).To(Equal("/tmp/music"))
|
|
})
|
|
|
|
It("escapes special characters and decodes them back in Path", func() {
|
|
u, err := LocalPathToURL("/tmp/Song #1 & More?.mp3")
|
|
Expect(err).ToNot(HaveOccurred())
|
|
Expect(u.Path).To(Equal("/tmp/Song #1 & More?.mp3"))
|
|
})
|
|
|
|
It("produces the same url.URL that For uses for a bare path", func() {
|
|
registry = map[string]constructor{}
|
|
Register("file", func(url url.URL) Storage { return &fakeLocalStorage{u: url} })
|
|
s, err := For("/tmp/music")
|
|
Expect(err).ToNot(HaveOccurred())
|
|
direct, err := LocalPathToURL("/tmp/music")
|
|
Expect(err).ToNot(HaveOccurred())
|
|
Expect(s.(*fakeLocalStorage).u).To(Equal(direct))
|
|
})
|
|
|
|
// On Windows, library paths are drive-lettered (e.g. C:\Music). This
|
|
// exercises the real conversion to confirm a drive-letter path yields a
|
|
// usable file:// URL on the actual platform (no-op on Unix).
|
|
It("handles a Windows drive-letter path", func() {
|
|
if runtime.GOOS != "windows" {
|
|
Skip("Windows-specific path handling")
|
|
}
|
|
u, err := LocalPathToURL(`C:\Music`)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
Expect(u.Scheme).To(Equal("file"))
|
|
})
|
|
})
|
|
})
|
|
|
|
type fakeLocalStorage struct {
|
|
Storage
|
|
u url.URL
|
|
}
|
|
type fakeS3Storage struct {
|
|
Storage
|
|
u url.URL
|
|
}
|