package dtoimport ("encoding/hex""strconv""strings""github.com/navidrome/navidrome/model/id". "github.com/onsi/ginkgo/v2". "github.com/onsi/gomega")var _ = Describe("id codec", func() {Describe("canonical ids", func() {It("round-trips a random id", func() {ndID := id.NewRandom()decoded, ok := DecodeID(EncodeID(ndID))Expect(ok).To(BeTrue())Expect(decoded).To(Equal(ndID))})It("round-trips a hash id", func() {ndID := id.NewHash("artist", "Weird Al")decoded, ok := DecodeID(EncodeID(ndID))Expect(ok).To(BeTrue())Expect(decoded).To(Equal(ndID))})It("emits exactly 32 lowercase hex chars", func() {for range 20 {Expect(EncodeID(id.NewRandom())).To(MatchRegexp("^[0-9a-f]{32}$"))}})It("accepts a dashed GUID, matching Jellyfin's Guid.Parse", func() {ndID := id.NewRandom()guid := EncodeID(ndID)dashed := guid[0:8] + "-" + guid[8:12] + "-" + guid[12:16] + "-" + guid[16:20] + "-" + guid[20:32]decoded, ok := DecodeID(dashed)Expect(ok).To(BeTrue())Expect(decoded).To(Equal(ndID))})It("accepts an uppercase GUID", func() {ndID := id.NewRandom()decoded, ok := DecodeID(strings.ToUpper(EncodeID(ndID)))Expect(ok).To(BeTrue())Expect(decoded).To(Equal(ndID))})})Describe("reserved space", func() {DescribeTable("round-trips library ids",func(libID int, guid string) {Expect(EncodeLibraryID(libID)).To(Equal(guid))decoded, ok := DecodeID(guid)Expect(ok).To(BeTrue())Expect(decoded).To(Equal(strconv.Itoa(libID)))},Entry("first library", 1, "00000000000000000000000001000001"),Entry("double digit", 42, "0000000000000000000000000100002a"),Entry("max payload", 0xffffff, "00000000000000000000000001ffffff"),)It("round-trips the playlists folder", func() {Expect(PlaylistsFolderGUID).To(Equal("00000000000000000000000002000000"))decoded, ok := DecodeID(PlaylistsFolderGUID)Expect(ok).To(BeTrue())Expect(decoded).To(Equal(PlaylistsFolderID))})DescribeTable("round-trips playlist entry positions",func(entryID, guid string) {Expect(EncodePlaylistEntryID(entryID)).To(Equal(guid))decoded, ok := DecodePlaylistEntryID(guid)Expect(ok).To(BeTrue())Expect(decoded).To(Equal(entryID))},Entry("first entry", "1", "00000000000000000000000003000001"),Entry("later entry", "300", "0000000000000000000000000300012c"),)It("rejects a non-integer playlist entry id", func() {Expect(EncodePlaylistEntryID("s1")).To(Equal(""))Expect(EncodePlaylistEntryID("")).To(Equal(""))Expect(EncodePlaylistEntryID("-1")).To(Equal(""))})It("rejects a payload wider than the reserved 24 bits", func() {Expect(EncodeLibraryID(1 << 24)).To(Equal(""))Expect(EncodeLibraryID(-1)).To(Equal(""))Expect(EncodePlaylistEntryID("16777216")).To(Equal(""))})It("keeps library and playlist-entry GUIDs distinct for the same number", func() {Expect(EncodeLibraryID(3)).ToNot(Equal(EncodePlaylistEntryID("3")))})It("does not let one reserved kind decode as another", func() {_, ok := DecodeID(EncodePlaylistEntryID("3"))Expect(ok).To(BeFalse())_, ok = DecodePlaylistEntryID(EncodeLibraryID(3))Expect(ok).To(BeFalse())_, ok = DecodePlaylistEntryID(PlaylistsFolderGUID)Expect(ok).To(BeFalse())})It("rejects an unknown kind tag", func() {_, ok := DecodeID("000000000000000000000000ff000000")Expect(ok).To(BeFalse())})It("never emits the all-zero GUID, which Jellyfin serializes as null", func() {Expect(EncodeLibraryID(0)).ToNot(Equal("00000000000000000000000000000000"))Expect(PlaylistsFolderGUID).ToNot(Equal("00000000000000000000000000000000"))})})Describe("malformed input", func() {DescribeTable("DecodeID reports ok=false",func(input string) {decoded, ok := DecodeID(input)Expect(ok).To(BeFalse())Expect(decoded).To(BeEmpty())},Entry("empty", ""),Entry("too short", "abc123"),Entry("too long", "000000000000000000000000010000011"),Entry("non-hex chars", "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz"),Entry("a raw base62 id", id.NewRandom()),Entry("the old 44-char hex format", hex.EncodeToString([]byte("5QFKvMsJrd57QE2Le2dKKo"))),)It("encodes a non-canonical id to the empty string", func() {Expect(EncodeID("")).To(Equal(""))Expect(EncodeID("playlists")).To(Equal(""))Expect(EncodeID("42")).To(Equal(""))})})Describe("DecodeIDs", func() {It("decodes every entry when all are well-formed", func() {a, b := id.NewRandom(), id.NewRandom()decoded, ok := DecodeIDs([]string{EncodeID(a), EncodeID(b)})Expect(ok).To(BeTrue())Expect(decoded).To(Equal([]string{a, b}))})It("is all-or-nothing: one malformed entry fails the whole list", func() {decoded, ok := DecodeIDs([]string{EncodeID(id.NewRandom()), "not-a-guid"})Expect(ok).To(BeFalse())Expect(decoded).To(BeNil())})It("succeeds on an empty list", func() {decoded, ok := DecodeIDs(nil)Expect(ok).To(BeTrue())Expect(decoded).To(BeEmpty())})})})