🏡 index : quine-global/quintodrome.git

package dto

import (
	"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())
		})
	})
})