mirror of
https://github.com/navidrome/navidrome.git
synced 2025-08-13 14:01:14 +03:00
Add EnsureCompiled calls in plugin test BeforeEach blocks to wait for WebAssembly compilation before loading plugins. This prevents race conditions where tests would attempt to load plugins before compilation completed, causing flaky test failures in CI environments. The race condition occurred because ScanPlugins() registers plugins synchronously but compiles them asynchronously in background goroutines with a concurrency limit of 2. Tests that immediately called LoadPlugin() or LoadMediaAgent() after ScanPlugins() could fail if compilation wasn't finished yet. Fixed in both adapter_media_agent_test.go and manager_test.go which had multiple tests vulnerable to this timing issue.
228 lines
6.7 KiB
Go
228 lines
6.7 KiB
Go
package plugins
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
|
|
"github.com/navidrome/navidrome/conf"
|
|
"github.com/navidrome/navidrome/conf/configtest"
|
|
"github.com/navidrome/navidrome/core/agents"
|
|
"github.com/navidrome/navidrome/core/metrics"
|
|
"github.com/navidrome/navidrome/plugins/api"
|
|
. "github.com/onsi/ginkgo/v2"
|
|
. "github.com/onsi/gomega"
|
|
)
|
|
|
|
var _ = Describe("Adapter Media Agent", func() {
|
|
var ctx context.Context
|
|
var mgr *managerImpl
|
|
|
|
BeforeEach(func() {
|
|
ctx = GinkgoT().Context()
|
|
|
|
// Ensure plugins folder is set to testdata
|
|
DeferCleanup(configtest.SetupConfig())
|
|
conf.Server.Plugins.Folder = testDataDir
|
|
|
|
mgr = createManager(nil, metrics.NewNoopInstance())
|
|
mgr.ScanPlugins()
|
|
|
|
// Wait for all plugins to compile to avoid race conditions
|
|
err := mgr.EnsureCompiled("multi_plugin")
|
|
Expect(err).NotTo(HaveOccurred(), "multi_plugin should compile successfully")
|
|
err = mgr.EnsureCompiled("fake_album_agent")
|
|
Expect(err).NotTo(HaveOccurred(), "fake_album_agent should compile successfully")
|
|
})
|
|
|
|
Describe("AgentName and PluginName", func() {
|
|
It("should return the plugin name", func() {
|
|
agent := mgr.LoadPlugin("multi_plugin", "MetadataAgent")
|
|
Expect(agent).NotTo(BeNil(), "multi_plugin should be loaded")
|
|
Expect(agent.PluginID()).To(Equal("multi_plugin"))
|
|
})
|
|
It("should return the agent name", func() {
|
|
agent, ok := mgr.LoadMediaAgent("multi_plugin")
|
|
Expect(ok).To(BeTrue(), "multi_plugin should be loaded as media agent")
|
|
Expect(agent.AgentName()).To(Equal("multi_plugin"))
|
|
})
|
|
})
|
|
|
|
Describe("Album methods", func() {
|
|
var agent *wasmMediaAgent
|
|
|
|
BeforeEach(func() {
|
|
a, ok := mgr.LoadMediaAgent("fake_album_agent")
|
|
Expect(ok).To(BeTrue(), "fake_album_agent should be loaded")
|
|
agent = a.(*wasmMediaAgent)
|
|
})
|
|
|
|
Context("GetAlbumInfo", func() {
|
|
It("should return album information", func() {
|
|
info, err := agent.GetAlbumInfo(ctx, "Test Album", "Test Artist", "mbid")
|
|
|
|
Expect(err).NotTo(HaveOccurred())
|
|
Expect(info).NotTo(BeNil())
|
|
Expect(info.Name).To(Equal("Test Album"))
|
|
Expect(info.MBID).To(Equal("album-mbid-123"))
|
|
Expect(info.Description).To(Equal("This is a test album description"))
|
|
Expect(info.URL).To(Equal("https://example.com/album"))
|
|
})
|
|
|
|
It("should return ErrNotFound when plugin returns not found", func() {
|
|
_, err := agent.GetAlbumInfo(ctx, "Test Album", "", "mbid")
|
|
|
|
Expect(err).To(Equal(agents.ErrNotFound))
|
|
})
|
|
|
|
It("should return ErrNotFound when plugin returns nil response", func() {
|
|
_, err := agent.GetAlbumInfo(ctx, "", "", "")
|
|
|
|
Expect(err).To(Equal(agents.ErrNotFound))
|
|
})
|
|
})
|
|
|
|
Context("GetAlbumImages", func() {
|
|
It("should return album images", func() {
|
|
images, err := agent.GetAlbumImages(ctx, "Test Album", "Test Artist", "mbid")
|
|
|
|
Expect(err).NotTo(HaveOccurred())
|
|
Expect(images).To(Equal([]agents.ExternalImage{
|
|
{URL: "https://example.com/album1.jpg", Size: 300},
|
|
{URL: "https://example.com/album2.jpg", Size: 400},
|
|
}))
|
|
})
|
|
})
|
|
})
|
|
|
|
Describe("Artist methods", func() {
|
|
var agent *wasmMediaAgent
|
|
|
|
BeforeEach(func() {
|
|
a, ok := mgr.LoadMediaAgent("fake_artist_agent")
|
|
Expect(ok).To(BeTrue(), "fake_artist_agent should be loaded")
|
|
agent = a.(*wasmMediaAgent)
|
|
})
|
|
|
|
Context("GetArtistMBID", func() {
|
|
It("should return artist MBID", func() {
|
|
mbid, err := agent.GetArtistMBID(ctx, "artist-id", "Test Artist")
|
|
|
|
Expect(err).NotTo(HaveOccurred())
|
|
Expect(mbid).To(Equal("1234567890"))
|
|
})
|
|
|
|
It("should return ErrNotFound when plugin returns not found", func() {
|
|
_, err := agent.GetArtistMBID(ctx, "artist-id", "")
|
|
|
|
Expect(err).To(Equal(agents.ErrNotFound))
|
|
})
|
|
})
|
|
|
|
Context("GetArtistURL", func() {
|
|
It("should return artist URL", func() {
|
|
url, err := agent.GetArtistURL(ctx, "artist-id", "Test Artist", "mbid")
|
|
|
|
Expect(err).NotTo(HaveOccurred())
|
|
Expect(url).To(Equal("https://example.com"))
|
|
})
|
|
})
|
|
|
|
Context("GetArtistBiography", func() {
|
|
It("should return artist biography", func() {
|
|
bio, err := agent.GetArtistBiography(ctx, "artist-id", "Test Artist", "mbid")
|
|
|
|
Expect(err).NotTo(HaveOccurred())
|
|
Expect(bio).To(Equal("This is a test biography"))
|
|
})
|
|
})
|
|
|
|
Context("GetSimilarArtists", func() {
|
|
It("should return similar artists", func() {
|
|
artists, err := agent.GetSimilarArtists(ctx, "artist-id", "Test Artist", "mbid", 10)
|
|
|
|
Expect(err).NotTo(HaveOccurred())
|
|
Expect(artists).To(Equal([]agents.Artist{
|
|
{Name: "Similar Artist 1", MBID: "mbid1"},
|
|
{Name: "Similar Artist 2", MBID: "mbid2"},
|
|
}))
|
|
})
|
|
})
|
|
|
|
Context("GetArtistImages", func() {
|
|
It("should return artist images", func() {
|
|
images, err := agent.GetArtistImages(ctx, "artist-id", "Test Artist", "mbid")
|
|
|
|
Expect(err).NotTo(HaveOccurred())
|
|
Expect(images).To(Equal([]agents.ExternalImage{
|
|
{URL: "https://example.com/image1.jpg", Size: 100},
|
|
{URL: "https://example.com/image2.jpg", Size: 200},
|
|
}))
|
|
})
|
|
})
|
|
|
|
Context("GetArtistTopSongs", func() {
|
|
It("should return artist top songs", func() {
|
|
songs, err := agent.GetArtistTopSongs(ctx, "artist-id", "Test Artist", "mbid", 10)
|
|
|
|
Expect(err).NotTo(HaveOccurred())
|
|
Expect(songs).To(Equal([]agents.Song{
|
|
{Name: "Song 1", MBID: "mbid1"},
|
|
{Name: "Song 2", MBID: "mbid2"},
|
|
}))
|
|
})
|
|
})
|
|
})
|
|
|
|
Describe("Helper functions", func() {
|
|
It("convertExternalImages should convert API image objects to agent image objects", func() {
|
|
apiImages := []*api.ExternalImage{
|
|
{Url: "https://example.com/image1.jpg", Size: 100},
|
|
{Url: "https://example.com/image2.jpg", Size: 200},
|
|
}
|
|
|
|
agentImages := convertExternalImages(apiImages)
|
|
Expect(agentImages).To(HaveLen(2))
|
|
|
|
for i, img := range agentImages {
|
|
Expect(img.URL).To(Equal(apiImages[i].Url))
|
|
Expect(img.Size).To(Equal(int(apiImages[i].Size)))
|
|
}
|
|
})
|
|
|
|
It("convertExternalImages should handle empty slice", func() {
|
|
agentImages := convertExternalImages([]*api.ExternalImage{})
|
|
Expect(agentImages).To(BeEmpty())
|
|
})
|
|
|
|
It("convertExternalImages should handle nil", func() {
|
|
agentImages := convertExternalImages(nil)
|
|
Expect(agentImages).To(BeEmpty())
|
|
})
|
|
})
|
|
|
|
Describe("Error mapping", func() {
|
|
var agent wasmMediaAgent
|
|
|
|
It("should map API ErrNotFound to agents.ErrNotFound", func() {
|
|
err := agent.mapError(api.ErrNotFound)
|
|
Expect(err).To(Equal(agents.ErrNotFound))
|
|
})
|
|
|
|
It("should map API ErrNotImplemented to agents.ErrNotFound", func() {
|
|
err := agent.mapError(api.ErrNotImplemented)
|
|
Expect(err).To(Equal(agents.ErrNotFound))
|
|
})
|
|
|
|
It("should pass through other errors", func() {
|
|
testErr := errors.New("test error")
|
|
err := agent.mapError(testErr)
|
|
Expect(err).To(Equal(testErr))
|
|
})
|
|
|
|
It("should handle nil error", func() {
|
|
err := agent.mapError(nil)
|
|
Expect(err).To(BeNil())
|
|
})
|
|
})
|
|
})
|