Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.
179 lines
5.8 KiB
Go
179 lines
5.8 KiB
Go
//go:build !windows
|
|||
|
|||
package plugins
|
|||
|
|||
import (
|
|||
"context"
|
|||
"crypto/sha256"
|
|||
"encoding/hex"
|
|||
"encoding/json"
|
|||
"net/http"
|
|||
"os"
|
|||
"os/exec"
|
|||
"path/filepath"
|
|||
"runtime"
|
|||
"testing"
|
|||
|
chore: add elapsed time logging to plugin build process
Signed-off-by: Deluan <deluan@navidrome.org>
|
"time"
|
||
|
|||
"github.com/navidrome/navidrome/conf"
|
|||
"github.com/navidrome/navidrome/conf/configtest"
|
|||
"github.com/navidrome/navidrome/log"
|
|||
"github.com/navidrome/navidrome/model"
|
|||
"github.com/navidrome/navidrome/tests"
|
|||
. "github.com/onsi/ginkgo/v2"
|
|||
. "github.com/onsi/gomega"
|
|||
)
|
|||
|
|||
const testDataDir = "plugins/testdata"
|
|||
|
|||
// Shared test state initialized in BeforeSuite
|
|||
var (
|
|||
testdataDir string // Path to testdata folder with test plugin .ndp packages
|
|||
tmpPluginsDir string // Temp directory for plugin tests that modify files
|
|||
testManager *Manager
|
|||
)
|
|||
|
|||
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 = conf.NewDir(sharedCacheDir)
|
|||
|
|||
log.SetLevel(log.LevelFatal)
|
|||
RegisterFailHandler(Fail)
|
|||
RunSpecs(t, "Plugins Suite")
|
|||
}
|
|||
|
|||
func buildTestPlugins(t *testing.T, path string) {
|
|||
t.Helper()
|
|||
|
chore: add elapsed time logging to plugin build process
Signed-off-by: Deluan <deluan@navidrome.org>
|
start := time.Now()
|
||
t.Logf("[BeforeSuite] Current working directory: %s", path)
|
|||
cmd := exec.Command("make", "-C", path)
|
|||
out, err := cmd.CombinedOutput()
|
|||
|
chore: add elapsed time logging to plugin build process
Signed-off-by: Deluan <deluan@navidrome.org>
|
t.Logf("[BeforeSuite] Make output: %s elapsed: %s", string(out), time.Since(start))
|
||
if err != nil {
|
|||
t.Fatalf("Failed to build test plugins: %v", err)
|
|||
}
|
|||
}
|
|||
|
|||
// createTestManager creates a new plugin Manager with the given plugin config.
|
|||
// It creates a temp directory, copies the test-metadata-agent plugin, and starts the manager.
|
|||
// Returns the manager, temp directory path, and a cleanup function.
|
|||
func createTestManager(pluginConfig map[string]map[string]string) (*Manager, string) {
|
|||
return createTestManagerWithPlugins(pluginConfig, "test-metadata-agent"+PackageExtension)
|
|||
}
|
|||
|
|||
// createTestManagerWithPlugins creates a new plugin Manager with the given plugin config
|
|||
// and specified plugins. It creates a temp directory, copies the specified plugins, and starts the manager.
|
|||
// Returns the manager and temp directory path.
|
|||
func createTestManagerWithPlugins(pluginConfig map[string]map[string]string, plugins ...string) (*Manager, string) {
|
|||
return createTestManagerWithPluginsAndMetrics(pluginConfig, noopMetricsRecorder{}, plugins...)
|
|||
}
|
|||
|
|||
// createTestManagerWithPluginsAndMetrics creates a new plugin Manager with the given plugin config,
|
|||
// metrics recorder, and specified plugins. It creates a temp directory, copies the specified plugins, and starts the manager.
|
|||
// Returns the manager and temp directory path.
|
|||
func createTestManagerWithPluginsAndMetrics(pluginConfig map[string]map[string]string, metrics PluginMetricsRecorder, plugins ...string) (*Manager, string) {
|
|||
// Create temp directory
|
|||
tmpDir, err := os.MkdirTemp("", "plugins-test-*")
|
|||
Expect(err).ToNot(HaveOccurred())
|
|||
|
|||
// Copy test plugins to temp dir and build plugin list with SHA256
|
|||
var enabledPlugins model.Plugins
|
|||
for _, plugin := range plugins {
|
|||
srcPath := filepath.Join(testdataDir, plugin)
|
|||
destPath := filepath.Join(tmpDir, plugin)
|
|||
data, err := os.ReadFile(srcPath)
|
|||
Expect(err).ToNot(HaveOccurred())
|
|||
err = os.WriteFile(destPath, data, 0600)
|
|||
Expect(err).ToNot(HaveOccurred())
|
|||
|
|||
// Compute SHA256 for the plugin
|
|||
hash := sha256.Sum256(data)
|
|||
hashHex := hex.EncodeToString(hash[:])
|
|||
pluginName := plugin[:len(plugin)-len(PackageExtension)] // Remove .ndp extension
|
|||
|
|||
// Build config JSON if provided
|
|||
configJSON := ""
|
|||
if pluginConfig != nil && pluginConfig[pluginName] != nil {
|
|||
// Encode config to JSON
|
|||
configBytes, err := json.Marshal(pluginConfig[pluginName])
|
|||
Expect(err).ToNot(HaveOccurred())
|
|||
configJSON = string(configBytes)
|
|||
}
|
|||
|
|||
enabledPlugins = append(enabledPlugins, model.Plugin{
|
|||
ID: pluginName,
|
|||
Path: destPath,
|
|||
SHA256: hashHex,
|
|||
Enabled: true,
|
|||
Config: configJSON,
|
|||
AllUsers: true, // Allow all users by default in tests
|
|||
})
|
|||
}
|
|||
|
|||
// Setup config
|
|||
DeferCleanup(configtest.SetupConfig())
|
|||
conf.Server.Plugins.Enabled = true
|
|||
conf.Server.Plugins.Folder = conf.NewDir(tmpDir)
|
|||
conf.Server.Plugins.AutoReload = false
|
|||
|
|||
// Setup mock DataStore with pre-enabled plugins
|
|||
mockPluginRepo := tests.CreateMockPluginRepo()
|
|||
mockPluginRepo.Permitted = true
|
|||
mockPluginRepo.SetData(enabledPlugins)
|
|||
dataStore := &tests.MockDataStore{MockedPlugin: mockPluginRepo}
|
|||
|
|||
// Create and start manager
|
|||
manager := &Manager{
|
|||
plugins: make(map[string]*plugin),
|
|||
ds: dataStore,
|
|||
metrics: metrics,
|
|||
subsonicRouter: http.NotFoundHandler(), // Stub router for tests
|
|||
}
|
|||
err = manager.Start(GinkgoT().Context())
|
|||
Expect(err).ToNot(HaveOccurred())
|
|||
|
|||
DeferCleanup(func() {
|
|||
_ = manager.Stop()
|
|||
_ = os.RemoveAll(tmpDir)
|
|||
})
|
|||
|
|||
return manager, tmpDir
|
|||
}
|
|||
|
|||
var _ = BeforeSuite(func() {
|
|||
// Get testdata directory (where test plugin .ndp packages live)
|
|||
_, currentFile, _, ok := runtime.Caller(0)
|
|||
Expect(ok).To(BeTrue())
|
|||
testdataDir = filepath.Join(filepath.Dir(currentFile), "testdata")
|
|||
|
|||
// Create shared manager for most tests
|
|||
testManager, tmpPluginsDir = createTestManager(nil)
|
|||
})
|
|||
|
|||
var _ = AfterSuite(func() {
|
|||
if testManager != nil {
|
|||
_ = testManager.Stop()
|
|||
}
|
|||
if tmpPluginsDir != "" {
|
|||
_ = os.RemoveAll(tmpPluginsDir)
|
|||
}
|
|||
})
|
|||
|
|||
// noopMetricsRecorder is a no-op implementation of PluginMetricsRecorder for tests
|
|||
type noopMetricsRecorder struct{}
|
|||
|
|||
func (noopMetricsRecorder) RecordPluginRequest(context.Context, string, string, bool, int64) {}
|