From f0f191266cb0a3e86d1e0c86f44fb2db7cb58b99 Mon Sep 17 00:00:00 2001 From: Deluan Date: Mon, 22 Dec 2025 12:40:38 -0500 Subject: [PATCH] tests(plugins): optimize tests Signed-off-by: Deluan --- plugins/manager_test.go | 109 ++++-------- plugins/metadata_agent_test.go | 8 +- plugins/plugins_suite_test.go | 21 +++ plugins/testdata/Makefile | 11 ++ .../go.mod | 0 .../go.sum | 0 .../main.go | 0 plugins/watcher.go | 74 +++++--- plugins/watcher_integration_test.go | 125 +++++++++++++ plugins/watcher_test.go | 165 +++--------------- 10 files changed, 268 insertions(+), 245 deletions(-) create mode 100644 plugins/testdata/Makefile rename plugins/testdata/{testplugin => fake-metadata-agent}/go.mod (100%) rename plugins/testdata/{testplugin => fake-metadata-agent}/go.sum (100%) rename plugins/testdata/{testplugin => fake-metadata-agent}/main.go (100%) create mode 100644 plugins/watcher_integration_test.go diff --git a/plugins/manager_test.go b/plugins/manager_test.go index b7aba6206..ab7f83dcd 100644 --- a/plugins/manager_test.go +++ b/plugins/manager_test.go @@ -3,7 +3,6 @@ package plugins import ( "context" "fmt" - "os" "path/filepath" "runtime" "sync" @@ -15,65 +14,43 @@ import ( . "github.com/onsi/gomega" ) -var _ = Describe("Manager", func() { +var _ = Describe("Manager", Ordered, func() { var ( manager *Manager ctx context.Context testdataDir string - tmpDir string ) - BeforeEach(func() { - ctx = GinkgoT().Context() + BeforeAll(func() { + ctx = context.Background() - // Get testdata directory + // Get testdata directory (where fake-metadata-agent.wasm lives) _, currentFile, _, ok := runtime.Caller(0) Expect(ok).To(BeTrue()) testdataDir = filepath.Join(filepath.Dir(currentFile), "testdata") - // Create temp dir for plugins - var err error - tmpDir, err = os.MkdirTemp("", "plugins-test-*") - Expect(err).ToNot(HaveOccurred()) - // Setup config DeferCleanup(configtest.SetupConfig()) conf.Server.Plugins.Enabled = true - conf.Server.Plugins.Folder = tmpDir - conf.Server.CacheFolder = filepath.Join(tmpDir, "cache") + conf.Server.Plugins.Folder = testdataDir - // Create a fresh manager for each test + // Create manager once for all tests manager = &Manager{ plugins: make(map[string]*pluginInstance), } - err = manager.Start(ctx) + err := manager.Start(ctx) Expect(err).ToNot(HaveOccurred()) DeferCleanup(func() { _ = manager.Stop() - _ = os.RemoveAll(tmpDir) }) }) - copyTestPlugin := func(destName string) string { - srcPath := filepath.Join(testdataDir, "test-plugin.wasm") - destPath := filepath.Join(tmpDir, destName+".wasm") - data, err := os.ReadFile(srcPath) - Expect(err).ToNot(HaveOccurred()) - err = os.WriteFile(destPath, data, 0600) - Expect(err).ToNot(HaveOccurred()) - return destPath - } - Describe("LoadPlugin", func() { - It("loads a new plugin by name", func() { - copyTestPlugin("new-plugin") - - err := manager.LoadPlugin("new-plugin") - Expect(err).ToNot(HaveOccurred()) - + It("auto-loads plugins from folder on Start", func() { + // Plugin is already loaded by manager.Start() via discoverPlugins names := manager.PluginNames(string(CapabilityMetadataAgent)) - Expect(names).To(ContainElement("new-plugin")) + Expect(names).To(ContainElement("fake-metadata-agent")) }) It("returns error when plugin file does not exist", func() { @@ -83,19 +60,21 @@ var _ = Describe("Manager", func() { }) It("returns error when plugin is already loaded", func() { - copyTestPlugin("duplicate") - - err := manager.LoadPlugin("duplicate") - Expect(err).ToNot(HaveOccurred()) - - err = manager.LoadPlugin("duplicate") + // Plugin was loaded on Start, try to load again + err := manager.LoadPlugin("fake-metadata-agent") Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("already loaded")) }) It("returns error when plugins folder is not configured", func() { + originalFolder := conf.Server.Plugins.Folder + originalDataFolder := conf.Server.DataFolder conf.Server.Plugins.Folder = "" conf.Server.DataFolder = "" + defer func() { + conf.Server.Plugins.Folder = originalFolder + conf.Server.DataFolder = originalDataFolder + }() err := manager.LoadPlugin("test") Expect(err).To(HaveOccurred()) @@ -105,15 +84,21 @@ var _ = Describe("Manager", func() { Describe("UnloadPlugin", func() { It("removes a loaded plugin", func() { - copyTestPlugin("to-unload") - err := manager.LoadPlugin("to-unload") - Expect(err).ToNot(HaveOccurred()) - - err = manager.UnloadPlugin("to-unload") + // Plugin is already loaded from Start + err := manager.UnloadPlugin("fake-metadata-agent") Expect(err).ToNot(HaveOccurred()) names := manager.PluginNames(string(CapabilityMetadataAgent)) - Expect(names).ToNot(ContainElement("to-unload")) + Expect(names).ToNot(ContainElement("fake-metadata-agent")) + }) + + It("can reload after unload", func() { + // Reload the plugin we just unloaded + err := manager.LoadPlugin("fake-metadata-agent") + Expect(err).ToNot(HaveOccurred()) + + names := manager.PluginNames(string(CapabilityMetadataAgent)) + Expect(names).To(ContainElement("fake-metadata-agent")) }) It("returns error when plugin not found", func() { @@ -125,15 +110,11 @@ var _ = Describe("Manager", func() { Describe("ReloadPlugin", func() { It("unloads and reloads a plugin", func() { - copyTestPlugin("to-reload") - err := manager.LoadPlugin("to-reload") - Expect(err).ToNot(HaveOccurred()) - - err = manager.ReloadPlugin("to-reload") + err := manager.ReloadPlugin("fake-metadata-agent") Expect(err).ToNot(HaveOccurred()) names := manager.PluginNames(string(CapabilityMetadataAgent)) - Expect(names).To(ContainElement("to-reload")) + Expect(names).To(ContainElement("fake-metadata-agent")) }) It("returns error when plugin not found", func() { @@ -141,31 +122,10 @@ var _ = Describe("Manager", func() { Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("failed to unload")) }) - - It("keeps plugin unloaded if reload fails", func() { - copyTestPlugin("fail-reload") - err := manager.LoadPlugin("fail-reload") - Expect(err).ToNot(HaveOccurred()) - - // Remove the wasm file so reload will fail - wasmPath := filepath.Join(tmpDir, "fail-reload.wasm") - err = os.Remove(wasmPath) - Expect(err).ToNot(HaveOccurred()) - - err = manager.ReloadPlugin("fail-reload") - Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring("failed to reload")) - - // Plugin should no longer be loaded - names := manager.PluginNames(string(CapabilityMetadataAgent)) - Expect(names).ToNot(ContainElement("fail-reload")) - }) }) It("can call the plugin concurrently", func() { - copyTestPlugin("new-plugin") - err := manager.LoadPlugin("new-plugin") - Expect(err).ToNot(HaveOccurred()) + // Plugin is already loaded const concurrency = 100 errs := make(chan error, concurrency) @@ -176,7 +136,7 @@ var _ = Describe("Manager", func() { for i := range concurrency { go func(i int) { defer g.Done() - a, ok := manager.LoadMediaAgent("new-plugin") + a, ok := manager.LoadMediaAgent("fake-metadata-agent") Expect(ok).To(BeTrue()) agent := a.(agents.ArtistBiographyRetriever) bio, err := agent.GetArtistBiography(ctx, fmt.Sprintf("artist-%d", i), fmt.Sprintf("Artist %d", i), "") @@ -199,5 +159,4 @@ var _ = Describe("Manager", func() { } } }) - }) diff --git a/plugins/metadata_agent_test.go b/plugins/metadata_agent_test.go index 21c980297..aeae47e63 100644 --- a/plugins/metadata_agent_test.go +++ b/plugins/metadata_agent_test.go @@ -36,8 +36,8 @@ var _ = Describe("MetadataAgent", Ordered, func() { Expect(err).ToNot(HaveOccurred()) // Copy test plugin to temp dir - srcPath := filepath.Join(testdataDir, "test-plugin.wasm") - destPath := filepath.Join(tmpDir, "test-plugin.wasm") + srcPath := filepath.Join(testdataDir, "fake-metadata-agent.wasm") + destPath := filepath.Join(tmpDir, "fake-metadata-agent.wasm") data, err := os.ReadFile(srcPath) Expect(err).ToNot(HaveOccurred()) err = os.WriteFile(destPath, data, 0600) @@ -58,7 +58,7 @@ var _ = Describe("MetadataAgent", Ordered, func() { // Load the agent via manager var ok2 bool - agent, ok2 = manager.LoadMediaAgent("test-plugin") + agent, ok2 = manager.LoadMediaAgent("fake-metadata-agent") Expect(ok2).To(BeTrue()) DeferCleanup(func() { @@ -69,7 +69,7 @@ var _ = Describe("MetadataAgent", Ordered, func() { Describe("AgentName", func() { It("returns the plugin name", func() { - Expect(agent.AgentName()).To(Equal("test-plugin")) + Expect(agent.AgentName()).To(Equal("fake-metadata-agent")) }) }) diff --git a/plugins/plugins_suite_test.go b/plugins/plugins_suite_test.go index 716630699..6533ff713 100644 --- a/plugins/plugins_suite_test.go +++ b/plugins/plugins_suite_test.go @@ -1,13 +1,34 @@ +//go:build !windows + package plugins import ( + "os/exec" "testing" + "github.com/navidrome/navidrome/log" + "github.com/navidrome/navidrome/tests" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) +const testDataDir = "plugins/testdata" + func TestPlugins(t *testing.T) { + tests.Init(t, false) + buildTestPlugins(t, testDataDir) + log.SetLevel(log.LevelFatal) RegisterFailHandler(Fail) RunSpecs(t, "Plugins Suite") } + +func buildTestPlugins(t *testing.T, path string) { + t.Helper() + t.Logf("[BeforeSuite] Current working directory: %s", path) + cmd := exec.Command("make", "-C", path) + out, err := cmd.CombinedOutput() + t.Logf("[BeforeSuite] Make output: %s", string(out)) + if err != nil { + t.Fatalf("Failed to build test plugins: %v", err) + } +} diff --git a/plugins/testdata/Makefile b/plugins/testdata/Makefile new file mode 100644 index 000000000..ecc7139b8 --- /dev/null +++ b/plugins/testdata/Makefile @@ -0,0 +1,11 @@ +# Build fake sample plugins used for testing +# Auto-discover all plugin folders (folders containing go.mod) +PLUGINS := $(patsubst %/go.mod,%,$(wildcard */go.mod)) + +all: $(PLUGINS:%=%.wasm) + +clean: + rm -f $(PLUGINS:%=%.wasm) + +%.wasm: %/main.go %/go.mod + cd $* && GOOS=wasip1 GOARCH=wasm go build -buildmode=c-shared -o ../$@ . \ No newline at end of file diff --git a/plugins/testdata/testplugin/go.mod b/plugins/testdata/fake-metadata-agent/go.mod similarity index 100% rename from plugins/testdata/testplugin/go.mod rename to plugins/testdata/fake-metadata-agent/go.mod diff --git a/plugins/testdata/testplugin/go.sum b/plugins/testdata/fake-metadata-agent/go.sum similarity index 100% rename from plugins/testdata/testplugin/go.sum rename to plugins/testdata/fake-metadata-agent/go.sum diff --git a/plugins/testdata/testplugin/main.go b/plugins/testdata/fake-metadata-agent/main.go similarity index 100% rename from plugins/testdata/testplugin/main.go rename to plugins/testdata/fake-metadata-agent/main.go diff --git a/plugins/watcher.go b/plugins/watcher.go index eef3b6901..7225d1bd3 100644 --- a/plugins/watcher.go +++ b/plugins/watcher.go @@ -106,6 +106,38 @@ func (m *Manager) handleWatcherEvent(event notify.EventInfo) { m.debounceMu.Unlock() } +// pluginAction represents the action to take on a plugin based on a file event +type pluginAction int + +const ( + actionNone pluginAction = iota // No action needed + actionLoad // Load the plugin + actionUnload // Unload the plugin + actionReload // Reload the plugin +) + +// determinePluginAction decides what action to take based on the file event type +// and whether the plugin is currently loaded. This is a pure function with no side effects. +func determinePluginAction(eventType notify.Event, isLoaded bool) pluginAction { + switch { + case eventType¬ify.Remove != 0 || eventType¬ify.Rename != 0: + // File removed or renamed away - unload if loaded + return actionUnload + + case eventType¬ify.Create != 0: + // New file - load it + return actionLoad + + case eventType¬ify.Write != 0: + // File modified - reload if loaded, otherwise load + if isLoaded { + return actionReload + } + return actionLoad + } + return actionNone +} + // processPluginEvent handles the actual plugin load/unload/reload after debouncing func (m *Manager) processPluginEvent(pluginName string, eventType notify.Event) { // Don't process if manager is stopping/stopped (atomic check to avoid race with Stop()) @@ -118,35 +150,25 @@ func (m *Manager) processPluginEvent(pluginName string, eventType notify.Event) delete(m.debounceTimers, pluginName) m.debounceMu.Unlock() - switch { - case eventType¬ify.Remove != 0 || eventType¬ify.Rename != 0: - // File removed or renamed away - unload if loaded + // Check if plugin is currently loaded + m.mu.RLock() + _, isLoaded := m.plugins[pluginName] + m.mu.RUnlock() + + // Determine and execute the appropriate action + action := determinePluginAction(eventType, isLoaded) + switch action { + case actionLoad: + if err := m.LoadPlugin(pluginName); err != nil { + log.Error(m.ctx, "Failed to load plugin", "plugin", pluginName, err) + } + case actionUnload: if err := m.UnloadPlugin(pluginName); err != nil { - // Plugin may not have been loaded, that's okay log.Debug(m.ctx, "Plugin not loaded, skipping unload", "plugin", pluginName, err) } - - case eventType¬ify.Create != 0: - // New file - load it - if err := m.LoadPlugin(pluginName); err != nil { - log.Error(m.ctx, "Failed to load new plugin", "plugin", pluginName, err) - } - - case eventType¬ify.Write != 0: - // File modified - check if it's loaded and reload - m.mu.RLock() - _, isLoaded := m.plugins[pluginName] - m.mu.RUnlock() - - if isLoaded { - if err := m.ReloadPlugin(pluginName); err != nil { - log.Error(m.ctx, "Failed to reload plugin", "plugin", pluginName, err) - } - } else { - // Not loaded yet, try to load it (might be a new file that was written after create) - if err := m.LoadPlugin(pluginName); err != nil { - log.Error(m.ctx, "Failed to load plugin", "plugin", pluginName, err) - } + case actionReload: + if err := m.ReloadPlugin(pluginName); err != nil { + log.Error(m.ctx, "Failed to reload plugin", "plugin", pluginName, err) } } } diff --git a/plugins/watcher_integration_test.go b/plugins/watcher_integration_test.go new file mode 100644 index 000000000..1f5d9c486 --- /dev/null +++ b/plugins/watcher_integration_test.go @@ -0,0 +1,125 @@ +package plugins + +import ( + "context" + "os" + "path/filepath" + "runtime" + "testing" + + "github.com/navidrome/navidrome/conf" + "github.com/navidrome/navidrome/conf/configtest" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + "github.com/rjeczalik/notify" +) + +var _ = Describe("Watcher Integration", Ordered, func() { + var ( + manager *Manager + ctx context.Context + testdataDir string + tmpDir string + ) + + BeforeAll(func() { + if testing.Short() { + Skip("Skipping integration test in short mode") + } + + ctx = GinkgoT().Context() + + // Get testdata directory + _, currentFile, _, ok := runtime.Caller(0) + Expect(ok).To(BeTrue()) + testdataDir = filepath.Join(filepath.Dir(currentFile), "testdata") + + // Create temp dir for plugins + var err error + tmpDir, err = os.MkdirTemp("", "plugins-watcher-integration-*") + Expect(err).ToNot(HaveOccurred()) + + // Setup config (AutoReload disabled - tests inject events directly) + DeferCleanup(configtest.SetupConfig()) + conf.Server.Plugins.Enabled = true + conf.Server.Plugins.Folder = tmpDir + conf.Server.Plugins.AutoReload = false + + // Create a fresh manager for each test + manager = &Manager{ + plugins: make(map[string]*pluginInstance), + } + err = manager.Start(ctx) + Expect(err).ToNot(HaveOccurred()) + + DeferCleanup(func() { + _ = manager.Stop() + _ = os.RemoveAll(tmpDir) + }) + }) + + // Helper to copy test plugin into the temp folder + copyTestPlugin := func() { + srcPath := filepath.Join(testdataDir, "fake-metadata-agent.wasm") + destPath := filepath.Join(tmpDir, "fake-metadata-agent.wasm") + data, err := os.ReadFile(srcPath) + Expect(err).ToNot(HaveOccurred()) + err = os.WriteFile(destPath, data, 0600) + Expect(err).ToNot(HaveOccurred()) + } + + Describe("Plugin event processing (integration)", func() { + // These tests verify the full flow with actual WASM plugin loading. + + AfterEach(func() { + // Clean up: unload plugin if loaded, remove copied file + _ = manager.UnloadPlugin("fake-metadata-agent") + _ = os.Remove(filepath.Join(tmpDir, "fake-metadata-agent.wasm")) + }) + + It("loads a plugin on CREATE event", func() { + copyTestPlugin() + manager.processPluginEvent("fake-metadata-agent", notify.Create) + Expect(manager.PluginNames(string(CapabilityMetadataAgent))).To(ContainElement("fake-metadata-agent")) + }) + + It("reloads a plugin on WRITE event", func() { + copyTestPlugin() + err := manager.LoadPlugin("fake-metadata-agent") + Expect(err).ToNot(HaveOccurred()) + + manager.processPluginEvent("fake-metadata-agent", notify.Write) + Expect(manager.PluginNames(string(CapabilityMetadataAgent))).To(ContainElement("fake-metadata-agent")) + }) + + It("unloads a plugin on REMOVE event", func() { + copyTestPlugin() + err := manager.LoadPlugin("fake-metadata-agent") + Expect(err).ToNot(HaveOccurred()) + + manager.processPluginEvent("fake-metadata-agent", notify.Remove) + Expect(manager.PluginNames(string(CapabilityMetadataAgent))).ToNot(ContainElement("fake-metadata-agent")) + }) + }) + + Describe("Watcher lifecycle", func() { + It("does not start file watcher when AutoReload is disabled", func() { + Expect(manager.watcherEvents).To(BeNil()) + Expect(manager.watcherDone).To(BeNil()) + }) + + It("starts file watcher when AutoReload is enabled", func() { + _ = manager.Stop() + + conf.Server.Plugins.AutoReload = true + manager = &Manager{ + plugins: make(map[string]*pluginInstance), + } + err := manager.Start(ctx) + Expect(err).ToNot(HaveOccurred()) + + Expect(manager.watcherEvents).ToNot(BeNil()) + Expect(manager.watcherDone).ToNot(BeNil()) + }) + }) +}) diff --git a/plugins/watcher_test.go b/plugins/watcher_test.go index 3fe179ebd..8d05940ed 100644 --- a/plugins/watcher_test.go +++ b/plugins/watcher_test.go @@ -1,156 +1,41 @@ package plugins import ( - "context" - "os" - "path/filepath" - "runtime" - "time" - - "github.com/navidrome/navidrome/conf" - "github.com/navidrome/navidrome/conf/configtest" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + "github.com/rjeczalik/notify" ) var _ = Describe("Watcher", func() { - var ( - manager *Manager - ctx context.Context - testdataDir string - tmpDir string - ) + Describe("determinePluginAction", func() { + // These are fast unit tests for the pure routing logic. + // No WASM compilation, no file I/O - runs in microseconds. - BeforeEach(func() { - ctx = GinkgoT().Context() + DescribeTable("returns correct action for event type and loaded state", + func(eventType notify.Event, isLoaded bool, expected pluginAction) { + Expect(determinePluginAction(eventType, isLoaded)).To(Equal(expected)) + }, + // CREATE events - always load + Entry("CREATE when not loaded", notify.Create, false, actionLoad), + Entry("CREATE when loaded", notify.Create, true, actionLoad), - // Use shorter debounce for faster tests - originalDebounce := debounceDuration - debounceDuration = 50 * time.Millisecond - DeferCleanup(func() { debounceDuration = originalDebounce }) + // WRITE events - reload if loaded, load if not + Entry("WRITE when not loaded", notify.Write, false, actionLoad), + Entry("WRITE when loaded", notify.Write, true, actionReload), - // Get testdata directory - _, currentFile, _, ok := runtime.Caller(0) - Expect(ok).To(BeTrue()) - testdataDir = filepath.Join(filepath.Dir(currentFile), "testdata") + // REMOVE events - always unload + Entry("REMOVE when not loaded", notify.Remove, false, actionUnload), + Entry("REMOVE when loaded", notify.Remove, true, actionUnload), - // Create temp dir for plugins - var err error - tmpDir, err = os.MkdirTemp("", "plugins-watcher-test-*") - Expect(err).ToNot(HaveOccurred()) + // RENAME events - treated same as REMOVE + Entry("RENAME when not loaded", notify.Rename, false, actionUnload), + Entry("RENAME when loaded", notify.Rename, true, actionUnload), + ) - // Setup config with AutoReload enabled - DeferCleanup(configtest.SetupConfig()) - conf.Server.Plugins.Enabled = true - conf.Server.Plugins.Folder = tmpDir - conf.Server.Plugins.AutoReload = true - conf.Server.CacheFolder = filepath.Join(tmpDir, "cache") - - // Create a fresh manager for each test - manager = &Manager{ - plugins: make(map[string]*pluginInstance), - } - err = manager.Start(ctx) - Expect(err).ToNot(HaveOccurred()) - - DeferCleanup(func() { - _ = manager.Stop() - _ = os.RemoveAll(tmpDir) - }) - }) - - copyTestPlugin := func(destName string) string { - srcPath := filepath.Join(testdataDir, "test-plugin.wasm") - destPath := filepath.Join(tmpDir, destName+".wasm") - data, err := os.ReadFile(srcPath) - Expect(err).ToNot(HaveOccurred()) - err = os.WriteFile(destPath, data, 0600) - Expect(err).ToNot(HaveOccurred()) - return destPath - } - - Describe("Auto-reload via file watcher", func() { - It("loads a plugin when a new wasm file is created", func() { - // Copy plugin file to trigger CREATE event - copyTestPlugin("watch-create") - - // Wait for debounce + processing - Eventually(func() []string { - return manager.PluginNames(string(CapabilityMetadataAgent)) - }, 1*time.Second, 50*time.Millisecond).Should(ContainElement("watch-create")) - }) - - It("reloads a plugin when the wasm file is modified", func() { - // First, load a plugin - copyTestPlugin("watch-modify") - - // Wait for it to be loaded - Eventually(func() []string { - return manager.PluginNames(string(CapabilityMetadataAgent)) - }, 1*time.Second, 50*time.Millisecond).Should(ContainElement("watch-modify")) - - // Get the original plugin info - originalInfo := manager.GetPluginInfo()["watch-modify"] - Expect(originalInfo.Name).ToNot(BeEmpty()) - - // Modify the file (re-copy to trigger WRITE event) - wasmPath := filepath.Join(tmpDir, "watch-modify.wasm") - data, err := os.ReadFile(wasmPath) - Expect(err).ToNot(HaveOccurred()) - - // Touch the file to trigger write event - err = os.WriteFile(wasmPath, data, 0600) - Expect(err).ToNot(HaveOccurred()) - - // Wait for reload - the plugin should still be there - // We can't easily verify it was reloaded without adding tracking, - // but at least verify it's still loaded - Consistently(func() []string { - return manager.PluginNames(string(CapabilityMetadataAgent)) - }, 300*time.Millisecond, 50*time.Millisecond).Should(ContainElement("watch-modify")) - }) - - It("unloads a plugin when the wasm file is removed", func() { - // First, load a plugin - wasmPath := copyTestPlugin("watch-remove") - - // Wait for it to be loaded - Eventually(func() []string { - return manager.PluginNames(string(CapabilityMetadataAgent)) - }, 1*time.Second, 50*time.Millisecond).Should(ContainElement("watch-remove")) - - // Remove the file - err := os.Remove(wasmPath) - Expect(err).ToNot(HaveOccurred()) - - // Wait for it to be unloaded - Eventually(func() []string { - return manager.PluginNames(string(CapabilityMetadataAgent)) - }, 1*time.Second, 50*time.Millisecond).ShouldNot(ContainElement("watch-remove")) - }) - }) - - Describe("Watcher disabled", func() { - BeforeEach(func() { - // Stop existing manager and create one without auto-reload - _ = manager.Stop() - - conf.Server.Plugins.AutoReload = false - manager = &Manager{ - plugins: make(map[string]*pluginInstance), - } - err := manager.Start(ctx) - Expect(err).ToNot(HaveOccurred()) - }) - - It("does not auto-load plugins when AutoReload is disabled", func() { - // Copy plugin file - copyTestPlugin("no-watch") - - // Wait a bit and verify plugin is NOT loaded - Consistently(func() []string { - return manager.PluginNames(string(CapabilityMetadataAgent)) - }, 300*time.Millisecond, 50*time.Millisecond).ShouldNot(ContainElement("no-watch")) + It("returns actionNone for unknown event types", func() { + // Event type 0 or other unknown values + Expect(determinePluginAction(0, false)).To(Equal(actionNone)) + Expect(determinePluginAction(0, true)).To(Equal(actionNone)) }) }) })