🏡 index : ironmagma/quintodrome.git

package persistence

import (
	"github.com/navidrome/navidrome/model"
	"github.com/navidrome/navidrome/model/request"
	. "github.com/onsi/ginkgo/v2"
	. "github.com/onsi/gomega"
)

var _ = Describe("Plugin Cleanup", func() {
	var pluginRepo model.PluginRepository
	var userRepo model.UserRepository
	var libraryRepo model.LibraryRepository

	BeforeEach(func() {
		ctx := GinkgoT().Context()
		ctx = request.WithUser(ctx, model.User{ID: "admin", UserName: "admin", IsAdmin: true})
		db := GetDBXBuilder()
		pluginRepo = NewPluginRepository(ctx, db)
		userRepo = NewUserRepository(ctx, db)
		libraryRepo = NewLibraryRepository(ctx, db)

		// Clean up any existing plugins
		all, _ := pluginRepo.GetAll()
		for _, p := range all {
			_ = pluginRepo.Delete(p.ID)
		}
	})

	AfterEach(func() {
		// Clean up after tests
		all, _ := pluginRepo.GetAll()
		for _, p := range all {
			_ = pluginRepo.Delete(p.ID)
		}
	})

	Describe("cleanupPluginUserReferences", func() {
		It("removes user ID from plugin users array", func() {
			// Create a plugin with multiple users
			plugin := &model.Plugin{
				ID:       "test-plugin",
				Path:     "/plugins/test.wasm",
				Manifest: `{"name":"test"}`,
				SHA256:   "abc123",
				Users:    `["user1","user2","user3"]`,
				Enabled:  true,
			}
			Expect(pluginRepo.Put(plugin)).To(Succeed())

			// Clean up user2 reference
			db := GetDBXBuilder()
			Expect(cleanupPluginUserReferences(db, "user2")).To(Succeed())

			// Verify user2 was removed
			updated, err := pluginRepo.Get("test-plugin")
			Expect(err).ToNot(HaveOccurred())
			Expect(updated.Users).To(Equal(`["user1","user3"]`))
			Expect(updated.Enabled).To(BeTrue()) // Still has users, should remain enabled
		})

		It("auto-disables plugin when last permitted user is removed", func() {
			// Create a plugin that requires users permission with only one user
			plugin := &model.Plugin{
				ID:       "user-plugin",
				Path:     "/plugins/user.wasm",
				Manifest: `{"name":"user-plugin","permissions":{"users":{}}}`,
				SHA256:   "def456",
				Users:    `["only-user"]`,
				AllUsers: false,
				Enabled:  true,
			}
			Expect(pluginRepo.Put(plugin)).To(Succeed())

			// Remove the only user
			db := GetDBXBuilder()
			Expect(cleanupPluginUserReferences(db, "only-user")).To(Succeed())

			// Verify plugin was auto-disabled
			updated, err := pluginRepo.Get("user-plugin")
			Expect(err).ToNot(HaveOccurred())
			Expect(updated.Users).To(Equal(`[]`))
			Expect(updated.Enabled).To(BeFalse())
		})

		It("does not disable plugin when allUsers is true", func() {
			plugin := &model.Plugin{
				ID:       "all-users-plugin",
				Path:     "/plugins/all.wasm",
				Manifest: `{"name":"all-users","permissions":{"users":{}}}`,
				SHA256:   "ghi789",
				Users:    `["user1"]`,
				AllUsers: true,
				Enabled:  true,
			}
			Expect(pluginRepo.Put(plugin)).To(Succeed())

			// Remove the user (but allUsers is true)
			db := GetDBXBuilder()
			Expect(cleanupPluginUserReferences(db, "user1")).To(Succeed())

			// Plugin should still be enabled because allUsers is true
			updated, err := pluginRepo.Get("all-users-plugin")
			Expect(err).ToNot(HaveOccurred())
			Expect(updated.Enabled).To(BeTrue())
		})

		It("does not affect plugins without users permission requirement", func() {
			plugin := &model.Plugin{
				ID:       "no-users-perm",
				Path:     "/plugins/noperm.wasm",
				Manifest: `{"name":"no-perm"}`, // No permissions.users in manifest
				SHA256:   "jkl012",
				Users:    `["user1"]`,
				Enabled:  true,
			}
			Expect(pluginRepo.Put(plugin)).To(Succeed())

			// Remove the user
			db := GetDBXBuilder()
			Expect(cleanupPluginUserReferences(db, "user1")).To(Succeed())

			// Plugin should still be enabled (no users permission requirement)
			updated, err := pluginRepo.Get("no-users-perm")
			Expect(err).ToNot(HaveOccurred())
			Expect(updated.Users).To(Equal(`[]`))
			Expect(updated.Enabled).To(BeTrue())
		})
	})

	Describe("cleanupPluginLibraryReferences", func() {
		It("removes library ID from plugin libraries array", func() {
			// Create a plugin with multiple libraries
			plugin := &model.Plugin{
				ID:        "lib-plugin",
				Path:      "/plugins/lib.wasm",
				Manifest:  `{"name":"lib-plugin"}`,
				SHA256:    "mno345",
				Libraries: `[1,2,3]`,
				Enabled:   true,
			}
			Expect(pluginRepo.Put(plugin)).To(Succeed())

			// Clean up library 2 reference
			db := GetDBXBuilder()
			Expect(cleanupPluginLibraryReferences(db, 2)).To(Succeed())

			// Verify library 2 was removed
			updated, err := pluginRepo.Get("lib-plugin")
			Expect(err).ToNot(HaveOccurred())
			Expect(updated.Libraries).To(Equal(`[1,3]`))
		})

		It("auto-disables plugin when last permitted library is removed", func() {
			// Create a plugin that requires library permission with only one library
			plugin := &model.Plugin{
				ID:           "lib-only-plugin",
				Path:         "/plugins/libonly.wasm",
				Manifest:     `{"name":"lib-only","permissions":{"library":{}}}`,
				SHA256:       "pqr678",
				Libraries:    `[99]`,
				AllLibraries: false,
				Enabled:      true,
			}
			Expect(pluginRepo.Put(plugin)).To(Succeed())

			// Remove the only library
			db := GetDBXBuilder()
			Expect(cleanupPluginLibraryReferences(db, 99)).To(Succeed())

			// Verify plugin was auto-disabled
			updated, err := pluginRepo.Get("lib-only-plugin")
			Expect(err).ToNot(HaveOccurred())
			Expect(updated.Libraries).To(Equal(`[]`))
			Expect(updated.Enabled).To(BeFalse())
		})

		It("does not disable plugin when allLibraries is true", func() {
			plugin := &model.Plugin{
				ID:           "all-libs-plugin",
				Path:         "/plugins/alllibs.wasm",
				Manifest:     `{"name":"all-libs","permissions":{"library":{}}}`,
				SHA256:       "stu901",
				Libraries:    `[1]`,
				AllLibraries: true,
				Enabled:      true,
			}
			Expect(pluginRepo.Put(plugin)).To(Succeed())

			// Remove the library (but allLibraries is true)
			db := GetDBXBuilder()
			Expect(cleanupPluginLibraryReferences(db, 1)).To(Succeed())

			// Plugin should still be enabled
			updated, err := pluginRepo.Get("all-libs-plugin")
			Expect(err).ToNot(HaveOccurred())
			Expect(updated.Enabled).To(BeTrue())
		})
	})

	Describe("User Delete integration", func() {
		It("cleans up plugin references when user is deleted", func() {
			// Create a test user
			user := &model.User{
				ID:       "test-delete-user",
				UserName: "plugin-cleanup-test-user",
				IsAdmin:  false,
			}
			user.NewPassword = "password123"
			Expect(userRepo.Put(user)).To(Succeed())

			// Create a plugin referencing this user
			plugin := &model.Plugin{
				ID:       "user-ref-plugin",
				Path:     "/plugins/userref.wasm",
				Manifest: `{"name":"user-ref"}`,
				SHA256:   "xyz123",
				Users:    `["test-delete-user","other-user"]`,
				Enabled:  true,
			}
			Expect(pluginRepo.Put(plugin)).To(Succeed())

			// Delete the user
			Expect(userRepo.Delete("test-delete-user")).To(Succeed())

			// Verify user was removed from plugin
			updated, err := pluginRepo.Get("user-ref-plugin")
			Expect(err).ToNot(HaveOccurred())
			Expect(updated.Users).To(Equal(`["other-user"]`))
		})
	})

	Describe("Library Delete integration", func() {
		It("cleans up plugin references when library is deleted", func() {
			// Create a test library (ID > 1 since ID 1 cannot be deleted)
			library := &model.Library{
				ID:   99,
				Name: "Test Library",
				Path: "/tmp/test-lib",
			}
			Expect(libraryRepo.Put(library)).To(Succeed())

			// Create a plugin referencing this library
			plugin := &model.Plugin{
				ID:        "lib-ref-plugin",
				Path:      "/plugins/libref.wasm",
				Manifest:  `{"name":"lib-ref"}`,
				SHA256:    "abc789",
				Libraries: `[99,1]`,
				Enabled:   true,
			}
			Expect(pluginRepo.Put(plugin)).To(Succeed())

			// Delete the library
			Expect(libraryRepo.Delete(99)).To(Succeed())

			// Verify library was removed from plugin
			updated, err := pluginRepo.Get("lib-ref-plugin")
			Expect(err).ToNot(HaveOccurred())
			Expect(updated.Libraries).To(Equal(`[1]`))
		})
	})
})