From 30df004d4d8916a3d11c14a188c03585d0f28233 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Deluan=20Quint=C3=A3o?= Date: Mon, 2 Mar 2026 16:18:30 -0500 Subject: [PATCH] test(plugins): speed up integration tests (~45% improvement) (#5137) * test(plugins): speed up integration tests with shared wazero cache Reduce plugin test suite runtime from ~22s to ~12s by: - Creating a shared wazero compilation cache directory in TestPlugins() and setting conf.Server.CacheFolder globally so all test Manager instances reuse compiled WASM binaries from disk cache - Moving 6 createTestManager* calls from inside It blocks to BeforeAll blocks in scrobbler_adapter_test.go and manager_call_test.go - Replacing time.Sleep(2s) in KVStore TTL test with Eventually polling - Reducing WebSocket callback sleeps from 100ms to 10ms Signed-off-by: Deluan * test(plugins): enhance websocket tests by storing server messages for verification Signed-off-by: Deluan --------- Signed-off-by: Deluan --- plugins/host_artwork_test.go | 1 - plugins/host_cache_test.go | 1 - plugins/host_config_test.go | 1 - plugins/host_kvstore_test.go | 20 ++-- plugins/host_library_test.go | 2 - plugins/host_scheduler_test.go | 1 - plugins/host_subsonicapi_test.go | 1 - plugins/host_users_test.go | 1 - plugins/host_websocket_test.go | 52 ++++------- plugins/manager_call_test.go | 84 ++++++++++------- plugins/plugins_suite_test.go | 15 ++- plugins/scrobbler_adapter_test.go | 116 +++++++++++++++--------- plugins/testdata/test-websocket/main.go | 5 +- 13 files changed, 165 insertions(+), 135 deletions(-) diff --git a/plugins/host_artwork_test.go b/plugins/host_artwork_test.go index b97e2684..151a0d03 100644 --- a/plugins/host_artwork_test.go +++ b/plugins/host_artwork_test.go @@ -49,7 +49,6 @@ var _ = Describe("ArtworkService", Ordered, func() { conf.Server.Plugins.Enabled = true conf.Server.Plugins.Folder = tmpDir conf.Server.Plugins.AutoReload = false - conf.Server.CacheFolder = filepath.Join(tmpDir, "cache") // Initialize auth (required for token generation) ds := &tests.MockDataStore{MockedProperty: &tests.MockedPropertyRepo{}} diff --git a/plugins/host_cache_test.go b/plugins/host_cache_test.go index ec225c1c..0f55bcfd 100644 --- a/plugins/host_cache_test.go +++ b/plugins/host_cache_test.go @@ -345,7 +345,6 @@ var _ = Describe("CacheService Integration", Ordered, func() { conf.Server.Plugins.Enabled = true conf.Server.Plugins.Folder = tmpDir conf.Server.Plugins.AutoReload = false - conf.Server.CacheFolder = filepath.Join(tmpDir, "cache") // Setup mock DataStore with pre-enabled plugin mockPluginRepo := tests.CreateMockPluginRepo() diff --git a/plugins/host_config_test.go b/plugins/host_config_test.go index 5ad5af19..bd3368a6 100644 --- a/plugins/host_config_test.go +++ b/plugins/host_config_test.go @@ -59,7 +59,6 @@ func setupTestConfigPlugin(configJSON string) (*Manager, func(context.Context, t conf.Server.Plugins.Enabled = true conf.Server.Plugins.Folder = tmpDir conf.Server.Plugins.AutoReload = false - conf.Server.CacheFolder = filepath.Join(tmpDir, "cache") // Setup mock DataStore mockPluginRepo := tests.CreateMockPluginRepo() diff --git a/plugins/host_kvstore_test.go b/plugins/host_kvstore_test.go index b900a659..4928825e 100644 --- a/plugins/host_kvstore_test.go +++ b/plugins/host_kvstore_test.go @@ -677,7 +677,6 @@ var _ = Describe("KVStoreService Integration", Ordered, func() { conf.Server.Plugins.Enabled = true conf.Server.Plugins.Folder = tmpDir conf.Server.Plugins.AutoReload = false - conf.Server.CacheFolder = filepath.Join(tmpDir, "cache") conf.Server.DataFolder = tmpDir // Setup mock DataStore with pre-enabled plugin @@ -924,16 +923,15 @@ var _ = Describe("KVStoreService Integration", Ordered, func() { Expect(output.Exists).To(BeTrue()) Expect(output.Value).To(Equal([]byte("temporary"))) - // Wait for expiration - time.Sleep(2 * time.Second) - - // Should no longer exist - output, err = callTestKVStore(ctx, testKVStoreInput{ - Operation: "get", - Key: "ttl_key", - }) - Expect(err).ToNot(HaveOccurred()) - Expect(output.Exists).To(BeFalse()) + // Poll until the key expires (1s TTL) + Eventually(func(g Gomega) { + output, err := callTestKVStore(ctx, testKVStoreInput{ + Operation: "get", + Key: "ttl_key", + }) + g.Expect(err).ToNot(HaveOccurred()) + g.Expect(output.Exists).To(BeFalse()) + }).WithTimeout(3 * time.Second).WithPolling(200 * time.Millisecond).Should(Succeed()) }) It("should delete keys by prefix", func() { diff --git a/plugins/host_library_test.go b/plugins/host_library_test.go index 413fc81c..d92abfe3 100644 --- a/plugins/host_library_test.go +++ b/plugins/host_library_test.go @@ -264,7 +264,6 @@ var _ = Describe("LibraryService", Ordered, func() { DeferCleanup(configtest.SetupConfig()) conf.Server.Plugins.Enabled = true conf.Server.Plugins.Folder = tmpDir - conf.Server.CacheFolder = filepath.Join(tmpDir, "cache") // Create mock &tests.MockLibraryRepo{} mockLibRepo := &tests.MockLibraryRepo{} @@ -360,7 +359,6 @@ var _ = Describe("LibraryService Integration", Ordered, func() { conf.Server.Plugins.Enabled = true conf.Server.Plugins.Folder = tmpDir conf.Server.Plugins.AutoReload = false - conf.Server.CacheFolder = filepath.Join(tmpDir, "cache") // Setup mock DataStore with pre-enabled plugin and library mockPluginRepo := tests.CreateMockPluginRepo() diff --git a/plugins/host_scheduler_test.go b/plugins/host_scheduler_test.go index 51311f1c..334d9b73 100644 --- a/plugins/host_scheduler_test.go +++ b/plugins/host_scheduler_test.go @@ -53,7 +53,6 @@ var _ = Describe("SchedulerService", Ordered, func() { conf.Server.Plugins.Enabled = true conf.Server.Plugins.Folder = tmpDir conf.Server.Plugins.AutoReload = false - conf.Server.CacheFolder = filepath.Join(tmpDir, "cache") // Create mock scheduler and timer registry mockSched = newMockScheduler() diff --git a/plugins/host_subsonicapi_test.go b/plugins/host_subsonicapi_test.go index b0589fa1..607f3a64 100644 --- a/plugins/host_subsonicapi_test.go +++ b/plugins/host_subsonicapi_test.go @@ -46,7 +46,6 @@ var _ = Describe("SubsonicAPI Host Function", Ordered, func() { conf.Server.Plugins.Enabled = true conf.Server.Plugins.Folder = tmpDir conf.Server.Plugins.AutoReload = false - conf.Server.CacheFolder = filepath.Join(tmpDir, "cache") // Setup mock router and data store router = &fakeSubsonicRouter{} diff --git a/plugins/host_users_test.go b/plugins/host_users_test.go index 2071a932..1c0de7d0 100644 --- a/plugins/host_users_test.go +++ b/plugins/host_users_test.go @@ -486,7 +486,6 @@ func setupTestUsersConfig(tmpDir string) { conf.Server.Plugins.Enabled = true conf.Server.Plugins.Folder = tmpDir conf.Server.Plugins.AutoReload = false - conf.Server.CacheFolder = filepath.Join(tmpDir, "cache") } // testUsersInput represents input for test-users plugin calls diff --git a/plugins/host_websocket_test.go b/plugins/host_websocket_test.go index 7a043912..e8cb9f8f 100644 --- a/plugins/host_websocket_test.go +++ b/plugins/host_websocket_test.go @@ -14,7 +14,6 @@ import ( "path/filepath" "strings" "sync" - "time" "github.com/gorilla/websocket" "github.com/navidrome/navidrome/conf" @@ -54,7 +53,6 @@ var _ = Describe("WebSocketService", Ordered, func() { conf.Server.Plugins.Enabled = true conf.Server.Plugins.Folder = tmpDir conf.Server.Plugins.AutoReload = false - conf.Server.CacheFolder = filepath.Join(tmpDir, "cache") // Setup mock DataStore with pre-enabled plugin mockPluginRepo := tests.CreateMockPluginRepo() @@ -295,10 +293,12 @@ var _ = Describe("WebSocketService", Ordered, func() { Describe("Plugin Callbacks", func() { var wsServer *httptest.Server var serverConn *websocket.Conn + var serverMessages []string var serverMu sync.Mutex BeforeEach(func() { serverConn = nil + serverMessages = nil upgrader := websocket.Upgrader{ CheckOrigin: func(r *http.Request) bool { return true }, @@ -312,12 +312,15 @@ var _ = Describe("WebSocketService", Ordered, func() { serverConn = conn serverMu.Unlock() - // Keep connection open + // Read and store messages for { - _, _, err := conn.ReadMessage() + _, msg, err := conn.ReadMessage() if err != nil { break } + serverMu.Lock() + serverMessages = append(serverMessages, string(msg)) + serverMu.Unlock() } })) @@ -336,36 +339,10 @@ var _ = Describe("WebSocketService", Ordered, func() { } }) - It("should invoke OnTextMessage callback when receiving text", func() { - ctx := GinkgoT().Context() - wsURL := "ws://" + strings.TrimPrefix(wsServer.URL, "http://") - connID, err := testService.Connect(ctx, wsURL, nil, "text-cb-conn") - Expect(err).ToNot(HaveOccurred()) - - // Wait for server to have the connection - Eventually(func() *websocket.Conn { - serverMu.Lock() - defer serverMu.Unlock() - return serverConn - }).ShouldNot(BeNil()) - - // Send message from server to plugin - serverMu.Lock() - err = serverConn.WriteMessage(websocket.TextMessage, []byte("test message")) - serverMu.Unlock() - Expect(err).ToNot(HaveOccurred()) - - // The plugin should have received the callback - // We can verify by checking the plugin's stored messages via vars - // For now we just verify no errors occurred - time.Sleep(100 * time.Millisecond) - _ = connID - }) - It("should invoke OnBinaryMessage callback when receiving binary", func() { ctx := GinkgoT().Context() wsURL := "ws://" + strings.TrimPrefix(wsServer.URL, "http://") - connID, err := testService.Connect(ctx, wsURL, nil, "binary-cb-conn") + _, err := testService.Connect(ctx, wsURL, nil, "binary-cb-conn") Expect(err).ToNot(HaveOccurred()) // Wait for server to have the connection @@ -382,9 +359,13 @@ var _ = Describe("WebSocketService", Ordered, func() { serverMu.Unlock() Expect(err).ToNot(HaveOccurred()) - // Give time for callback to execute - time.Sleep(100 * time.Millisecond) - _ = connID + // Plugin echoes binary data back as text prefixed with "binary_echo:" + expectedEcho := "binary_echo:" + base64.StdEncoding.EncodeToString(binaryData) + Eventually(func() []string { + serverMu.Lock() + defer serverMu.Unlock() + return serverMessages + }).Should(ContainElement(expectedEcho)) }) It("should invoke OnClose callback when server closes connection", func() { @@ -466,7 +447,7 @@ var _ = Describe("WebSocketService", Ordered, func() { It("should allow plugin to send messages via host function", func() { ctx := GinkgoT().Context() wsURL := "ws://" + strings.TrimPrefix(wsServer.URL, "http://") - connID, err := testService.Connect(ctx, wsURL, nil, "host-send-conn") + _, err := testService.Connect(ctx, wsURL, nil, "host-send-conn") Expect(err).ToNot(HaveOccurred()) // Wait for server to have the connection @@ -488,7 +469,6 @@ var _ = Describe("WebSocketService", Ordered, func() { defer serverMu.Unlock() return serverMessages }).Should(ContainElement("echo:echo")) - _ = connID }) It("should allow plugin to close connection via host function", func() { diff --git a/plugins/manager_call_test.go b/plugins/manager_call_test.go index 8865e126..3e64f1ce 100644 --- a/plugins/manager_call_test.go +++ b/plugins/manager_call_test.go @@ -81,48 +81,66 @@ var _ = Describe("callPluginFunction metrics", Ordered, func() { Expect(calls[0].elapsed).To(BeNumerically(">=", 0)) }) - It("records metrics for failed plugin calls (error returned)", func() { - // Create a manager with error config to force plugin errors - errorRecorder := &mockMetricsRecorder{} - errorManager, _ := createTestManagerWithPluginsAndMetrics( - map[string]map[string]string{ - "test-metadata-agent": {"error": "simulated error"}, - }, - errorRecorder, - "test-metadata-agent"+PackageExtension, + Context("with error config", Ordered, func() { + var ( + errorRecorder *mockMetricsRecorder + errorAgent agents.Interface ) - errorAgent, ok := errorManager.LoadMediaAgent("test-metadata-agent") - Expect(ok).To(BeTrue()) + BeforeAll(func() { + errorRecorder = &mockMetricsRecorder{} + errorManager, _ := createTestManagerWithPluginsAndMetrics( + map[string]map[string]string{ + "test-metadata-agent": {"error": "simulated error"}, + }, + errorRecorder, + "test-metadata-agent"+PackageExtension, + ) - retriever := errorAgent.(agents.ArtistBiographyRetriever) - _, err := retriever.GetArtistBiography(GinkgoT().Context(), "artist-1", "Test Artist", "mbid") - Expect(err).To(HaveOccurred()) + var ok bool + errorAgent, ok = errorManager.LoadMediaAgent("test-metadata-agent") + Expect(ok).To(BeTrue()) + }) - calls := errorRecorder.getCalls() - Expect(calls).To(HaveLen(1)) - Expect(calls[0].plugin).To(Equal("test-metadata-agent")) - Expect(calls[0].method).To(Equal(FuncGetArtistBiography)) - Expect(calls[0].ok).To(BeFalse()) + It("records metrics for failed plugin calls (error returned)", func() { + retriever := errorAgent.(agents.ArtistBiographyRetriever) + _, err := retriever.GetArtistBiography(GinkgoT().Context(), "artist-1", "Test Artist", "mbid") + Expect(err).To(HaveOccurred()) + + calls := errorRecorder.getCalls() + Expect(calls).To(HaveLen(1)) + Expect(calls[0].plugin).To(Equal("test-metadata-agent")) + Expect(calls[0].method).To(Equal(FuncGetArtistBiography)) + Expect(calls[0].ok).To(BeFalse()) + }) }) - It("does not record metrics for not-implemented functions", func() { - // Use partial metadata agent that doesn't implement GetArtistMBID - partialRecorder := &mockMetricsRecorder{} - partialManager, _ := createTestManagerWithPluginsAndMetrics( - nil, - partialRecorder, - "partial-metadata-agent"+PackageExtension, + Context("with partial metadata agent", Ordered, func() { + var ( + partialRecorder *mockMetricsRecorder + partialAgent agents.Interface ) - partialAgent, ok := partialManager.LoadMediaAgent("partial-metadata-agent") - Expect(ok).To(BeTrue()) + BeforeAll(func() { + partialRecorder = &mockMetricsRecorder{} + partialManager, _ := createTestManagerWithPluginsAndMetrics( + nil, + partialRecorder, + "partial-metadata-agent"+PackageExtension, + ) - retriever := partialAgent.(agents.ArtistMBIDRetriever) - _, err := retriever.GetArtistMBID(GinkgoT().Context(), "artist-1", "Test Artist") - Expect(err).To(MatchError(errNotImplemented)) + var ok bool + partialAgent, ok = partialManager.LoadMediaAgent("partial-metadata-agent") + Expect(ok).To(BeTrue()) + }) - calls := partialRecorder.getCalls() - Expect(calls).To(HaveLen(0)) + It("does not record metrics for not-implemented functions", func() { + retriever := partialAgent.(agents.ArtistMBIDRetriever) + _, err := retriever.GetArtistMBID(GinkgoT().Context(), "artist-1", "Test Artist") + Expect(err).To(MatchError(errNotImplemented)) + + calls := partialRecorder.getCalls() + Expect(calls).To(HaveLen(0)) + }) }) }) diff --git a/plugins/plugins_suite_test.go b/plugins/plugins_suite_test.go index 3b5e610d..1799ba3c 100644 --- a/plugins/plugins_suite_test.go +++ b/plugins/plugins_suite_test.go @@ -36,6 +36,20 @@ var ( func TestPlugins(t *testing.T) { tests.Init(t, false) buildTestPlugins(t, testDataDir) + + // Create a shared wazero compilation cache directory. + // All test managers will point CacheFolder here so that WASM compilation + // is done once per binary and then reused from disk cache. + sharedCacheDir, err := os.MkdirTemp("", "plugins-shared-cache-*") + if err != nil { + t.Fatalf("Failed to create shared cache dir: %v", err) + } + t.Cleanup(func() { os.RemoveAll(sharedCacheDir) }) + + // Set CacheFolder globally so all tests (including those using + // configtest.SetupConfig) inherit it without needing to set it manually. + conf.Server.CacheFolder = sharedCacheDir + log.SetLevel(log.LevelFatal) RegisterFailHandler(Fail) RunSpecs(t, "Plugins Suite") @@ -114,7 +128,6 @@ func createTestManagerWithPluginsAndMetrics(pluginConfig map[string]map[string]s conf.Server.Plugins.Enabled = true conf.Server.Plugins.Folder = tmpDir conf.Server.Plugins.AutoReload = false - conf.Server.CacheFolder = filepath.Join(tmpDir, "cache") // Setup mock DataStore with pre-enabled plugins mockPluginRepo := tests.CreateMockPluginRepo() diff --git a/plugins/scrobbler_adapter_test.go b/plugins/scrobbler_adapter_test.go index 05fc1175..ab8dc6f8 100644 --- a/plugins/scrobbler_adapter_test.go +++ b/plugins/scrobbler_adapter_test.go @@ -58,16 +58,23 @@ var _ = Describe("ScrobblerPlugin", Ordered, func() { Expect(result).To(BeTrue()) }) - It("returns false when plugin is configured to not authorize", func() { - manager, _ := createTestManagerWithPlugins(map[string]map[string]string{ - "test-scrobbler": {"authorized": "false"}, - }, "test-scrobbler"+PackageExtension) + Context("when plugin is configured to not authorize", Ordered, func() { + var notAuthScrobbler scrobbler.Scrobbler - sc, ok := manager.LoadScrobbler("test-scrobbler") - Expect(ok).To(BeTrue()) + BeforeAll(func() { + mgr, _ := createTestManagerWithPlugins(map[string]map[string]string{ + "test-scrobbler": {"authorized": "false"}, + }, "test-scrobbler"+PackageExtension) - result := sc.IsAuthorized(ctxWithUser(), "user-1") - Expect(result).To(BeFalse()) + var ok bool + notAuthScrobbler, ok = mgr.LoadScrobbler("test-scrobbler") + Expect(ok).To(BeTrue()) + }) + + It("returns false", func() { + result := notAuthScrobbler.IsAuthorized(ctxWithUser(), "user-1") + Expect(result).To(BeFalse()) + }) }) }) @@ -127,18 +134,25 @@ var _ = Describe("ScrobblerPlugin", Ordered, func() { Expect(err).ToNot(HaveOccurred()) }) - It("returns error when plugin returns error", func() { - manager, _ := createTestManagerWithPlugins(map[string]map[string]string{ - "test-scrobbler": {"error": "service unavailable", "error_type": "scrobbler(retry_later)"}, - }, "test-scrobbler"+PackageExtension) + Context("when plugin returns error", Ordered, func() { + var retryScrobbler scrobbler.Scrobbler - sc, ok := manager.LoadScrobbler("test-scrobbler") - Expect(ok).To(BeTrue()) + BeforeAll(func() { + mgr, _ := createTestManagerWithPlugins(map[string]map[string]string{ + "test-scrobbler": {"error": "service unavailable", "error_type": "scrobbler(retry_later)"}, + }, "test-scrobbler"+PackageExtension) - track := &model.MediaFile{ID: "track-1", Title: "Test Song"} - err := sc.NowPlaying(ctxWithUser(), "user-1", track, 30) - Expect(err).To(HaveOccurred()) - Expect(err).To(MatchError(scrobbler.ErrRetryLater)) + var ok bool + retryScrobbler, ok = mgr.LoadScrobbler("test-scrobbler") + Expect(ok).To(BeTrue()) + }) + + It("returns ErrRetryLater", func() { + track := &model.MediaFile{ID: "track-1", Title: "Test Song"} + err := retryScrobbler.NowPlaying(ctxWithUser(), "user-1", track, 30) + Expect(err).To(HaveOccurred()) + Expect(err).To(MatchError(scrobbler.ErrRetryLater)) + }) }) }) @@ -166,38 +180,52 @@ var _ = Describe("ScrobblerPlugin", Ordered, func() { Expect(err).ToNot(HaveOccurred()) }) - It("returns error when plugin returns not_authorized error", func() { - manager, _ := createTestManagerWithPlugins(map[string]map[string]string{ - "test-scrobbler": {"error": "user not linked", "error_type": "scrobbler(not_authorized)"}, - }, "test-scrobbler"+PackageExtension) + Context("when plugin returns not_authorized error", Ordered, func() { + var notAuthScrobbler scrobbler.Scrobbler - sc, ok := manager.LoadScrobbler("test-scrobbler") - Expect(ok).To(BeTrue()) + BeforeAll(func() { + mgr, _ := createTestManagerWithPlugins(map[string]map[string]string{ + "test-scrobbler": {"error": "user not linked", "error_type": "scrobbler(not_authorized)"}, + }, "test-scrobbler"+PackageExtension) - scrobble := scrobbler.Scrobble{ - MediaFile: model.MediaFile{ID: "track-1", Title: "Test Song"}, - TimeStamp: time.Now(), - } - err := sc.Scrobble(ctxWithUser(), "user-1", scrobble) - Expect(err).To(HaveOccurred()) - Expect(err).To(MatchError(scrobbler.ErrNotAuthorized)) + var ok bool + notAuthScrobbler, ok = mgr.LoadScrobbler("test-scrobbler") + Expect(ok).To(BeTrue()) + }) + + It("returns ErrNotAuthorized", func() { + scrobble := scrobbler.Scrobble{ + MediaFile: model.MediaFile{ID: "track-1", Title: "Test Song"}, + TimeStamp: time.Now(), + } + err := notAuthScrobbler.Scrobble(ctxWithUser(), "user-1", scrobble) + Expect(err).To(HaveOccurred()) + Expect(err).To(MatchError(scrobbler.ErrNotAuthorized)) + }) }) - It("returns error when plugin returns unrecoverable error", func() { - manager, _ := createTestManagerWithPlugins(map[string]map[string]string{ - "test-scrobbler": {"error": "track rejected", "error_type": "scrobbler(unrecoverable)"}, - }, "test-scrobbler"+PackageExtension) + Context("when plugin returns unrecoverable error", Ordered, func() { + var unrecoverableScrobbler scrobbler.Scrobbler - sc, ok := manager.LoadScrobbler("test-scrobbler") - Expect(ok).To(BeTrue()) + BeforeAll(func() { + mgr, _ := createTestManagerWithPlugins(map[string]map[string]string{ + "test-scrobbler": {"error": "track rejected", "error_type": "scrobbler(unrecoverable)"}, + }, "test-scrobbler"+PackageExtension) - scrobble := scrobbler.Scrobble{ - MediaFile: model.MediaFile{ID: "track-1", Title: "Test Song"}, - TimeStamp: time.Now(), - } - err := sc.Scrobble(ctxWithUser(), "user-1", scrobble) - Expect(err).To(HaveOccurred()) - Expect(err).To(MatchError(scrobbler.ErrUnrecoverable)) + var ok bool + unrecoverableScrobbler, ok = mgr.LoadScrobbler("test-scrobbler") + Expect(ok).To(BeTrue()) + }) + + It("returns ErrUnrecoverable", func() { + scrobble := scrobbler.Scrobble{ + MediaFile: model.MediaFile{ID: "track-1", Title: "Test Song"}, + TimeStamp: time.Now(), + } + err := unrecoverableScrobbler.Scrobble(ctxWithUser(), "user-1", scrobble) + Expect(err).To(HaveOccurred()) + Expect(err).To(MatchError(scrobbler.ErrUnrecoverable)) + }) }) }) diff --git a/plugins/testdata/test-websocket/main.go b/plugins/testdata/test-websocket/main.go index 7e2683e4..b85eaa35 100644 --- a/plugins/testdata/test-websocket/main.go +++ b/plugins/testdata/test-websocket/main.go @@ -45,10 +45,11 @@ func (t *testWebSocket) OnTextMessage(input websocket.OnTextMessageRequest) erro } // OnBinaryMessage is called when a binary message is received. +// Echoes the data back as a text message prefixed with "binary_echo:" so tests +// can observe the callback fired. func (t *testWebSocket) OnBinaryMessage(input websocket.OnBinaryMessageRequest) error { - // Store received binary data for test verification storeReceivedMessage("binary:" + input.Data) - return nil + return host.WebSocketSendText(input.ConnectionID, "binary_echo:"+input.Data) } // OnError is called when an error occurs on a WebSocket connection.