🏡 index : quine-global/quintodrome.git

package plugins

import (
	"context"
	"path/filepath"
	"time"

	"github.com/navidrome/navidrome/core/scrobbler"
	"github.com/navidrome/navidrome/model"
	"github.com/navidrome/navidrome/tests"
	. "github.com/onsi/ginkgo/v2"
	. "github.com/onsi/gomega"
)

var _ = Describe("syncPlugins", func() {
	var ctx context.Context
	var m *Manager
	var repo *tests.MockPluginRepo
	var folder string

	BeforeEach(func() {
		ctx = GinkgoT().Context()
		folder = GinkgoT().TempDir()
		repo = tests.CreateMockPluginRepo()
		repo.SetData(model.Plugins{})
		m = &Manager{ds: &tests.MockDataStore{MockedPlugin: repo}}
	})

	writePackage := func(name string) {
		GinkgoHelper()
		manifest := &Manifest{Name: "Test Plugin", Author: "Test Author", Version: "1.0.0"}
		wasm := []byte{0x00, 0x61, 0x73, 0x6d} // Minimal wasm header
		Expect(createTestPackage(filepath.Join(folder, name), manifest, wasm)).To(Succeed())
	}

	It("registers a plugin with a usable ID", func() {
		writePackage("my-plugin" + PackageExtension)

		Expect(m.syncPlugins(context.Background(), folder)).To(Succeed())

		_, err := repo.Get(ctx, "my-plugin")
		Expect(err).ToNot(HaveOccurred())
	})

	It("skips packages whose name yields a path-like ID", func() {
		writePackage("." + PackageExtension)
		writePackage(".." + PackageExtension)

		Expect(m.syncPlugins(context.Background(), folder)).To(Succeed())

		all, err := repo.GetAll(ctx)
		Expect(err).ToNot(HaveOccurred())
		Expect(all).To(BeEmpty())
	})
})

var _ = Describe("removePluginFromDB", func() {
	var ctx context.Context

	BeforeEach(func() {
		ctx = GinkgoT().Context()
	})

	It("discards buffered scrobbles for the removed plugin", func() {
		buffer := tests.CreateMockedScrobbleBufferRepo()
		Expect(buffer.Enqueue(ctx, "my-plugin", "user1", "track1", time.Now())).To(Succeed())
		Expect(buffer.Enqueue(ctx, "other-plugin", "user1", "track2", time.Now())).To(Succeed())

		repo := tests.CreateMockPluginRepo()
		plugin := model.Plugin{ID: "my-plugin", Enabled: false}
		repo.SetData(model.Plugins{plugin})

		// No broker: sendPluginRefreshEvent is nil-safe, and testBroker is
		// defined in manager_test.go, which is excluded on Windows.
		m := &Manager{
			ds: &tests.MockDataStore{MockedScrobbleBuffer: buffer},
		}
		Expect(m.removePluginFromDB(ctx, repo, &plugin)).To(Succeed())

		_, err := repo.Get(ctx, "my-plugin")
		Expect(err).To(MatchError(model.ErrNotFound))

		remaining, err := buffer.Length(ctx)
		Expect(err).ToNot(HaveOccurred())
		Expect(remaining).To(Equal(int64(1)))
		entry, err := buffer.Next(ctx, "other-plugin", "user1")
		Expect(err).ToNot(HaveOccurred())
		Expect(entry).ToNot(BeNil(), "entries of other services must be kept")
	})

	It("keeps buffered scrobbles of a builtin scrobbler sharing the removed plugin's name", func() {
		scrobbler.Register("builtin-svc", func(model.DataStore) scrobbler.Scrobbler { return nil })
		buffer := tests.CreateMockedScrobbleBufferRepo()
		Expect(buffer.Enqueue(ctx, "builtin-svc", "user1", "track1", time.Now())).To(Succeed())

		repo := tests.CreateMockPluginRepo()
		plugin := model.Plugin{ID: "builtin-svc", Enabled: false}
		repo.SetData(model.Plugins{plugin})

		m := &Manager{
			ds: &tests.MockDataStore{MockedScrobbleBuffer: buffer},
		}
		Expect(m.removePluginFromDB(ctx, repo, &plugin)).To(Succeed())

		remaining, err := buffer.Length(ctx)
		Expect(err).ToNot(HaveOccurred())
		Expect(remaining).To(Equal(int64(1)), "builtin scrobbler queue must not be wiped")
	})
})

var _ = Describe("ComputeFileSHA256", func() {
	It("returns a consistent 64-char lowercase hex hash for the same file", func() {
		dir := GinkgoT().TempDir()
		ndpPath := filepath.Join(dir, "test.ndp")
		err := createTestPackage(ndpPath, &Manifest{Name: "S", Author: "a", Version: "1.0.0"}, []byte{0x00, 0x61, 0x73, 0x6d})
		Expect(err).ToNot(HaveOccurred())

		hash1, err := ComputeFileSHA256(ndpPath)
		Expect(err).ToNot(HaveOccurred())
		hash2, err := ComputeFileSHA256(ndpPath)
		Expect(err).ToNot(HaveOccurred())

		Expect(hash1).To(Equal(hash2))
		Expect(hash1).To(MatchRegexp(`^[0-9a-f]{64}$`))
	})

	It("returns an error for a non-existent path", func() {
		_, err := ComputeFileSHA256(filepath.Join(GinkgoT().TempDir(), "does-not-exist.ndp"))
		Expect(err).To(HaveOccurred())
	})
})