From 11fa8761d0b5db4c4076cdbc135791463e133060 Mon Sep 17 00:00:00 2001 From: kangeunchan Date: Fri, 27 Feb 2026 13:10:46 +0900 Subject: [PATCH 1/2] refactor(di): consolidate to single container implementation Signed-off-by: kangeunchan --- internal/di/container_v2.go | 517 ------------------------------------ internal/di/factory.go | 75 ------ 2 files changed, 592 deletions(-) delete mode 100644 internal/di/container_v2.go diff --git a/internal/di/container_v2.go b/internal/di/container_v2.go deleted file mode 100644 index 43b7ea74..00000000 --- a/internal/di/container_v2.go +++ /dev/null @@ -1,517 +0,0 @@ -package di - -import ( - "context" - "sync" - - appbinary "github.com/altuslabsxyz/devnet-builder/internal/application/binary" - "github.com/altuslabsxyz/devnet-builder/internal/application/build" - appdevnet "github.com/altuslabsxyz/devnet-builder/internal/application/devnet" - "github.com/altuslabsxyz/devnet-builder/internal/application/ports" - "github.com/altuslabsxyz/devnet-builder/internal/application/upgrade" - "github.com/altuslabsxyz/devnet-builder/internal/di/providers" - "github.com/altuslabsxyz/devnet-builder/internal/infrastructure/network" - "github.com/altuslabsxyz/devnet-builder/internal/infrastructure/plugin" - "github.com/altuslabsxyz/devnet-builder/internal/output" -) - -// ContainerV2 is a refactored dependency injection container that composes -// domain-specific providers. This design follows Clean Architecture and SOLID -// principles by separating concerns into cohesive provider groups. -// -// Benefits over the original Container: -// - Reduced complexity: 4 providers vs 43 direct fields -// - Better testability: Each provider can be mocked independently -// - Clearer boundaries: Domain-specific use cases are grouped together -// - Easier maintenance: Changes to one domain don't affect others -type ContainerV2 struct { - mu sync.RWMutex - - // Configuration - config *Config - - // Core dependencies (always available) - logger *output.Logger - networkReg *NetworkRegistry - pluginMgr *plugin.PluginManager - - // Infrastructure provider - shared by all use case providers - infra providers.InfrastructureProvider - - // Domain-specific use case providers (lazy initialized) - devnetProvider providers.DevnetUseCasesProvider - upgradeProvider providers.UpgradeUseCasesProvider - buildProvider providers.BuildUseCasesProvider - binaryProvider providers.BinaryUseCasesProvider -} - -// OptionV2 is a function that configures ContainerV2. -type OptionV2 func(*ContainerV2) - -// WithLoggerV2 sets a custom logger. -func WithLoggerV2(logger *output.Logger) OptionV2 { - return func(c *ContainerV2) { - c.logger = logger - } -} - -// WithConfigV2 sets the configuration. -func WithConfigV2(config *Config) OptionV2 { - return func(c *ContainerV2) { - c.config = config - } -} - -// WithPluginManagerV2 sets a custom plugin manager. -func WithPluginManagerV2(pm *plugin.PluginManager) OptionV2 { - return func(c *ContainerV2) { - c.pluginMgr = pm - } -} - -// WithInfrastructureV2 sets the infrastructure provider. -func WithInfrastructureV2(infra providers.InfrastructureProvider) OptionV2 { - return func(c *ContainerV2) { - c.infra = infra - } -} - -// NewV2 creates a new ContainerV2 with the given options. -func NewV2(opts ...OptionV2) *ContainerV2 { - c := &ContainerV2{ - logger: output.NewLogger(), - networkReg: &NetworkRegistry{}, - config: &Config{}, - } - - for _, opt := range opts { - opt(c) - } - - // Apply config to logger - if c.config != nil { - c.logger.SetVerbose(c.config.Verbose) - c.logger.SetNoColor(c.config.NoColor) - c.logger.SetJSONMode(c.config.JSONMode) - } - - // Initialize plugin manager if not provided - if c.pluginMgr == nil && c.config.PluginDir != "" { - c.pluginMgr = plugin.NewPluginManager(c.config.PluginDir) - } - - return c -} - -// ensureProviders initializes the use case providers if not already set. -func (c *ContainerV2) ensureProviders() { - c.mu.Lock() - defer c.mu.Unlock() - - if c.infra == nil { - return // Cannot initialize providers without infrastructure - } - - if c.devnetProvider == nil { - c.devnetProvider = providers.NewDevnetUseCases(c.infra) - } - if c.upgradeProvider == nil { - c.upgradeProvider = providers.NewUpgradeUseCases(c.infra, c.config.HomeDir) - } - if c.buildProvider == nil { - c.buildProvider = providers.NewBuildUseCases(c.infra) - } - if c.binaryProvider == nil { - c.binaryProvider = providers.NewBinaryUseCases(c.infra) - } -} - -// ───────────────────────────────────────────────────────────────────────────── -// Core Accessors -// ───────────────────────────────────────────────────────────────────────────── - -// Logger returns the logger instance. -func (c *ContainerV2) Logger() *output.Logger { - return c.logger -} - -// LoggerPort returns the logger as a port interface. -func (c *ContainerV2) LoggerPort() ports.Logger { - return providers.NewLoggerAdapter(c.logger) -} - -// NetworkRegistry returns the network registry wrapper. -func (c *ContainerV2) NetworkRegistry() *NetworkRegistry { - return c.networkReg -} - -// PluginManager returns the plugin manager. -func (c *ContainerV2) PluginManager() *plugin.PluginManager { - return c.pluginMgr -} - -// Config returns the configuration. -func (c *ContainerV2) Config() *Config { - return c.config -} - -// Infrastructure returns the infrastructure provider. -func (c *ContainerV2) Infrastructure() providers.InfrastructureProvider { - return c.infra -} - -// ───────────────────────────────────────────────────────────────────────────── -// Provider Accessors -// ───────────────────────────────────────────────────────────────────────────── - -// DevnetProvider returns the devnet use cases provider. -func (c *ContainerV2) DevnetProvider() providers.DevnetUseCasesProvider { - c.ensureProviders() - return c.devnetProvider -} - -// UpgradeProvider returns the upgrade use cases provider. -func (c *ContainerV2) UpgradeProvider() providers.UpgradeUseCasesProvider { - c.ensureProviders() - return c.upgradeProvider -} - -// BuildProvider returns the build use cases provider. -func (c *ContainerV2) BuildProvider() providers.BuildUseCasesProvider { - c.ensureProviders() - return c.buildProvider -} - -// BinaryProvider returns the binary use cases provider. -func (c *ContainerV2) BinaryProvider() providers.BinaryUseCasesProvider { - c.ensureProviders() - return c.binaryProvider -} - -// ───────────────────────────────────────────────────────────────────────────── -// Direct Infrastructure Accessors (for backward compatibility) -// ───────────────────────────────────────────────────────────────────────────── - -// DevnetRepository returns the devnet repository. -func (c *ContainerV2) DevnetRepository() ports.DevnetRepository { - if c.infra == nil { - return nil - } - return c.infra.DevnetRepository() -} - -// NodeRepository returns the node repository. -func (c *ContainerV2) NodeRepository() ports.NodeRepository { - if c.infra == nil { - return nil - } - return c.infra.NodeRepository() -} - -// ExportRepository returns the export repository. -func (c *ContainerV2) ExportRepository() ports.ExportRepository { - if c.infra == nil { - return nil - } - return c.infra.ExportRepository() -} - -// Executor returns the process executor. -func (c *ContainerV2) Executor() ports.ProcessExecutor { - if c.infra == nil { - return nil - } - return c.infra.ProcessExecutor() -} - -// HealthChecker returns the health checker. -func (c *ContainerV2) HealthChecker() ports.HealthChecker { - if c.infra == nil { - return nil - } - return c.infra.HealthChecker() -} - -// NetworkModule returns the network module. -func (c *ContainerV2) NetworkModule() ports.NetworkModule { - if c.infra == nil { - return nil - } - return c.infra.NetworkModule() -} - -// GitHubClient returns the GitHub client. -func (c *ContainerV2) GitHubClient() ports.GitHubClient { - if c.infra == nil { - return nil - } - return c.infra.GitHubClient() -} - -// InteractiveSelector returns the interactive selector. -func (c *ContainerV2) InteractiveSelector() ports.InteractiveSelector { - if c.infra == nil { - return nil - } - return c.infra.InteractiveSelector() -} - -// BinaryCache returns the binary cache. -func (c *ContainerV2) BinaryCache() ports.BinaryCache { - if c.infra == nil { - return nil - } - return c.infra.BinaryCache() -} - -// Builder returns the builder. -func (c *ContainerV2) Builder() ports.Builder { - if c.infra == nil { - return nil - } - return c.infra.Builder() -} - -// ValidatorKeyLoader returns the validator key loader. -func (c *ContainerV2) ValidatorKeyLoader() ports.ValidatorKeyLoader { - if c.infra == nil { - return nil - } - return c.infra.ValidatorKeyLoader() -} - -// EVMClient returns the EVM client. -func (c *ContainerV2) EVMClient() ports.EVMClient { - if c.infra == nil { - return nil - } - return c.infra.EVMClient() -} - -// RPCClient returns the RPC client. -func (c *ContainerV2) RPCClient() ports.RPCClient { - if c.infra == nil { - return nil - } - return c.infra.RPCClient() -} - -// NodeInitializer returns the node initializer. -func (c *ContainerV2) NodeInitializer() ports.NodeInitializer { - if c.infra == nil { - return nil - } - return c.infra.NodeInitializer() -} - -// ───────────────────────────────────────────────────────────────────────────── -// Use Case Accessors (for backward compatibility) -// These delegate to the domain-specific providers. -// ───────────────────────────────────────────────────────────────────────────── - -// ProvisionUseCase returns the provision use case. -func (c *ContainerV2) ProvisionUseCase() *appdevnet.ProvisionUseCase { - return c.DevnetProvider().ProvisionUseCase() -} - -// RunUseCase returns the run use case. -func (c *ContainerV2) RunUseCase() *appdevnet.RunUseCase { - return c.DevnetProvider().RunUseCase() -} - -// StopUseCase returns the stop use case. -func (c *ContainerV2) StopUseCase() *appdevnet.StopUseCase { - return c.DevnetProvider().StopUseCase() -} - -// HealthUseCase returns the health use case. -func (c *ContainerV2) HealthUseCase() *appdevnet.HealthUseCase { - return c.DevnetProvider().HealthUseCase() -} - -// ResetUseCase returns the reset use case. -func (c *ContainerV2) ResetUseCase() *appdevnet.ResetUseCase { - return c.DevnetProvider().ResetUseCase() -} - -// DestroyUseCase returns the destroy use case. -func (c *ContainerV2) DestroyUseCase() *appdevnet.DestroyUseCase { - return c.DevnetProvider().DestroyUseCase() -} - -// ExportUseCase returns the export use case. -func (c *ContainerV2) ExportUseCase(ctx context.Context) *appdevnet.ExportUseCase { - return c.DevnetProvider().ExportUseCase(ctx) -} - -// NodeLifecycleManager returns the node lifecycle manager. -func (c *ContainerV2) NodeLifecycleManager() ports.NodeLifecycleManager { - return c.DevnetProvider().NodeLifecycleManager() -} - -// ProposeUseCase returns the propose use case. -func (c *ContainerV2) ProposeUseCase() *upgrade.ProposeUseCase { - return c.UpgradeProvider().ProposeUseCase() -} - -// VoteUseCase returns the vote use case. -func (c *ContainerV2) VoteUseCase() *upgrade.VoteUseCase { - return c.UpgradeProvider().VoteUseCase() -} - -// SwitchBinaryUseCase returns the switch binary use case. -func (c *ContainerV2) SwitchBinaryUseCase() *upgrade.SwitchBinaryUseCase { - return c.UpgradeProvider().SwitchBinaryUseCase() -} - -// ExecuteUpgradeUseCase returns the execute upgrade use case. -func (c *ContainerV2) ExecuteUpgradeUseCase() *upgrade.ExecuteUpgradeUseCase { - return c.UpgradeProvider().ExecuteUpgradeUseCase(c.DevnetProvider()) -} - -// MonitorUseCase returns the monitor use case. -func (c *ContainerV2) MonitorUseCase() *upgrade.MonitorUseCase { - return c.UpgradeProvider().MonitorUseCase() -} - -// ResumableExecuteUpgradeUseCase returns the resumable execute upgrade use case. -func (c *ContainerV2) ResumableExecuteUpgradeUseCase() *upgrade.ResumableExecuteUpgradeUseCase { - return c.UpgradeProvider().ResumableExecuteUseCase(c.DevnetProvider()) -} - -// ResumeUseCase returns the resume use case. -func (c *ContainerV2) ResumeUseCase() *upgrade.ResumeUseCase { - return c.UpgradeProvider().ResumeUseCase(c.DevnetProvider(), c.logger) -} - -// BuildUseCase returns the build use case. -func (c *ContainerV2) BuildUseCase() *build.BuildUseCase { - return c.BuildProvider().BuildUseCase() -} - -// CacheListUseCase returns the cache list use case. -func (c *ContainerV2) CacheListUseCase() *build.CacheListUseCase { - return c.BuildProvider().CacheListUseCase() -} - -// CacheCleanUseCase returns the cache clean use case. -func (c *ContainerV2) CacheCleanUseCase() *build.CacheCleanUseCase { - return c.BuildProvider().CacheCleanUseCase() -} - -// PassthroughUseCase returns the binary passthrough use case. -func (c *ContainerV2) PassthroughUseCase() *appbinary.PassthroughUseCase { - return c.BinaryProvider().PassthroughUseCase() -} - -// ImportCustomBinaryUseCase returns the import custom binary use case. -func (c *ContainerV2) ImportCustomBinaryUseCase() *appbinary.ImportCustomBinaryUseCase { - return c.BinaryProvider().ImportCustomBinaryUseCase(c.config.HomeDir) -} - -// ───────────────────────────────────────────────────────────────────────────── -// Setters (for runtime updates) -// ───────────────────────────────────────────────────────────────────────────── - -// SetNetworkModule sets the network module at runtime. -// Note: This is a transitional method. In the new architecture, the network -// module should be set when creating the InfrastructureProvider. -func (c *ContainerV2) SetNetworkModule(module ports.NetworkModule) { - // This method exists for backward compatibility but is a no-op in V2 - // since the network module is set through the infrastructure provider. - c.logger.Warn("SetNetworkModule called on ContainerV2 - this is deprecated. " + - "Set the network module when creating the InfrastructureProvider instead.") -} - -// SetBinaryResolver sets the binary resolver. -// Note: This is a transitional method. In the new architecture, the binary -// resolver should be set when creating the InfrastructureProvider. -func (c *ContainerV2) SetBinaryResolver(resolver ports.BinaryResolver) { - // This method exists for backward compatibility but is a no-op in V2 - c.logger.Warn("SetBinaryResolver called on ContainerV2 - this is deprecated. " + - "Set the binary resolver when creating the InfrastructureProvider instead.") -} - -// ───────────────────────────────────────────────────────────────────────────── -// NetworkRegistry Wrapper (unchanged from original) -// ───────────────────────────────────────────────────────────────────────────── - -// NetworkRegistryV2 wraps network registration operations. -// This provides an injectable alternative to the global registry. -type NetworkRegistryV2 struct{} - -// Get retrieves a network module by name. -func (r *NetworkRegistryV2) Get(name string) (network.NetworkModule, error) { - return network.Get(name) -} - -// Has checks if a network is registered. -func (r *NetworkRegistryV2) Has(name string) bool { - return network.Has(name) -} - -// List returns all registered network names. -func (r *NetworkRegistryV2) List() []string { - return network.List() -} - -// ListModules returns all registered network modules. -func (r *NetworkRegistryV2) ListModules() []network.NetworkModule { - return network.ListModules() -} - -// Default returns the default network module. -func (r *NetworkRegistryV2) Default() (network.NetworkModule, error) { - return network.Default() -} - -// SetDefault changes the default network name. -func (r *NetworkRegistryV2) SetDefault(name string) error { - return network.SetDefault(name) -} - -// ───────────────────────────────────────────────────────────────────────────── -// Compatibility Layer -// ───────────────────────────────────────────────────────────────────────────── - -// ToLegacyContainer creates a legacy Container from ContainerV2. -// This is provided for backward compatibility during migration. -// -// Deprecated: Use ContainerV2 directly instead. -func (c *ContainerV2) ToLegacyContainer() *Container { - opts := []Option{ - WithLogger(c.logger), - WithConfig(c.config), - } - - if c.pluginMgr != nil { - opts = append(opts, WithPluginManager(c.pluginMgr)) - } - - if c.infra != nil { - opts = append(opts, - WithDevnetRepository(c.infra.DevnetRepository()), - WithNodeRepository(c.infra.NodeRepository()), - WithExportRepository(c.infra.ExportRepository()), - WithExecutor(c.infra.ProcessExecutor()), - WithBinaryCache(c.infra.BinaryCache()), - WithBinaryVersionDetector(c.infra.BinaryVersionDetector()), - WithRPCClient(c.infra.RPCClient()), - WithEVMClient(c.infra.EVMClient()), - WithSnapshotFetcher(c.infra.SnapshotFetcher()), - WithGenesisFetcher(c.infra.GenesisFetcher()), - WithStateExportService(c.infra.StateExportService()), - WithNodeInitializer(c.infra.NodeInitializer()), - WithHealthChecker(c.infra.HealthChecker()), - WithBuilder(c.infra.Builder()), - WithValidatorKeyLoader(c.infra.ValidatorKeyLoader()), - WithGitHubClient(c.infra.GitHubClient()), - WithInteractiveSelector(c.infra.InteractiveSelector()), - WithBinaryExecutor(c.infra.BinaryExecutor()), - WithBinaryResolver(c.infra.BinaryResolver()), - WithNetworkModule(c.infra.NetworkModule()), - ) - } - - return New(opts...) -} diff --git a/internal/di/factory.go b/internal/di/factory.go index da7fa5a6..79415399 100644 --- a/internal/di/factory.go +++ b/internal/di/factory.go @@ -6,7 +6,6 @@ import ( "github.com/altuslabsxyz/devnet-builder/internal/application/ports" appversion "github.com/altuslabsxyz/devnet-builder/internal/application/version" - "github.com/altuslabsxyz/devnet-builder/internal/di/providers" "github.com/altuslabsxyz/devnet-builder/internal/infrastructure/binary" infrabuilder "github.com/altuslabsxyz/devnet-builder/internal/infrastructure/builder" infracache "github.com/altuslabsxyz/devnet-builder/internal/infrastructure/cache" @@ -580,77 +579,3 @@ func (f *InfrastructureFactory) CreateBinaryResolver(loader *plugin.Loader, cach func (f *InfrastructureFactory) CreateBinaryExecutor() ports.BinaryExecutor { return binary.NewPassthroughExecutor() } - -// WireContainerV2 wires all infrastructure components into a ContainerV2. -// This method creates the new provider-based container architecture. -func (f *InfrastructureFactory) WireContainerV2(opts ...OptionV2) (*ContainerV2, error) { - // Create all infrastructure implementations - devnetRepo := f.CreateDevnetRepository() - nodeRepo := f.CreateNodeRepository() - exportRepo := f.CreateExportRepository() - executor := f.CreateProcessExecutor() - snapshotFetcher := f.CreateSnapshotFetcher() - genesisFetcher := f.CreateGenesisFetcher() - stateExportSvc := f.CreateStateExportService() - nodeInitializer := f.CreateNodeInitializer() - builder := f.CreateBuilder() - - binaryCache, err := f.CreateBinaryCache() - if err != nil { - return nil, err - } - - binaryVersionDetector := f.CreateBinaryVersionDetector() - rpcClient := f.CreateRPCClient("localhost", 26657) - healthChecker := f.CreateHealthChecker(26657) - evmClient := f.CreateEVMClient("http://localhost:8545") - validatorKeyLoader := f.CreateValidatorKeyLoader() - githubClient := f.CreateGitHubClient() - interactiveSelector := f.CreateInteractiveSelector() - binaryExecutor := f.CreateBinaryExecutor() - - // Build infrastructure provider config - infraCfg := providers.InfrastructureConfig{ - DevnetRepo: devnetRepo, - NodeRepo: nodeRepo, - ExportRepo: exportRepo, - Executor: executor, - HealthChecker: healthChecker, - RPCClient: rpcClient, - EVMClient: evmClient, - SnapshotSvc: snapshotFetcher, - GenesisSvc: genesisFetcher, - StateExportSvc: stateExportSvc, - NodeInitializer: nodeInitializer, - ValidatorKeyLoader: validatorKeyLoader, - Builder: builder, - BinaryCache: binaryCache, - BinaryExecutor: binaryExecutor, - BinaryVersionDetector: binaryVersionDetector, - GitHubClient: githubClient, - InteractiveSelector: interactiveSelector, - Logger: providers.NewLoggerAdapter(f.logger), - } - - // Add network module adapter if available - if f.module != nil { - infraCfg.NetworkModule = &networkModuleAdapter{module: f.module} - } - - // Create infrastructure provider - infra := providers.NewInfrastructure(infraCfg) - - // Build container options - allOpts := []OptionV2{ - WithLoggerV2(f.logger), - WithInfrastructureV2(infra), - WithConfigV2(&Config{ - HomeDir: f.homeDir, - }), - } - - // Append user-provided options - allOpts = append(allOpts, opts...) - - return NewV2(allOpts...), nil -} From a0e1065d1be6ff358d6c923f0c97ccf96e874360 Mon Sep 17 00:00:00 2001 From: kangeunchan Date: Fri, 27 Feb 2026 13:10:51 +0900 Subject: [PATCH 2/2] test(di): add coverage for container wiring and adapters Signed-off-by: kangeunchan --- internal/di/container_test.go | 106 ++++++++++++++ internal/di/factory_test.go | 254 ++++++++++++++++++++++++++++++++++ 2 files changed, 360 insertions(+) create mode 100644 internal/di/container_test.go create mode 100644 internal/di/factory_test.go diff --git a/internal/di/container_test.go b/internal/di/container_test.go new file mode 100644 index 00000000..2471b6bd --- /dev/null +++ b/internal/di/container_test.go @@ -0,0 +1,106 @@ +package di + +import ( + "context" + "testing" + + "github.com/altuslabsxyz/devnet-builder/internal/output" +) + +func TestNewContainerDefaults(t *testing.T) { + c := New() + if c == nil { + t.Fatal("expected container") + } + if c.Logger() == nil { + t.Fatal("expected default logger") + } + if c.Config() == nil { + t.Fatal("expected default config") + } + if c.NetworkRegistry() == nil { + t.Fatal("expected network registry wrapper") + } +} + +func TestWithConfigInitializesPluginManager(t *testing.T) { + c := New(WithConfig(&Config{PluginDir: t.TempDir()})) + if c.PluginManager() == nil { + t.Fatal("expected plugin manager to be initialized when PluginDir is set") + } +} + +func TestWireContainerBuildsCoreDependencies(t *testing.T) { + factory := NewInfrastructureFactory(t.TempDir(), output.NewLogger()).WithNetworkModule(&testNetworkModule{}) + container, err := factory.WireContainer() + if err != nil { + t.Fatalf("WireContainer failed: %v", err) + } + + if container.DevnetRepository() == nil { + t.Fatal("expected devnet repository") + } + if container.NodeRepository() == nil { + t.Fatal("expected node repository") + } + if container.ProvisionUseCase() == nil { + t.Fatal("expected provision use case") + } + if container.ExecuteUpgradeUseCase() == nil { + t.Fatal("expected execute upgrade use case") + } +} + +func TestContainerLazyAccessors(t *testing.T) { + factory := NewInfrastructureFactory(t.TempDir(), output.NewLogger()).WithNetworkModule(&testNetworkModule{}) + container, err := factory.WireContainer() + if err != nil { + t.Fatalf("WireContainer failed: %v", err) + } + + container.SetNetworkModule(nil) + container.SetBinaryResolver(nil) + + _ = container.Logger() + _ = container.LoggerPort() + _ = container.NetworkRegistry() + _ = container.PluginManager() + _ = container.Config() + _ = container.DevnetRepository() + _ = container.NodeRepository() + _ = container.ExportRepository() + _ = container.Executor() + _ = container.HealthChecker() + _ = container.NetworkModule() + _ = container.GitHubClient() + _ = container.InteractiveSelector() + _ = container.BinaryCache() + _ = container.Builder() + _ = container.ValidatorKeyLoader() + _ = container.EVMClient() + _ = container.RPCClient() + _ = container.NodeInitializer() + _ = container.ProvisionUseCase() + _ = container.RunUseCase() + _ = container.StopUseCase() + _ = container.HealthUseCase() + _ = container.ResetUseCase() + _ = container.DestroyUseCase() + _ = container.ProposeUseCase() + _ = container.VoteUseCase() + _ = container.SwitchBinaryUseCase() + _ = container.ExecuteUpgradeUseCase() + _ = container.MonitorUseCase() + _ = container.StateManager() + _ = container.StateTransitioner() + _ = container.StateDetector() + _ = container.ResumableExecuteUpgradeUseCase() + _ = container.ResumeUseCase() + _ = container.BuildUseCase() + _ = container.CacheListUseCase() + _ = container.CacheCleanUseCase() + _ = container.PassthroughUseCase() + _ = container.ImportCustomBinaryUseCase() + _ = container.NodeLifecycleManager() + _ = container.ExportUseCase(context.Background()) +} diff --git a/internal/di/factory_test.go b/internal/di/factory_test.go new file mode 100644 index 00000000..b24a0354 --- /dev/null +++ b/internal/di/factory_test.go @@ -0,0 +1,254 @@ +package di + +import ( + "context" + "testing" + + cosmoslog "cosmossdk.io/log" + "github.com/altuslabsxyz/devnet-builder/internal/application/ports" + "github.com/altuslabsxyz/devnet-builder/internal/infrastructure/network" + "github.com/altuslabsxyz/devnet-builder/internal/infrastructure/plugin" + "github.com/altuslabsxyz/devnet-builder/internal/output" + pkgnetwork "github.com/altuslabsxyz/devnet-builder/pkg/network" +) + +type testNetworkModule struct{} + +func (m *testNetworkModule) Name() string { return "testnet" } +func (m *testNetworkModule) DisplayName() string { return "Testnet" } +func (m *testNetworkModule) Version() string { return "1.0.0" } +func (m *testNetworkModule) BinaryName() string { return "testd" } +func (m *testNetworkModule) BinarySource() network.BinarySource { + return network.BinarySource{Type: network.BinarySourceGitHub, Owner: "owner", Repo: "repo"} +} +func (m *testNetworkModule) DefaultBinaryVersion() string { return "v1.0.0" } +func (m *testNetworkModule) GetBuildConfig(networkType string) (*pkgnetwork.BuildConfig, error) { + return &pkgnetwork.BuildConfig{Tags: []string{"netgo"}}, nil +} +func (m *testNetworkModule) Bech32Prefix() string { return "test" } +func (m *testNetworkModule) BaseDenom() string { return "utest" } +func (m *testNetworkModule) GenesisConfig() network.GenesisConfig { return network.GenesisConfig{} } +func (m *testNetworkModule) DefaultChainID() string { return "testnet-1" } +func (m *testNetworkModule) DockerImage() string { return "ghcr.io/example/testd" } +func (m *testNetworkModule) DockerImageTag(version string) string { return version } +func (m *testNetworkModule) DockerHomeDir() string { return "/home/testd" } +func (m *testNetworkModule) InitCommand(homeDir, chainID, moniker string) []string { + return []string{"init", moniker, "--chain-id", chainID} +} +func (m *testNetworkModule) StartCommand(homeDir string, networkMode string) []string { + return []string{"start"} +} +func (m *testNetworkModule) ExportCommand(homeDir string) []string { return []string{"export"} } +func (m *testNetworkModule) DefaultMoniker(index int) string { return "node0" } +func (m *testNetworkModule) DefaultNodeHome() string { return ".testd" } +func (m *testNetworkModule) PIDFileName() string { return "testd.pid" } +func (m *testNetworkModule) LogFileName() string { return "testd.log" } +func (m *testNetworkModule) ProcessPattern() string { return "testd" } +func (m *testNetworkModule) DefaultPorts() network.PortConfig { return network.DefaultPortConfig() } +func (m *testNetworkModule) ConfigDir(homeDir string) string { return homeDir + "/config" } +func (m *testNetworkModule) DataDir(homeDir string) string { return homeDir + "/data" } +func (m *testNetworkModule) KeyringDir(homeDir string, backend string) string { + return homeDir + "/keyring-" + backend +} +func (m *testNetworkModule) ModifyGenesis(genesis []byte, opts network.GenesisOptions) ([]byte, error) { + return genesis, nil +} +func (m *testNetworkModule) NewGenerator(config *network.GeneratorConfig, logger cosmoslog.Logger) (network.Generator, error) { + return nil, nil +} +func (m *testNetworkModule) DefaultGeneratorConfig() *network.GeneratorConfig { + return &network.GeneratorConfig{} +} +func (m *testNetworkModule) Validate() error { return nil } +func (m *testNetworkModule) SnapshotURL(networkType string) string { return "https://snapshot.example" } +func (m *testNetworkModule) RPCEndpoint(networkType string) string { return "https://rpc.example" } +func (m *testNetworkModule) AvailableNetworks() []string { return []string{"mainnet", "testnet"} } +func (m *testNetworkModule) GetConfigOverrides(nodeIndex int, opts network.NodeConfigOptions) ([]byte, []byte, error) { + return []byte("config"), []byte("app"), nil +} + +type fileCapableModule struct { + *testNetworkModule +} + +func (m *fileCapableModule) ModifyGenesisFile(inputPath, outputPath string, opts network.GenesisOptions) (int64, error) { + return 42, nil +} + +func TestOptionFunctionsApplyWithoutPanics(t *testing.T) { + c := New() + logger := output.NewLogger() + pm := plugin.NewPluginManager(t.TempDir()) + + opts := []Option{ + WithLogger(logger), + WithConfig(&Config{HomeDir: t.TempDir(), PluginDir: t.TempDir()}), + WithPluginManager(pm), + WithDevnetRepository((ports.DevnetRepository)(nil)), + WithNodeRepository((ports.NodeRepository)(nil)), + WithBinaryCache((ports.BinaryCache)(nil)), + WithExecutor((ports.ProcessExecutor)(nil)), + WithRPCClient((ports.RPCClient)(nil)), + WithEVMClient((ports.EVMClient)(nil)), + WithSnapshotFetcher((ports.SnapshotFetcher)(nil)), + WithGenesisFetcher((ports.GenesisFetcher)(nil)), + WithStateExportService((ports.StateExportService)(nil)), + WithNodeInitializer((ports.NodeInitializer)(nil)), + WithKeyManager((ports.KeyManager)(nil)), + WithHealthChecker((ports.HealthChecker)(nil)), + WithValidatorKeyLoader((ports.ValidatorKeyLoader)(nil)), + WithBuilder((ports.Builder)(nil)), + WithNetworkModule((ports.NetworkModule)(nil)), + WithGitHubClient((ports.GitHubClient)(nil)), + WithInteractiveSelector((ports.InteractiveSelector)(nil)), + WithBinaryResolver((ports.BinaryResolver)(nil)), + WithBinaryExecutor((ports.BinaryExecutor)(nil)), + WithExportRepository((ports.ExportRepository)(nil)), + WithBinaryVersionDetector((ports.BinaryVersionDetector)(nil)), + } + + for _, opt := range opts { + opt(c) + } + + if c.Logger() != logger { + t.Fatal("expected logger option to be applied") + } + if c.PluginManager() != pm { + t.Fatal("expected plugin manager option to be applied") + } +} + +func TestNetworkModuleAdapterMethods(t *testing.T) { + base := &testNetworkModule{} + adapter := &networkModuleAdapter{module: base} + + if adapter.Name() != "testnet" { + t.Fatalf("unexpected adapter name: %s", adapter.Name()) + } + if adapter.BinaryName() != "testd" { + t.Fatalf("unexpected binary name: %s", adapter.BinaryName()) + } + if adapter.DockerImageTag("v1") != "v1" { + t.Fatal("expected docker image tag passthrough") + } + if _, err := adapter.ModifyGenesis([]byte(`{}`), ports.GenesisModifyOptions{}); err != nil { + t.Fatalf("unexpected ModifyGenesis error: %v", err) + } + + portsCfg := adapter.DefaultPorts() + if portsCfg.PProf != 6060 || portsCfg.Rosetta != 8080 { + t.Fatalf("unexpected default extra ports: %+v", portsCfg) + } + + if _, err := adapter.ModifyGenesisFile("in", "out", ports.GenesisModifyOptions{}); err == nil { + t.Fatal("expected ModifyGenesisFile to fail when module does not support file modifier") + } + + fileAdapter := &networkModuleAdapter{module: &fileCapableModule{testNetworkModule: base}} + size, err := fileAdapter.ModifyGenesisFile("in", "out", ports.GenesisModifyOptions{}) + if err != nil { + t.Fatalf("unexpected file ModifyGenesisFile error: %v", err) + } + if size != 42 { + t.Fatalf("unexpected output size: %d", size) + } +} + +func TestInfrastructureFactoryCreateMethods(t *testing.T) { + logger := output.NewLogger() + module := &testNetworkModule{} + f := NewInfrastructureFactory(t.TempDir(), logger). + WithNetworkModule(module). + WithGitHubConfig("token", "owner", "repo") + + if f.CreateDevnetRepository() == nil { + t.Fatal("expected devnet repository") + } + if f.CreateNodeRepository() == nil { + t.Fatal("expected node repository") + } + if f.CreateExportRepository() == nil { + t.Fatal("expected export repository") + } + if f.CreateDockerExecutor() == nil { + t.Fatal("expected docker executor") + } + if f.CreateRPCClient("localhost", 26657) == nil { + t.Fatal("expected rpc client") + } + if _, err := f.CreateBinaryCache(); err != nil { + t.Fatalf("expected binary cache, got error: %v", err) + } + if f.CreateBinaryVersionDetector() == nil { + t.Fatal("expected version detector") + } + if f.CreateBuilder() == nil { + t.Fatal("expected builder") + } + if f.CreateSnapshotFetcher() == nil { + t.Fatal("expected snapshot fetcher") + } + if f.CreateGenesisFetcher() == nil { + t.Fatal("expected genesis fetcher") + } + if f.CreateStateExportService() == nil { + t.Fatal("expected state export service") + } + if f.CreateNodeInitializer() == nil { + t.Fatal("expected node initializer") + } + if f.CreateNodeManagerFactory() == nil { + t.Fatal("expected node manager factory") + } + if f.CreateGitHubClient() == nil { + t.Fatal("expected github client") + } + if f.CreateInteractiveSelector() == nil { + t.Fatal("expected interactive selector") + } + if f.CreateEVMClient("http://localhost:8545") == nil { + t.Fatal("expected evm client") + } + if f.CreateValidatorKeyLoader() == nil { + t.Fatal("expected validator key loader") + } + if f.CreateVersionRepository() == nil { + t.Fatal("expected version repository") + } + if f.CreateMigrationService() == nil { + t.Fatal("expected migration service") + } + if f.CreateBinaryExecutor() == nil { + t.Fatal("expected binary executor") + } + if f.CreateBinaryResolver(nil, nil) == nil { + t.Fatal("expected binary resolver") + } + + f.WithDockerMode(false) + if f.CreateProcessExecutor() == nil { + t.Fatal("expected local process executor") + } + f.WithDockerMode(true) + if f.CreateProcessExecutor() == nil { + t.Fatal("expected docker process executor") + } + + health := f.CreateHealthChecker(26657) + results, err := health.CheckAllNodes(context.Background(), []*ports.NodeMetadata{}) + if err != nil { + t.Fatalf("unexpected CheckAllNodes error: %v", err) + } + if len(results) != 0 { + t.Fatalf("expected 0 health results, got %d", len(results)) + } + + container, err := f.WireContainer() + if err != nil { + t.Fatalf("WireContainer failed: %v", err) + } + if container == nil { + t.Fatal("expected wired container") + } +}