264 lines
7.9 KiB
Go
264 lines
7.9 KiB
Go
|
|
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]`))
|
||
|
|
})
|
||
|
|
})
|
||
|
|
})
|