From 930411a07bc988071beb3f7f199f29eafdbcb82d Mon Sep 17 00:00:00 2001 From: Chris Gianelloni Date: Tue, 2 Dec 2025 18:24:45 -0500 Subject: [PATCH] fix(database): set default .dingo data dir Signed-off-by: Chris Gianelloni --- PLUGIN_DEVELOPMENT.md | 22 ++- connmanager/connection_manager.go | 6 +- database/database.go | 25 ++- database/plugin/blob/aws/database.go | 2 +- database/plugin/blob/badger/database.go | 10 +- database/plugin/blob/badger/plugin.go | 3 +- database/plugin/blob/gcs/plugin_test.go | 2 +- database/plugin/metadata/sqlite/database.go | 4 +- database/plugin/metadata/sqlite/plugin.go | 4 +- database/plugin/plugin.go | 170 ++++++++++++++++++++ database/plugin/plugin_test.go | 56 +++++++ database/plugin/register.go | 4 + internal/integration/benchmark_test.go | 8 +- internal/integration/integration_test.go | 146 +++++++++++++---- 14 files changed, 414 insertions(+), 48 deletions(-) create mode 100644 database/plugin/plugin_test.go diff --git a/PLUGIN_DEVELOPMENT.md b/PLUGIN_DEVELOPMENT.md index dc2bce9a..a02a568c 100644 --- a/PLUGIN_DEVELOPMENT.md +++ b/PLUGIN_DEVELOPMENT.md @@ -276,4 +276,24 @@ Test environment variables and YAML configuration: ```bash DINGO_DATABASE_BLOB_MYPLUGIN_OPTION1=value ./dingo --blob myplugin -``` \ No newline at end of file +``` + +## Programmatic Option Overrides (for tests) + +When writing tests or programmatically constructing database instances you can override plugin options +without importing plugin implementation packages directly by using the plugin registry helper: + +```go +// Set data-dir for the blob plugin to a per-test temp directory +plugin.SetPluginOption(plugin.PluginTypeBlob, "badger", "data-dir", t.TempDir()) + +// Set data-dir for the metadata plugin +plugin.SetPluginOption(plugin.PluginTypeMetadata, "sqlite", "data-dir", t.TempDir()) +``` + +The helper sets the plugin option's destination variable in the registry before plugin instantiation. +If the requested option is not defined by the targeted plugin the call is non-fatal and returns nil, +allowing tests to run regardless of which plugin implementation is selected. + +Using `t.TempDir()` guarantees each test uses its own on-disk path and prevents concurrent tests from +colliding on shared directories (for example the default `.dingo` Badger directory). diff --git a/connmanager/connection_manager.go b/connmanager/connection_manager.go index 09d64eca..20fbe071 100644 --- a/connmanager/connection_manager.go +++ b/connmanager/connection_manager.go @@ -36,8 +36,8 @@ const ( type connectionInfo struct { conn *ouroboros.Connection - isInbound bool peerAddr string + isInbound bool } type peerConnectionState struct { @@ -47,19 +47,19 @@ type peerConnectionState struct { type ConnectionManager struct { connections map[ouroboros.ConnectionId]*connectionInfo + metrics *connectionManagerMetrics config ConnectionManagerConfig connectionsMutex sync.Mutex - metrics *connectionManagerMetrics } type ConnectionManagerConfig struct { + PromRegistry prometheus.Registerer Logger *slog.Logger EventBus *event.EventBus ConnClosedFunc ConnectionManagerConnClosedFunc Listeners []ListenerConfig OutboundConnOpts []ouroboros.ConnectionOptionFunc OutboundSourcePort uint - PromRegistry prometheus.Registerer } type connectionManagerMetrics struct { diff --git a/database/database.go b/database/database.go index 50ab027d..cd4a9900 100644 --- a/database/database.go +++ b/database/database.go @@ -19,6 +19,7 @@ import ( "io" "log/slog" + "github.com/blinklabs-io/dingo/database/plugin" "github.com/blinklabs-io/dingo/database/plugin/blob" "github.com/blinklabs-io/dingo/database/plugin/metadata" "github.com/prometheus/client_golang/prometheus" @@ -119,6 +120,7 @@ func (d *Database) init() error { func New( config *Config, ) (*Database, error) { + var err error if config == nil { config = DefaultConfig } @@ -132,10 +134,31 @@ func New( if configCopy.MetadataPlugin == "" { configCopy.MetadataPlugin = DefaultConfig.MetadataPlugin } - // DataDir defaulting behavior: + // Handle DataDir configuration for plugins: // - nil config → DefaultConfig.DataDir (".dingo" for persistence) // - empty DataDir → in-memory storage // - non-empty DataDir → persistent storage at specified path + // NOTE: SetPluginOption mutates global plugin state, so DataDir is effectively + // process-wide and not concurrency-safe. Multiple Database instances in the + // same process will share and overwrite these options. + err = plugin.SetPluginOption( + plugin.PluginTypeBlob, + configCopy.BlobPlugin, + "data-dir", + configCopy.DataDir, + ) + if err != nil { + return nil, err + } + err = plugin.SetPluginOption( + plugin.PluginTypeMetadata, + configCopy.MetadataPlugin, + "data-dir", + configCopy.DataDir, + ) + if err != nil { + return nil, err + } blobDb, err := blob.New( configCopy.BlobPlugin, ) diff --git a/database/plugin/blob/aws/database.go b/database/plugin/blob/aws/database.go index e884feb6..098f68f9 100644 --- a/database/plugin/blob/aws/database.go +++ b/database/plugin/blob/aws/database.go @@ -35,10 +35,10 @@ type BlobStoreS3 struct { startupCtx context.Context logger *S3Logger client *s3.Client + startupCancel context.CancelFunc bucket string prefix string region string - startupCancel context.CancelFunc timeout time.Duration } diff --git a/database/plugin/blob/badger/database.go b/database/plugin/blob/badger/database.go index c612bdf0..02c4539e 100644 --- a/database/plugin/blob/badger/database.go +++ b/database/plugin/blob/badger/database.go @@ -34,13 +34,13 @@ type BlobStoreBadger struct { promRegistry prometheus.Registerer db *badger.DB logger *slog.Logger - dataDir string - gcEnabled bool - blockCacheSize uint64 - indexCacheSize uint64 gcTicker *time.Ticker gcStopCh chan struct{} + dataDir string gcWg sync.WaitGroup + blockCacheSize uint64 + indexCacheSize uint64 + gcEnabled bool } // New creates a new database @@ -76,7 +76,7 @@ func New(opts ...BlobStoreBadgerOptionFunc) (*BlobStoreBadger, error) { return nil, fmt.Errorf("failed to read data dir: %w", err) } // Create data directory - if err := os.MkdirAll(db.dataDir, fs.ModePerm); err != nil { + if err := os.MkdirAll(db.dataDir, 0o755); err != nil { return nil, fmt.Errorf("failed to create data dir: %w", err) } } diff --git a/database/plugin/blob/badger/plugin.go b/database/plugin/blob/badger/plugin.go index 7c04d0d8..ee01a5bb 100644 --- a/database/plugin/blob/badger/plugin.go +++ b/database/plugin/blob/badger/plugin.go @@ -43,6 +43,7 @@ func initCmdlineOptions() { cmdlineOptions.blockCacheSize = DefaultBlockCacheSize cmdlineOptions.indexCacheSize = DefaultIndexCacheSize cmdlineOptions.gcEnabled = true + cmdlineOptions.dataDir = ".dingo" } // Register plugin @@ -59,7 +60,7 @@ func init() { Name: "data-dir", Type: plugin.PluginOptionTypeString, Description: "Data directory for badger storage", - DefaultValue: "", + DefaultValue: ".dingo", Dest: &(cmdlineOptions.dataDir), }, { diff --git a/database/plugin/blob/gcs/plugin_test.go b/database/plugin/blob/gcs/plugin_test.go index 48d60e11..7ecbb68e 100644 --- a/database/plugin/blob/gcs/plugin_test.go +++ b/database/plugin/blob/gcs/plugin_test.go @@ -28,8 +28,8 @@ func TestCredentialValidation(t *testing.T) { tests := []struct { name string credentialsFile string - expectError bool errorMessage string + expectError bool }{ { name: "valid credentials file", diff --git a/database/plugin/metadata/sqlite/database.go b/database/plugin/metadata/sqlite/database.go index b752769c..62484ec4 100644 --- a/database/plugin/metadata/sqlite/database.go +++ b/database/plugin/metadata/sqlite/database.go @@ -39,8 +39,8 @@ type MetadataStoreSqlite struct { db *gorm.DB logger *slog.Logger timerVacuum *time.Timer - timerMutex sync.Mutex dataDir string + timerMutex sync.Mutex closed bool } @@ -159,7 +159,7 @@ func (d *MetadataStoreSqlite) Start() error { return fmt.Errorf("failed to read data dir: %w", err) } // Create data directory - if err := os.MkdirAll(d.dataDir, fs.ModePerm); err != nil { + if err := os.MkdirAll(d.dataDir, 0o755); err != nil { return fmt.Errorf("failed to create data dir: %w", err) } } diff --git a/database/plugin/metadata/sqlite/plugin.go b/database/plugin/metadata/sqlite/plugin.go index eec017b3..300de627 100644 --- a/database/plugin/metadata/sqlite/plugin.go +++ b/database/plugin/metadata/sqlite/plugin.go @@ -31,7 +31,7 @@ var ( func initCmdlineOptions() { cmdlineOptionsMutex.Lock() defer cmdlineOptionsMutex.Unlock() - cmdlineOptions.dataDir = "" + cmdlineOptions.dataDir = ".dingo" } // Register plugin @@ -48,7 +48,7 @@ func init() { Name: "data-dir", Type: plugin.PluginOptionTypeString, Description: "Data directory for sqlite storage", - DefaultValue: "", + DefaultValue: ".dingo", Dest: &(cmdlineOptions.dataDir), }, }, diff --git a/database/plugin/plugin.go b/database/plugin/plugin.go index d06529a4..455fbe7c 100644 --- a/database/plugin/plugin.go +++ b/database/plugin/plugin.go @@ -63,3 +63,173 @@ func StartPlugin(pluginType PluginType, pluginName string) (Plugin, error) { return p, nil } + +// SetPluginOption sets the value of a named option for a plugin entry. This +// is used by callers that need to programmatically override plugin defaults +// (for example to set data-dir before starting a plugin). It returns an error +// if the plugin or option is not found or if the value type is incompatible. +// NOTE: This function accesses the global pluginEntries slice without +// synchronization. It should only be called during initialization or in +// single-threaded contexts to avoid race conditions. +// NOTE: This function writes directly to plugin option destinations (e.g., +// cmdlineOptions fields) without acquiring the plugin's cmdlineOptionsMutex. +// It must be called before any plugin instantiation to avoid data races with +// concurrent reads in NewFromCmdlineOptions. +func SetPluginOption( + pluginType PluginType, + pluginName string, + optionName string, + value any, +) error { + for i := range pluginEntries { + p := &pluginEntries[i] + if p.Type != pluginType || p.Name != pluginName { + continue + } + for _, opt := range p.Options { + if opt.Name != optionName { + continue + } + // Perform a type-checked assignment into the Dest pointer + switch opt.Type { + case PluginOptionTypeString: + v, ok := value.(string) + if !ok { + return fmt.Errorf( + "invalid type for option %s: expected string", + optionName, + ) + } + if opt.Dest == nil { + return fmt.Errorf( + "nil destination for option %s", + optionName, + ) + } + dest, ok := opt.Dest.(*string) + if !ok { + return fmt.Errorf( + "invalid destination type for option %s: expected *string", + optionName, + ) + } + if dest == nil { + return fmt.Errorf( + "nil destination pointer for option %s", + optionName, + ) + } + *dest = v + return nil + case PluginOptionTypeBool: + v, ok := value.(bool) + if !ok { + return fmt.Errorf( + "invalid type for option %s: expected bool", + optionName, + ) + } + if opt.Dest == nil { + return fmt.Errorf( + "nil destination for option %s", + optionName, + ) + } + dest, ok := opt.Dest.(*bool) + if !ok { + return fmt.Errorf( + "invalid destination type for option %s: expected *bool", + optionName, + ) + } + if dest == nil { + return fmt.Errorf( + "nil destination pointer for option %s", + optionName, + ) + } + *dest = v + return nil + case PluginOptionTypeInt: + v, ok := value.(int) + if !ok { + return fmt.Errorf( + "invalid type for option %s: expected int", + optionName, + ) + } + if opt.Dest == nil { + return fmt.Errorf( + "nil destination for option %s", + optionName, + ) + } + dest, ok := opt.Dest.(*int) + if !ok { + return fmt.Errorf( + "invalid destination type for option %s: expected *int", + optionName, + ) + } + if dest == nil { + return fmt.Errorf( + "nil destination pointer for option %s", + optionName, + ) + } + *dest = v + return nil + case PluginOptionTypeUint: + // accept uint64 or int + switch tv := value.(type) { + case uint64: + if opt.Dest == nil { + return fmt.Errorf("nil destination for option %s", optionName) + } + dest, ok := opt.Dest.(*uint64) + if !ok { + return fmt.Errorf("invalid destination type for option %s: expected *uint64", optionName) + } + if dest == nil { + return fmt.Errorf("nil destination pointer for option %s", optionName) + } + *dest = tv + return nil + case int: + if tv < 0 { + return fmt.Errorf("invalid value for option %s: negative int", optionName) + } + if opt.Dest == nil { + return fmt.Errorf("nil destination for option %s", optionName) + } + dest, ok := opt.Dest.(*uint64) + if !ok { + return fmt.Errorf("invalid destination type for option %s: expected *uint64", optionName) + } + if dest == nil { + return fmt.Errorf("nil destination pointer for option %s", optionName) + } + *dest = uint64(tv) + return nil + default: + return fmt.Errorf("invalid type for option %s: expected uint64 or int", optionName) + } + default: + return fmt.Errorf( + "unknown plugin option type %d for option %s", + opt.Type, + optionName, + ) + } + } + // Option not found for this plugin: treat as non-fatal. This allows + // callers to attempt to set options that may not exist for all + // implementations (for example `data-dir` may not be relevant). + return nil + } + return fmt.Errorf( + "plugin %s of type %s not found", + pluginName, + PluginTypeName(pluginType), + ) +} diff --git a/database/plugin/plugin_test.go b/database/plugin/plugin_test.go new file mode 100644 index 00000000..0a7bc47c --- /dev/null +++ b/database/plugin/plugin_test.go @@ -0,0 +1,56 @@ +package plugin_test + +import ( + "testing" + + "github.com/blinklabs-io/dingo/database/plugin" + _ "github.com/blinklabs-io/dingo/database/plugin/blob/badger" + _ "github.com/blinklabs-io/dingo/database/plugin/metadata/sqlite" + "github.com/blinklabs-io/dingo/internal/config" +) + +// Basic tests for SetPluginOption to ensure programmatic option setting works +func TestSetPluginOption_SuccessAndTypeCheck(t *testing.T) { + // Note: This test mutates global plugin state (cmdlineOptions in subpackages). + // If tests run in parallel, this could cause interference. Currently, tests + // are run sequentially, but consider adding cleanup if parallelism is enabled. + + // Set data-dir for sqlite plugin to an empty string (in-memory) and ensure no error + if err := plugin.SetPluginOption(plugin.PluginTypeMetadata, config.DefaultMetadataPlugin, "data-dir", ""); err != nil { + t.Fatalf("unexpected error setting sqlite data-dir: %v", err) + } + + // Setting with wrong type should return an error + if err := plugin.SetPluginOption(plugin.PluginTypeMetadata, config.DefaultMetadataPlugin, "data-dir", 123); err == nil { + t.Fatalf( + "expected type error when setting sqlite data-dir with int, got nil", + ) + } + + // Setting an unknown option is a no-op (non-fatal) so should not return an error + if err := plugin.SetPluginOption(plugin.PluginTypeMetadata, config.DefaultMetadataPlugin, "does-not-exist", "x"); err != nil { + t.Fatalf("unexpected error when setting unknown option: %v", err) + } + + // Test setting data-dir for badger plugin (blob type) + if err := plugin.SetPluginOption(plugin.PluginTypeBlob, config.DefaultBlobPlugin, "data-dir", t.TempDir()); err != nil { + t.Fatalf("unexpected error setting badger data-dir: %v", err) + } + + // Test uint option handling for badger block-cache-size + if err := plugin.SetPluginOption(plugin.PluginTypeBlob, config.DefaultBlobPlugin, "block-cache-size", uint64(100000000)); err != nil { + t.Fatalf("unexpected error setting badger block-cache-size: %v", err) + } + + // Test bool option handling for badger gc + if err := plugin.SetPluginOption(plugin.PluginTypeBlob, config.DefaultBlobPlugin, "gc", true); err != nil { + t.Fatalf("unexpected error setting badger gc: %v", err) + } + + // Test plugin not found error + if err := plugin.SetPluginOption(plugin.PluginTypeMetadata, "nonexistent", "data-dir", t.TempDir()); err == nil { + t.Fatalf( + "expected error when setting option for nonexistent plugin, got nil", + ) + } +} diff --git a/database/plugin/register.go b/database/plugin/register.go index f19ab4fe..d0f51106 100644 --- a/database/plugin/register.go +++ b/database/plugin/register.go @@ -49,6 +49,10 @@ type PluginEntry struct { var pluginEntries []PluginEntry +// Register adds a plugin entry to the global registry. +// NOTE: This function is not thread-safe and should only be called during +// package initialization (e.g., in init() functions) before any concurrent +// goroutines begin. Concurrent access to pluginEntries is not protected. func Register(pluginEntry PluginEntry) { pluginEntries = append(pluginEntries, pluginEntry) } diff --git a/internal/integration/benchmark_test.go b/internal/integration/benchmark_test.go index 00d39b1e..1f47d36f 100644 --- a/internal/integration/benchmark_test.go +++ b/internal/integration/benchmark_test.go @@ -99,12 +99,12 @@ func loadBlockData(numBlocks int) ([][]byte, error) { // getTestBackends returns a slice of test backends for benchmarking func getTestBackends(b *testing.B, diskDataDir string) []struct { - name string config *database.Config + name string } { backends := []struct { - name string config *database.Config + name string }{ { name: "memory", @@ -133,8 +133,8 @@ func getTestBackends(b *testing.B, diskDataDir string) []struct { // Use path prefix for isolation instead of unique bucket names testPrefix := strings.ReplaceAll(b.Name(), "/", "-") backends = append(backends, struct { - name string config *database.Config + name string }{ name: "GCS", config: &database.Config{ @@ -153,8 +153,8 @@ func getTestBackends(b *testing.B, diskDataDir string) []struct { // Use path prefix for isolation instead of unique bucket names testPrefix := strings.ReplaceAll(b.Name(), "/", "-") backends = append(backends, struct { - name string config *database.Config + name string }{ name: "S3", config: &database.Config{ diff --git a/internal/integration/integration_test.go b/internal/integration/integration_test.go index f6191d3e..0df9b672 100644 --- a/internal/integration/integration_test.go +++ b/internal/integration/integration_test.go @@ -15,10 +15,13 @@ package integration import ( + "fmt" "os" "path/filepath" "testing" + "gopkg.in/yaml.v3" + "github.com/blinklabs-io/dingo/database/plugin" "github.com/blinklabs-io/dingo/database/plugin/blob/aws" _ "github.com/blinklabs-io/dingo/database/plugin/blob/badger" @@ -27,7 +30,58 @@ import ( "github.com/blinklabs-io/dingo/internal/config" ) +func TestMain(m *testing.M) { + // Create a package-level temp dir and set plugin data-dir options + tmpDir, err := os.MkdirTemp("", "dingo-integration-") + if err != nil { + panic("failed to create temp dir for integration tests: " + err.Error()) + } + + // Set data-dir for blob and metadata plugins before any tests run + if err := plugin.SetPluginOption( + plugin.PluginTypeBlob, + config.DefaultBlobPlugin, + "data-dir", + tmpDir, + ); err != nil { + panic("failed to set blob plugin data-dir: " + err.Error()) + } + if err := plugin.SetPluginOption( + plugin.PluginTypeMetadata, + config.DefaultMetadataPlugin, + "data-dir", + tmpDir, + ); err != nil { + panic("failed to set metadata plugin data-dir: " + err.Error()) + } + + // Run tests + code := m.Run() + + // Clean up temp directory after tests finish + if err := os.RemoveAll(tmpDir); err != nil { + // Log cleanup error but don't fail since tests already ran + fmt.Fprintf( + os.Stderr, + "failed to clean up temp dir %s: %v\n", + tmpDir, + err, + ) + } + + os.Exit(code) +} + func TestPluginSystemIntegration(t *testing.T) { + // Ensure on-disk plugins use a temp dir to avoid collisions when tests run + // in parallel or on shared working directories. + if err := plugin.SetPluginOption(plugin.PluginTypeBlob, config.DefaultBlobPlugin, "data-dir", t.TempDir()); err != nil { + t.Fatalf("failed to set badger data-dir: %v", err) + } + if err := plugin.SetPluginOption(plugin.PluginTypeMetadata, config.DefaultMetadataPlugin, "data-dir", t.TempDir()); err != nil { + t.Fatalf("failed to set sqlite data-dir: %v", err) + } + // Test that all plugins are registered blobPlugins := plugin.GetPlugins(plugin.PluginTypeBlob) if len(blobPlugins) == 0 { @@ -40,12 +94,18 @@ func TestPluginSystemIntegration(t *testing.T) { } // Test that we can get specific plugins - badgerPlugin := plugin.GetPlugin(plugin.PluginTypeBlob, "badger") + badgerPlugin := plugin.GetPlugin( + plugin.PluginTypeBlob, + config.DefaultBlobPlugin, + ) if badgerPlugin == nil { t.Fatal("badger plugin not found") } - sqlitePlugin := plugin.GetPlugin(plugin.PluginTypeMetadata, "sqlite") + sqlitePlugin := plugin.GetPlugin( + plugin.PluginTypeMetadata, + config.DefaultMetadataPlugin, + ) if sqlitePlugin == nil { t.Fatal("sqlite plugin not found") } @@ -128,7 +188,7 @@ func TestPluginLifecycleEndToEnd(t *testing.T) { // 1. Verify plugins are registered blobPlugins := plugin.GetPlugins(plugin.PluginTypeBlob) // Check for expected plugins by name instead of count - expectedBlobs := []string{"badger", "gcs", "s3"} + expectedBlobs := []string{config.DefaultBlobPlugin, "gcs", "s3"} for _, name := range expectedBlobs { if findPluginEntry(blobPlugins, name) == nil { t.Errorf("expected blob plugin %q not found", name) @@ -137,7 +197,7 @@ func TestPluginLifecycleEndToEnd(t *testing.T) { metadataPlugins := plugin.GetPlugins(plugin.PluginTypeMetadata) // Check for expected metadata plugins - expectedMetadata := []string{"sqlite"} + expectedMetadata := []string{config.DefaultMetadataPlugin} for _, name := range expectedMetadata { if findPluginEntry(metadataPlugins, name) == nil { t.Errorf("expected metadata plugin %q not found", name) @@ -145,8 +205,14 @@ func TestPluginLifecycleEndToEnd(t *testing.T) { } // 2. Test instantiation of plugins that can work with defaults - // Badger should work with defaults (creates temp directory) - badgerPlugin := plugin.GetPlugin(plugin.PluginTypeBlob, "badger") + // Set data-dir to temp directories to avoid collisions + if err := plugin.SetPluginOption(plugin.PluginTypeBlob, config.DefaultBlobPlugin, "data-dir", t.TempDir()); err != nil { + t.Fatalf("failed to set badger data-dir: %v", err) + } + badgerPlugin := plugin.GetPlugin( + plugin.PluginTypeBlob, + config.DefaultBlobPlugin, + ) if badgerPlugin == nil { t.Error("failed to instantiate badger plugin") } else { @@ -159,8 +225,14 @@ func TestPluginLifecycleEndToEnd(t *testing.T) { } } + if err := plugin.SetPluginOption(plugin.PluginTypeMetadata, config.DefaultMetadataPlugin, "data-dir", t.TempDir()); err != nil { + t.Fatalf("failed to set sqlite data-dir: %v", err) + } // SQLite should work with defaults (creates temp database) - sqlitePlugin := plugin.GetPlugin(plugin.PluginTypeMetadata, "sqlite") + sqlitePlugin := plugin.GetPlugin( + plugin.PluginTypeMetadata, + config.DefaultMetadataPlugin, + ) if sqlitePlugin == nil { t.Error("failed to instantiate sqlite plugin") } else { @@ -181,28 +253,43 @@ func TestPluginConfigurationIntegration(t *testing.T) { // Test that plugin configuration works end-to-end // This tests the integration between config loading and plugin instantiation - // Create a temporary config that specifies plugins - configContent := ` -database: - blob: - plugin: "badger" - badger: - data-dir: "/tmp/test-config-badger" - block-cache-size: 1000000 - metadata: - plugin: "sqlite" - sqlite: - data-dir: "/tmp/test-config-sqlite.db" -` + // Create temp directories for test paths + tempDir := t.TempDir() + badgerDataDir := filepath.Join(tempDir, "test-config-badger") + sqliteDataDir := filepath.Join(tempDir, "test-config-sqlite.db") + + // Create a config structure and marshal to YAML (avoid formatting pitfalls) + cfgMap := map[string]any{ + "database": map[string]any{ + "blob": map[string]any{ + "plugin": config.DefaultBlobPlugin, + config.DefaultBlobPlugin: map[string]any{ + "data-dir": badgerDataDir, + "block-cache-size": 1000000, + }, + }, + "metadata": map[string]any{ + "plugin": config.DefaultMetadataPlugin, + config.DefaultMetadataPlugin: map[string]any{ + "data-dir": sqliteDataDir, + }, + }, + }, + } + + yamlBytes, err := yaml.Marshal(cfgMap) + if err != nil { + t.Fatalf("failed to marshal yaml: %v", err) + } // Write config to a temporary file tmpFile, err := os.CreateTemp("", "dingo-config-*.yaml") if err != nil { t.Fatalf("failed to create temp config file: %v", err) } - defer os.Remove(tmpFile.Name()) + t.Cleanup(func() { os.Remove(tmpFile.Name()) }) - if _, err := tmpFile.WriteString(configContent); err != nil { + if _, err := tmpFile.Write(yamlBytes); err != nil { t.Fatalf("failed to write config: %v", err) } tmpFile.Close() @@ -214,12 +301,17 @@ database: } // Verify that the config was loaded with the expected plugin settings - if cfg.BlobPlugin != "badger" { - t.Errorf("expected BlobPlugin to be 'badger', got '%s'", cfg.BlobPlugin) + if cfg.BlobPlugin != config.DefaultBlobPlugin { + t.Errorf( + "expected BlobPlugin to be '%s', got '%s'", + config.DefaultBlobPlugin, + cfg.BlobPlugin, + ) } - if cfg.MetadataPlugin != "sqlite" { + if cfg.MetadataPlugin != config.DefaultMetadataPlugin { t.Errorf( - "expected MetadataPlugin to be 'sqlite', got '%s'", + "expected MetadataPlugin to be '%s', got '%s'", + config.DefaultMetadataPlugin, cfg.MetadataPlugin, ) } @@ -239,7 +331,7 @@ func TestPluginSwitching(t *testing.T) { } // Test that we can retrieve different plugin entries - badgerEntry := findPluginEntry(blobPlugins, "badger") + badgerEntry := findPluginEntry(blobPlugins, config.DefaultBlobPlugin) if badgerEntry == nil { t.Error("badger plugin entry not found") }