From 32f1c50cc0a5ff08372c4c2d8f09d1ee33f48a04 Mon Sep 17 00:00:00 2001 From: Ryan VanGundy Date: Sat, 26 Apr 2025 07:08:40 -0400 Subject: [PATCH] Refactor controller --- cmd/check_test.go | 26 +- cmd/down_test.go | 203 ++++- cmd/hook_test.go | 18 +- cmd/install_test.go | 44 +- cmd/root.go | 7 +- cmd/root_test.go | 53 +- cmd/up_test.go | 59 +- cmd/version.go | 7 +- cmd/version_test.go | 5 + cmd/windsor/main.go | 2 +- pkg/controller/controller.go | 532 +++++++++++- pkg/controller/controller_test.go | 1103 +++++++++++++++++++++++- pkg/controller/mock_controller.go | 410 +++------ pkg/controller/mock_controller_test.go | 230 +++-- pkg/controller/real_controller.go | 300 ------- pkg/controller/real_controller_test.go | 620 ------------- pkg/network/mock_network.go | 16 + pkg/network/shims.go | 9 + 18 files changed, 2191 insertions(+), 1453 deletions(-) delete mode 100644 pkg/controller/real_controller.go delete mode 100644 pkg/controller/real_controller_test.go diff --git a/cmd/check_test.go b/cmd/check_test.go index 4afb5dff8..fe11a3046 100644 --- a/cmd/check_test.go +++ b/cmd/check_test.go @@ -35,6 +35,17 @@ func setupSafeCheckCmdMocks(optionalInjector ...di.Injector) MockSafeCheckCmdCom // Use the injector to create a mock controller mockController = ctrl.NewMockController(injector) + // Set up controller mock functions + mockController.InitializeFunc = func() error { return nil } + mockController.CreateCommonComponentsFunc = func() error { return nil } + mockController.InitializeComponentsFunc = func() error { return nil } + mockController.CreateProjectComponentsFunc = func() error { return nil } + + // Initialize the controller + if err := mockController.Initialize(); err != nil { + panic(fmt.Sprintf("Failed to initialize controller: %v", err)) + } + // Setup mock config handler mockConfigHandler := config.NewMockConfigHandler() mockConfigHandler.IsLoadedFunc = func() bool { return true } @@ -50,6 +61,11 @@ func setupSafeCheckCmdMocks(optionalInjector ...di.Injector) MockSafeCheckCmdCom mockShell.GetProjectRootFunc = func() (string, error) { return "/path/to/project/root", nil } injector.Register("shell", mockShell) + // Setup mock tools manager + mockToolsManager := tools.NewMockToolsManager() + mockToolsManager.CheckFunc = func() error { return nil } + injector.Register("toolsManager", mockToolsManager) + mocks := MockSafeCheckCmdComponents{ Injector: injector, MockController: mockController, @@ -57,10 +73,18 @@ func setupSafeCheckCmdMocks(optionalInjector ...di.Injector) MockSafeCheckCmdCom MockShell: mockShell, } - mocks.MockController.ResolveConfigHandlerFunc = func() config.ConfigHandler { + mockController.ResolveConfigHandlerFunc = func() config.ConfigHandler { return mocks.MockConfigHandler } + mockController.ResolveShellFunc = func() shell.Shell { + return mocks.MockShell + } + + mockController.ResolveToolsManagerFunc = func() tools.ToolsManager { + return mockToolsManager + } + return mocks } diff --git a/cmd/down_test.go b/cmd/down_test.go index 333b11f10..13cc2c736 100644 --- a/cmd/down_test.go +++ b/cmd/down_test.go @@ -1,6 +1,7 @@ package cmd import ( + "context" "fmt" "path/filepath" "strings" @@ -17,7 +18,7 @@ import ( type MockSafeDownCmdComponents struct { Injector di.Injector - MockController *ctrl.MockController + Controller ctrl.Controller MockConfigHandler *config.MockConfigHandler MockShell *shell.MockShell MockNetworkManager *network.MockNetworkManager @@ -25,25 +26,68 @@ type MockSafeDownCmdComponents struct { MockContainerRuntime *virt.MockVirt } -// setupSafeDownCmdMocks creates mock components for testing the down command -func setupSafeDownCmdMocks(optionalInjector ...di.Injector) MockSafeDownCmdComponents { - var mockController *ctrl.MockController - var injector di.Injector +// setupSafeDownCmdMocks creates mock components with a mock controller for testing failures +func setupSafeDownCmdMocks() MockSafeDownCmdComponents { + injector := di.NewInjector() + mockController := ctrl.NewMockController(injector) - // Use the provided injector if passed, otherwise create a new one - if len(optionalInjector) > 0 { - injector = optionalInjector[0] - } else { - injector = di.NewInjector() + // Setup mock config handler + mockConfigHandler := config.NewMockConfigHandler() + mockConfigHandler.SetFunc = func(key string, value any) error { + return nil } + mockConfigHandler.GetContextFunc = func() string { + return "test-context" + } + mockConfigHandler.IsLoadedFunc = func() bool { + return true + } + injector.Register("configHandler", mockConfigHandler) - // Use the injector to create a mock controller - mockController = ctrl.NewMockController(injector) + // Setup mock shell + mockShell := shell.NewMockShell() + injector.Register("shell", mockShell) - // Manually override and set up components - mockController.CreateCommonComponentsFunc = func() error { - return nil + // Setup mock network manager + mockNetworkManager := network.NewMockNetworkManager() + injector.Register("networkManager", mockNetworkManager) + + // Setup mock virtual machine + mockVirtualMachine := virt.NewMockVirt() + injector.Register("virtualMachine", mockVirtualMachine) + + // Setup mock container runtime + mockContainerRuntime := virt.NewMockVirt() + mockContainerRuntime.DownFunc = func() error { return nil } + injector.Register("containerRuntime", mockContainerRuntime) + + // Set up controller mock functions + mockController.InitializeFunc = func() error { return nil } + mockController.CreateCommonComponentsFunc = func() error { return nil } + mockController.InitializeComponentsFunc = func() error { return nil } + mockController.CreateVirtualizationComponentsFunc = func() error { return nil } + mockController.CreateServiceComponentsFunc = func() error { return nil } + mockController.CreateEnvComponentsFunc = func() error { return nil } + mockController.CreateStackComponentsFunc = func() error { return nil } + mockController.CreateSecretsProvidersFunc = func() error { return nil } + mockController.ResolveConfigHandlerFunc = func() config.ConfigHandler { return mockConfigHandler } + mockController.ResolveShellFunc = func() shell.Shell { return mockShell } + mockController.ResolveContainerRuntimeFunc = func() virt.ContainerRuntime { return mockContainerRuntime } + + return MockSafeDownCmdComponents{ + Injector: injector, + Controller: mockController, + MockConfigHandler: mockConfigHandler, + MockShell: mockShell, + MockNetworkManager: mockNetworkManager, + MockVirtualMachine: mockVirtualMachine, + MockContainerRuntime: mockContainerRuntime, } +} + +// setupSafeDownCmdWithRealController creates mock components with a real controller for testing DI resolution +func setupSafeDownCmdWithRealController() MockSafeDownCmdComponents { + injector := di.NewInjector() // Setup mock config handler mockConfigHandler := config.NewMockConfigHandler() @@ -72,11 +116,21 @@ func setupSafeDownCmdMocks(optionalInjector ...di.Injector) MockSafeDownCmdCompo // Setup mock container runtime mockContainerRuntime := virt.NewMockVirt() + mockContainerRuntime.DownFunc = func() error { return nil } injector.Register("containerRuntime", mockContainerRuntime) + // Create mock controller with mock components + mockController := ctrl.NewMockController(injector) + mockController.InitializeFunc = func() error { return nil } + mockController.CreateCommonComponentsFunc = func() error { return nil } + mockController.ResolveShellFunc = func() shell.Shell { return mockShell } + mockController.ResolveConfigHandlerFunc = func() config.ConfigHandler { return mockConfigHandler } + mockController.CreateSecretsProvidersFunc = func() error { return nil } + mockController.ResolveContainerRuntimeFunc = func() virt.ContainerRuntime { return mockContainerRuntime } + return MockSafeDownCmdComponents{ Injector: injector, - MockController: mockController, + Controller: mockController, MockConfigHandler: mockConfigHandler, MockShell: mockShell, MockNetworkManager: mockNetworkManager, @@ -93,13 +147,21 @@ func TestDownCmd(t *testing.T) { }) t.Run("Success", func(t *testing.T) { - // Given a set of mock components - mocks := setupSafeDownCmdMocks() + defer resetRootCmd() + + // Given a set of mock components with real controller + mocks := setupSafeDownCmdWithRealController() + if err := mocks.Controller.Initialize(); err != nil { + t.Fatalf("Failed to initialize controller: %v", err) + } + + // Set the controller in the command context + rootCmd.SetContext(context.WithValue(context.Background(), controllerKey, mocks.Controller)) // When the down command is executed output := captureStderr(func() { rootCmd.SetArgs([]string{"down"}) - if err := Execute(mocks.MockController); err != nil { + if err := Execute(mocks.Controller); err != nil { t.Fatalf("Execute() error = %v", err) } }) @@ -112,14 +174,23 @@ func TestDownCmd(t *testing.T) { }) t.Run("ErrorCreatingVirtualizationComponents", func(t *testing.T) { + defer resetRootCmd() + mocks := setupSafeDownCmdMocks() - mocks.MockController.CreateVirtualizationComponentsFunc = func() error { + mockController := mocks.Controller.(*ctrl.MockController) + mockController.CreateVirtualizationComponentsFunc = func() error { return fmt.Errorf("Error creating virtualization components: %w", fmt.Errorf("error creating virtualization components")) } + if err := mockController.Initialize(); err != nil { + t.Fatalf("Failed to initialize controller: %v", err) + } + + // Set the controller in the command context + rootCmd.SetContext(context.WithValue(context.Background(), controllerKey, mocks.Controller)) // Given a mock controller that returns an error when creating virtualization components rootCmd.SetArgs([]string{"down"}) - err := Execute(mocks.MockController) + err := Execute(mocks.Controller) // Then the error should contain the expected message if err == nil || !strings.Contains(err.Error(), "Error creating virtualization components: error creating virtualization components") { t.Fatalf("Expected error containing 'Error creating virtualization components: error creating virtualization components', got %v", err) @@ -127,14 +198,23 @@ func TestDownCmd(t *testing.T) { }) t.Run("ErrorInitializingComponents", func(t *testing.T) { + defer resetRootCmd() + mocks := setupSafeDownCmdMocks() - mocks.MockController.InitializeComponentsFunc = func() error { + mockController := mocks.Controller.(*ctrl.MockController) + mockController.InitializeComponentsFunc = func() error { return fmt.Errorf("Error initializing components: %w", fmt.Errorf("error initializing components")) } + if err := mockController.Initialize(); err != nil { + t.Fatalf("Failed to initialize controller: %v", err) + } + + // Set the controller in the command context + rootCmd.SetContext(context.WithValue(context.Background(), controllerKey, mocks.Controller)) // Given a mock controller that returns an error when initializing components rootCmd.SetArgs([]string{"down"}) - err := Execute(mocks.MockController) + err := Execute(mocks.Controller) // Then the error should contain the expected message if err == nil || !strings.Contains(err.Error(), "Error initializing components: error initializing components") { t.Fatalf("Expected error containing 'Error initializing components: error initializing components', got %v", err) @@ -142,10 +222,13 @@ func TestDownCmd(t *testing.T) { }) t.Run("ErrorResolvingConfigHandler", func(t *testing.T) { + defer resetRootCmd() + mocks := setupSafeDownCmdMocks() + mockController := mocks.Controller.(*ctrl.MockController) // Allows for reaching the third call of the function callCount := 0 - mocks.MockController.ResolveConfigHandlerFunc = func() config.ConfigHandler { + mockController.ResolveConfigHandlerFunc = func() config.ConfigHandler { callCount++ if callCount == 2 { return nil @@ -153,9 +236,12 @@ func TestDownCmd(t *testing.T) { return mocks.MockConfigHandler } - // Given a mock controller that returns nil on the second call to ResolveConfigHandler + // Set the controller in the command context + rootCmd.SetContext(context.WithValue(context.Background(), controllerKey, mocks.Controller)) + + // Given a mock controller that returns nil when resolving config handler rootCmd.SetArgs([]string{"down"}) - err := Execute(mocks.MockController) + err := Execute(mocks.Controller) // Then the error should contain the expected message if err == nil || !strings.Contains(err.Error(), "No config handler found") { t.Fatalf("Expected error containing 'No config handler found', got %v", err) @@ -164,13 +250,17 @@ func TestDownCmd(t *testing.T) { t.Run("ErrorResolvingContainerRuntime", func(t *testing.T) { mocks := setupSafeDownCmdMocks() - mocks.MockController.ResolveContainerRuntimeFunc = func() virt.ContainerRuntime { + mockController := mocks.Controller.(*ctrl.MockController) + if err := mockController.Initialize(); err != nil { + t.Fatalf("Failed to initialize controller: %v", err) + } + mockController.ResolveContainerRuntimeFunc = func() virt.ContainerRuntime { return nil } // Given a mock controller that returns nil when resolving the container runtime rootCmd.SetArgs([]string{"down"}) - err := Execute(mocks.MockController) + err := Execute(mocks.Controller) // Then the error should contain the expected message if err == nil || !strings.Contains(err.Error(), "No container runtime found") { t.Fatalf("Expected error containing 'No container runtime found', got %v", err) @@ -179,7 +269,11 @@ func TestDownCmd(t *testing.T) { t.Run("ErrorRunningContainerRuntimeDown", func(t *testing.T) { mocks := setupSafeDownCmdMocks() - mocks.MockController.ResolveContainerRuntimeFunc = func() virt.ContainerRuntime { + mockController := mocks.Controller.(*ctrl.MockController) + if err := mockController.Initialize(); err != nil { + t.Fatalf("Failed to initialize controller: %v", err) + } + mockController.ResolveContainerRuntimeFunc = func() virt.ContainerRuntime { mocks.MockContainerRuntime.DownFunc = func() error { return fmt.Errorf("Error running container runtime Down command: %w", fmt.Errorf("error running container runtime down")) } @@ -188,7 +282,7 @@ func TestDownCmd(t *testing.T) { // Given a mock container runtime that returns an error when running the Down command rootCmd.SetArgs([]string{"down"}) - err := Execute(mocks.MockController) + err := Execute(mocks.Controller) // Then the error should contain the expected message if err == nil || !strings.Contains(err.Error(), "Error running container runtime Down command: error running container runtime down") { t.Fatalf("Expected error containing 'Error running container runtime Down command: error running container runtime down', got %v", err) @@ -197,13 +291,17 @@ func TestDownCmd(t *testing.T) { t.Run("ErrorCleaningConfigArtifacts", func(t *testing.T) { mocks := setupSafeDownCmdMocks() + mockController := mocks.Controller.(*ctrl.MockController) + if err := mockController.Initialize(); err != nil { + t.Fatalf("Failed to initialize controller: %v", err) + } mocks.MockConfigHandler.CleanFunc = func() error { return fmt.Errorf("Error cleaning up context specific artifacts: %w", fmt.Errorf("error cleaning context artifacts")) } // Given a mock context handler that returns an error when cleaning context specific artifacts rootCmd.SetArgs([]string{"down", "--clean"}) - err := Execute(mocks.MockController) + err := Execute(mocks.Controller) // Then the error should contain the expected message if err == nil || !strings.Contains(err.Error(), "Error cleaning up context specific artifacts: error cleaning context artifacts") { t.Fatalf("Expected error containing 'Error cleaning up context specific artifacts: error cleaning context artifacts', got %v", err) @@ -212,6 +310,10 @@ func TestDownCmd(t *testing.T) { t.Run("ErrorDeletingVolumes", func(t *testing.T) { mocks := setupSafeDownCmdMocks() + mockController := mocks.Controller.(*ctrl.MockController) + if err := mockController.Initialize(); err != nil { + t.Fatalf("Failed to initialize controller: %v", err) + } mocks.MockShell.GetProjectRootFunc = func() (string, error) { return filepath.Join("mock", "project", "root"), nil } @@ -228,7 +330,7 @@ func TestDownCmd(t *testing.T) { // Given a mock osRemoveAll that returns an error when deleting the .volumes folder rootCmd.SetArgs([]string{"down", "--clean"}) - err := Execute(mocks.MockController) + err := Execute(mocks.Controller) // Then the error should contain the expected message if err == nil || !strings.Contains(err.Error(), "Error deleting .volumes folder") { t.Fatalf("Expected error containing 'Error deleting .volumes folder', got %v", err) @@ -237,6 +339,10 @@ func TestDownCmd(t *testing.T) { t.Run("SuccessDeletingVolumes", func(t *testing.T) { mocks := setupSafeDownCmdMocks() + mockController := mocks.Controller.(*ctrl.MockController) + if err := mockController.Initialize(); err != nil { + t.Fatalf("Failed to initialize controller: %v", err) + } mocks.MockShell.GetProjectRootFunc = func() (string, error) { return filepath.Join("mock", "project", "root"), nil } @@ -252,7 +358,7 @@ func TestDownCmd(t *testing.T) { // Given a mock shell that successfully deletes the .volumes folder output := captureStderr(func() { rootCmd.SetArgs([]string{"down", "--clean"}) - if err := Execute(mocks.MockController); err != nil { + if err := Execute(mocks.Controller); err != nil { t.Fatalf("Execute() error = %v", err) } }) @@ -266,6 +372,10 @@ func TestDownCmd(t *testing.T) { t.Run("ErrorGettingProjectRoot", func(t *testing.T) { mocks := setupSafeDownCmdMocks() + mockController := mocks.Controller.(*ctrl.MockController) + if err := mockController.Initialize(); err != nil { + t.Fatalf("Failed to initialize controller: %v", err) + } callCount := 0 mocks.MockShell.GetProjectRootFunc = func() (string, error) { callCount++ @@ -276,19 +386,22 @@ func TestDownCmd(t *testing.T) { } rootCmd.SetArgs([]string{"down", "--clean"}) - err := Execute(mocks.MockController) + err := Execute(mocks.Controller) if err == nil || !strings.Contains(err.Error(), "Error retrieving project root") { t.Fatalf("Expected error containing 'Error retrieving project root', got %v", err) } }) t.Run("ErrorConfigNotLoaded", func(t *testing.T) { - // Create mock components mocks := setupSafeDownCmdMocks() + mockController := mocks.Controller.(*ctrl.MockController) + if err := mockController.Initialize(); err != nil { + t.Fatalf("Failed to initialize controller: %v", err) + } mocks.MockConfigHandler.IsLoadedFunc = func() bool { return false } // When: the down command is executed - err := Execute(mocks.MockController) + err := Execute(mocks.Controller) // Then: it should return an error indicating the configuration is not loaded if err == nil || !strings.Contains(err.Error(), "No configuration is loaded. Is there a project to tear down?") { @@ -297,15 +410,18 @@ func TestDownCmd(t *testing.T) { }) t.Run("ErrorSettingEnvironmentVariables", func(t *testing.T) { - // Given a mock controller that returns an error when setting environment variables mocks := setupSafeDownCmdMocks() - mocks.MockController.SetEnvironmentVariablesFunc = func() error { + mockController := mocks.Controller.(*ctrl.MockController) + if err := mockController.Initialize(); err != nil { + t.Fatalf("Failed to initialize controller: %v", err) + } + mockController.SetEnvironmentVariablesFunc = func() error { return fmt.Errorf("mock environment variables error") } // When the down command is executed rootCmd.SetArgs([]string{"down"}) - err := Execute(mocks.MockController) + err := Execute(mocks.Controller) // Then the error should contain the expected message if err == nil || !strings.Contains(err.Error(), "Error setting environment variables: mock environment variables error") { @@ -314,10 +430,13 @@ func TestDownCmd(t *testing.T) { }) t.Run("SuccessSettingEnvironmentVariables", func(t *testing.T) { - // Given a mock controller where SetEnvironmentVariables is successful mocks := setupSafeDownCmdMocks() + mockController := mocks.Controller.(*ctrl.MockController) + if err := mockController.Initialize(); err != nil { + t.Fatalf("Failed to initialize controller: %v", err) + } setEnvVarsCalled := false - mocks.MockController.SetEnvironmentVariablesFunc = func() error { + mockController.SetEnvironmentVariablesFunc = func() error { setEnvVarsCalled = true return nil } @@ -325,7 +444,7 @@ func TestDownCmd(t *testing.T) { // When the down command is executed output := captureStderr(func() { rootCmd.SetArgs([]string{"down"}) - if err := Execute(mocks.MockController); err != nil { + if err := Execute(mocks.Controller); err != nil { t.Fatalf("Execute() error = %v", err) } }) diff --git a/cmd/hook_test.go b/cmd/hook_test.go index 85d703afb..7143e1c49 100644 --- a/cmd/hook_test.go +++ b/cmd/hook_test.go @@ -16,11 +16,22 @@ func setupSafeHookCmdMocks() *MockObjects { injector := di.NewInjector() mockController := ctrl.NewMockController(injector) - // Setup mock context handler + // Set up controller mock functions + mockController.InitializeFunc = func() error { return nil } + mockController.CreateCommonComponentsFunc = func() error { return nil } + mockController.InitializeComponentsFunc = func() error { return nil } + + // Initialize the controller + if err := mockController.Initialize(); err != nil { + panic(fmt.Sprintf("Failed to initialize controller: %v", err)) + } + + // Setup mock config handler mockConfigHandler := config.NewMockConfigHandler() mockConfigHandler.GetContextFunc = func() string { return "test-context" } + mockConfigHandler.IsLoadedFunc = func() bool { return true } injector.Register("configHandler", mockConfigHandler) mockShell := shell.NewMockShell() @@ -30,6 +41,11 @@ func setupSafeHookCmdMocks() *MockObjects { } return nil } + + mockController.ResolveConfigHandlerFunc = func() config.ConfigHandler { + return mockConfigHandler + } + mockController.ResolveShellFunc = func() shell.Shell { return mockShell } diff --git a/cmd/install_test.go b/cmd/install_test.go index 3fcfaf5a0..71f3c19ee 100644 --- a/cmd/install_test.go +++ b/cmd/install_test.go @@ -1,6 +1,7 @@ package cmd import ( + "context" "fmt" "strings" "testing" @@ -10,6 +11,7 @@ import ( ctrl "github.com/windsorcli/cli/pkg/controller" "github.com/windsorcli/cli/pkg/di" "github.com/windsorcli/cli/pkg/secrets" + "github.com/windsorcli/cli/pkg/shell" ) type InstallCmdComponents struct { @@ -35,9 +37,17 @@ func setupMockInstallCmdComponents(optionalInjector ...di.Injector) InstallCmdCo // Use the injector to create a mock controller controller = ctrl.NewMockController(injector) - // Manually override and set up components - controller.CreateProjectComponentsFunc = func() error { - return nil + // Set up controller mock functions + controller.InitializeFunc = func() error { return nil } + controller.CreateCommonComponentsFunc = func() error { return nil } + controller.InitializeComponentsFunc = func() error { return nil } + controller.CreateProjectComponentsFunc = func() error { return nil } + controller.CreateServiceComponentsFunc = func() error { return nil } + controller.CreateVirtualizationComponentsFunc = func() error { return nil } + + // Initialize the controller + if err := controller.Initialize(); err != nil { + panic(fmt.Sprintf("Failed to initialize controller: %v", err)) } // Setup mock config handler @@ -50,6 +60,19 @@ func setupMockInstallCmdComponents(optionalInjector ...di.Injector) InstallCmdCo } injector.Register("configHandler", configHandler) + // Setup mock shell + mockShell := shell.NewMockShell() + mockShell.GetProjectRootFunc = func() (string, error) { + return "/mock/project/root", nil + } + mockShell.CheckTrustedDirectoryFunc = func() error { + return nil + } + controller.ResolveShellFunc = func() shell.Shell { + return mockShell + } + injector.Register("shell", mockShell) + // Setup mock secrets provider secretsProvider := secrets.NewMockSecretsProvider(injector) controller.ResolveAllSecretsProvidersFunc = func() []secrets.SecretsProvider { @@ -86,6 +109,9 @@ func TestInstallCmd(t *testing.T) { // Initialize mocks mocks := setupMockInstallCmdComponents() + // Set the controller in the command context + rootCmd.SetContext(context.WithValue(context.Background(), controllerKey, mocks.Controller)) + // Capture the output using captureStdout output := captureStdout(func() { rootCmd.SetArgs([]string{"install"}) @@ -111,6 +137,9 @@ func TestInstallCmd(t *testing.T) { return fmt.Errorf("error creating project components") } + // Set the controller in the command context + rootCmd.SetContext(context.WithValue(context.Background(), controllerKey, mocks.Controller)) + // When the install command is executed rootCmd.SetArgs([]string{"install"}) err := Execute(mocks.Controller) @@ -134,6 +163,9 @@ func TestInstallCmd(t *testing.T) { return fmt.Errorf("error creating service components") } + // Set the controller in the command context + rootCmd.SetContext(context.WithValue(context.Background(), controllerKey, mocks.Controller)) + // When the install command is executed rootCmd.SetArgs([]string{"install"}) err := Execute(mocks.Controller) @@ -157,6 +189,9 @@ func TestInstallCmd(t *testing.T) { return fmt.Errorf("error creating virtualization components") } + // Set the controller in the command context + rootCmd.SetContext(context.WithValue(context.Background(), controllerKey, mocks.Controller)) + // When the install command is executed rootCmd.SetArgs([]string{"install"}) err := Execute(mocks.Controller) @@ -180,6 +215,9 @@ func TestInstallCmd(t *testing.T) { return nil } + // Set the controller in the command context + rootCmd.SetContext(context.WithValue(context.Background(), controllerKey, mocks.Controller)) + // When the install command is executed rootCmd.SetArgs([]string{"install"}) err := Execute(mocks.Controller) diff --git a/cmd/root.go b/cmd/root.go index 964cd7ad4..4a6d3003b 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -37,7 +37,12 @@ func preRunEInitializeCommonComponents(cmd *cobra.Command, args []string) error cmd.SetContext(cmd.Root().Context()) // Retrieve the controller from the context - controller := cmd.Context().Value(controllerKey).(ctrl.Controller) + controllerValue := cmd.Context().Value(controllerKey) + if controllerValue == nil { + return fmt.Errorf("No controller found in context") + } + + controller := controllerValue.(ctrl.Controller) // Initialize the controller if err := controller.Initialize(); err != nil { diff --git a/cmd/root_test.go b/cmd/root_test.go index 115f4cee0..e817bd917 100644 --- a/cmd/root_test.go +++ b/cmd/root_test.go @@ -81,15 +81,56 @@ func setupSafeRootMocks(optionalInjector ...di.Injector) *MockObjects { mockController := ctrl.NewMockController(injector) - mockShell := &shell.MockShell{} - mockEnvPrinter := &env.MockEnvPrinter{} - mockConfigHandler := config.NewMockConfigHandler() - mockSecretsProvider := &secrets.MockSecretsProvider{} + // Set up controller mock functions + mockController.InitializeFunc = func() error { return nil } + mockController.CreateCommonComponentsFunc = func() error { return nil } + mockController.InitializeComponentsFunc = func() error { return nil } + mockController.CreateProjectComponentsFunc = func() error { return nil } + mockController.CreateServiceComponentsFunc = func() error { return nil } + mockController.CreateVirtualizationComponentsFunc = func() error { return nil } + mockController.CreateSecretsProvidersFunc = func() error { return nil } + + // Initialize the controller + if err := mockController.Initialize(); err != nil { + panic(fmt.Sprintf("Failed to initialize controller: %v", err)) + } + // Setup mock shell + mockShell := shell.NewMockShell() + mockShell.GetProjectRootFunc = func() (string, error) { + return "/mock/project/root", nil + } + mockShell.CheckTrustedDirectoryFunc = func() error { + return nil + } + mockController.ResolveShellFunc = func() shell.Shell { + return mockShell + } + injector.Register("shell", mockShell) + + // Setup mock config handler + mockConfigHandler := config.NewMockConfigHandler() + mockConfigHandler.IsLoadedFunc = func() bool { + return true + } + mockConfigHandler.GetContextFunc = func() string { + return "test-context" + } + mockController.ResolveConfigHandlerFunc = func() config.ConfigHandler { + return mockConfigHandler + } injector.Register("configHandler", mockConfigHandler) - injector.Register("secretsProvider", mockSecretsProvider) - // No cleanup function is returned + // Setup mock env printer + mockEnvPrinter := env.NewMockEnvPrinter() + injector.Register("envPrinter", mockEnvPrinter) + + // Setup mock secrets provider + mockSecretsProvider := secrets.NewMockSecretsProvider(injector) + mockController.ResolveAllSecretsProvidersFunc = func() []secrets.SecretsProvider { + return []secrets.SecretsProvider{mockSecretsProvider} + } + injector.Register("secretsProvider", mockSecretsProvider) return &MockObjects{ Controller: mockController, diff --git a/cmd/up_test.go b/cmd/up_test.go index ae5144da9..31abd8504 100644 --- a/cmd/up_test.go +++ b/cmd/up_test.go @@ -44,9 +44,20 @@ func setupSafeUpCmdMocks(optionalInjector ...di.Injector) SafeUpCmdComponents { // Use the injector to create a mock controller mockController = ctrl.NewMockController(injector) - // Manually override and set up components - mockController.CreateCommonComponentsFunc = func() error { - return nil + // Set up controller mock functions + mockController.InitializeFunc = func() error { return nil } + mockController.CreateCommonComponentsFunc = func() error { return nil } + mockController.InitializeComponentsFunc = func() error { return nil } + mockController.CreateProjectComponentsFunc = func() error { return nil } + mockController.CreateServiceComponentsFunc = func() error { return nil } + mockController.CreateVirtualizationComponentsFunc = func() error { return nil } + mockController.CreateEnvComponentsFunc = func() error { return nil } + mockController.CreateStackComponentsFunc = func() error { return nil } + mockController.CreateSecretsProvidersFunc = func() error { return nil } + + // Initialize the controller + if err := mockController.Initialize(); err != nil { + panic(fmt.Sprintf("Failed to initialize controller: %v", err)) } // Setup mock config handler @@ -72,28 +83,70 @@ func setupSafeUpCmdMocks(optionalInjector ...di.Injector) SafeUpCmdComponents { mockConfigHandler.IsLoadedFunc = func() bool { return true } + mockController.ResolveConfigHandlerFunc = func() config.ConfigHandler { + return mockConfigHandler + } injector.Register("configHandler", mockConfigHandler) // Setup mock shell mockShell := shell.NewMockShell() + mockShell.GetProjectRootFunc = func() (string, error) { + return "/mock/project/root", nil + } + mockShell.CheckTrustedDirectoryFunc = func() error { + return nil + } + mockController.ResolveShellFunc = func() shell.Shell { + return mockShell + } injector.Register("shell", mockShell) // Setup mock network manager mockNetworkManager := network.NewMockNetworkManager() + mockController.ResolveNetworkManagerFunc = func() network.NetworkManager { + return mockNetworkManager + } injector.Register("networkManager", mockNetworkManager) // Setup mock virtual machine mockVirtualMachine := virt.NewMockVirt() + mockController.ResolveVirtualMachineFunc = func() virt.VirtualMachine { + return mockVirtualMachine + } injector.Register("virtualMachine", mockVirtualMachine) // Setup mock container runtime mockContainerRuntime := virt.NewMockVirt() + mockController.ResolveContainerRuntimeFunc = func() virt.ContainerRuntime { + return mockContainerRuntime + } injector.Register("containerRuntime", mockContainerRuntime) // Setup mock tools manager mockToolsManager := tools.NewMockToolsManager() injector.Register("toolsManager", mockToolsManager) + // Setup mock stack manager + mockStackManager := stack.NewMockStack(injector) + mockController.ResolveStackFunc = func() stack.Stack { + return mockStackManager + } + injector.Register("stackManager", mockStackManager) + + // Setup mock blueprint handler + mockBlueprintHandler := bp.NewMockBlueprintHandler(injector) + mockController.ResolveBlueprintHandlerFunc = func() bp.BlueprintHandler { + return mockBlueprintHandler + } + injector.Register("blueprintHandler", mockBlueprintHandler) + + // Setup mock secrets provider + mockSecretsProvider := secrets.NewMockSecretsProvider(injector) + mockController.ResolveAllSecretsProvidersFunc = func() []secrets.SecretsProvider { + return []secrets.SecretsProvider{mockSecretsProvider} + } + injector.Register("secretsProvider", mockSecretsProvider) + return SafeUpCmdComponents{ Injector: injector, Controller: mockController, diff --git a/cmd/version.go b/cmd/version.go index 54c619801..d9e65a1c3 100644 --- a/cmd/version.go +++ b/cmd/version.go @@ -15,9 +15,10 @@ var ( // versionCmd represents the version command var versionCmd = &cobra.Command{ - Use: "version", - Short: "Display the current version", - Long: "Display the current version of the application", + Use: "version", + Short: "Display the current version", + Long: "Display the current version of the application", + PersistentPreRunE: func(cmd *cobra.Command, args []string) error { return nil }, Run: func(cmd *cobra.Command, args []string) { platform := fmt.Sprintf("%s/%s", runtime.GOOS, runtime.GOARCH) fmt.Printf("Version: %s\nCommit SHA: %s\nPlatform: %s\n", version, commitSHA, platform) diff --git a/cmd/version_test.go b/cmd/version_test.go index f4003b72e..c423e4379 100644 --- a/cmd/version_test.go +++ b/cmd/version_test.go @@ -20,6 +20,11 @@ func TestVersionCommand(t *testing.T) { // Create a mock controller injector := di.NewInjector() mockController := ctrl.NewMockController(injector) + mockController.InitializeFunc = func() error { return nil } + mockController.CreateCommonComponentsFunc = func() error { return nil } + if err := mockController.Initialize(); err != nil { + t.Fatalf("Failed to initialize controller: %v", err) + } // When: the version command is executed output := captureStdout(func() { diff --git a/cmd/windsor/main.go b/cmd/windsor/main.go index 297edf178..aa23c878e 100644 --- a/cmd/windsor/main.go +++ b/cmd/windsor/main.go @@ -13,7 +13,7 @@ func main() { injector := di.NewInjector() // Create a new controller - controller := controller.NewRealController(injector) + controller := controller.NewController(injector) // Execute the root command and handle the error, // exiting with a non-zero exit code if there's an error diff --git a/pkg/controller/controller.go b/pkg/controller/controller.go index 414322ede..29cb25fc1 100644 --- a/pkg/controller/controller.go +++ b/pkg/controller/controller.go @@ -2,7 +2,12 @@ package controller import ( "fmt" + "net" + "os" + "path/filepath" + "strings" + secretsConfigType "github.com/windsorcli/cli/api/v1alpha1/secrets" "github.com/windsorcli/cli/pkg/blueprint" "github.com/windsorcli/cli/pkg/config" "github.com/windsorcli/cli/pkg/di" @@ -12,6 +17,7 @@ import ( "github.com/windsorcli/cli/pkg/secrets" "github.com/windsorcli/cli/pkg/services" "github.com/windsorcli/cli/pkg/shell" + "github.com/windsorcli/cli/pkg/ssh" "github.com/windsorcli/cli/pkg/stack" "github.com/windsorcli/cli/pkg/tools" "github.com/windsorcli/cli/pkg/virt" @@ -70,6 +76,281 @@ type Controller interface { type BaseController struct { injector di.Injector configHandler config.ConfigHandler + constructors ComponentConstructors +} + +// ComponentConstructors contains factory functions for all components used in the controller +type ComponentConstructors struct { + // Common components + NewYamlConfigHandler func(di.Injector) config.ConfigHandler + NewDefaultShell func(di.Injector) shell.Shell + NewSecureShell func(di.Injector) shell.Shell + + // Project components + NewGitGenerator func(di.Injector) generators.Generator + NewBlueprintHandler func(di.Injector) blueprint.BlueprintHandler + NewTerraformGenerator func(di.Injector) generators.Generator + NewKustomizeGenerator func(di.Injector) generators.Generator + NewToolsManager func(di.Injector) tools.ToolsManager + + // Environment printers + NewAwsEnvPrinter func(di.Injector) env.EnvPrinter + NewDockerEnvPrinter func(di.Injector) env.EnvPrinter + NewKubeEnvPrinter func(di.Injector) env.EnvPrinter + NewOmniEnvPrinter func(di.Injector) env.EnvPrinter + NewTalosEnvPrinter func(di.Injector) env.EnvPrinter + NewTerraformEnvPrinter func(di.Injector) env.EnvPrinter + NewWindsorEnvPrinter func(di.Injector) env.EnvPrinter + + // Service components + NewDNSService func(di.Injector) services.Service + NewGitLivereloadService func(di.Injector) services.Service + NewLocalstackService func(di.Injector) services.Service + NewRegistryService func(di.Injector) services.Service + NewTalosService func(di.Injector, string) services.Service + + // Virtualization components + NewSSHClient func() *ssh.SSHClient + NewColimaVirt func(di.Injector) virt.VirtualMachine + NewColimaNetworkManager func(di.Injector) network.NetworkManager + NewBaseNetworkManager func(di.Injector) network.NetworkManager + NewDockerVirt func(di.Injector) virt.ContainerRuntime + NewNetworkInterfaceProvider func() network.NetworkInterfaceProvider + + // Secrets providers + NewSopsSecretsProvider func(string, di.Injector) secrets.SecretsProvider + NewOnePasswordSDKSecretsProvider func(secretsConfigType.OnePasswordVault, di.Injector) secrets.SecretsProvider + NewOnePasswordCLISecretsProvider func(secretsConfigType.OnePasswordVault, di.Injector) secrets.SecretsProvider + + // Stack components + NewWindsorStack func(di.Injector) stack.Stack +} + +// DefaultConstructors returns a ComponentConstructors with the default implementation +// of all factory functions +func DefaultConstructors() ComponentConstructors { + return ComponentConstructors{ + // Common components + NewYamlConfigHandler: func(injector di.Injector) config.ConfigHandler { + return config.NewYamlConfigHandler(injector) + }, + NewDefaultShell: func(injector di.Injector) shell.Shell { + return shell.NewDefaultShell(injector) + }, + NewSecureShell: func(injector di.Injector) shell.Shell { + return shell.NewSecureShell(injector) + }, + + // Project components + NewGitGenerator: func(injector di.Injector) generators.Generator { + return generators.NewGitGenerator(injector) + }, + NewBlueprintHandler: func(injector di.Injector) blueprint.BlueprintHandler { + return blueprint.NewBlueprintHandler(injector) + }, + NewTerraformGenerator: func(injector di.Injector) generators.Generator { + return generators.NewTerraformGenerator(injector) + }, + NewKustomizeGenerator: func(injector di.Injector) generators.Generator { + return generators.NewKustomizeGenerator(injector) + }, + NewToolsManager: func(injector di.Injector) tools.ToolsManager { + return tools.NewToolsManager(injector) + }, + + // Environment printers + NewAwsEnvPrinter: func(injector di.Injector) env.EnvPrinter { + return env.NewAwsEnvPrinter(injector) + }, + NewDockerEnvPrinter: func(injector di.Injector) env.EnvPrinter { + return env.NewDockerEnvPrinter(injector) + }, + NewKubeEnvPrinter: func(injector di.Injector) env.EnvPrinter { + return env.NewKubeEnvPrinter(injector) + }, + NewOmniEnvPrinter: func(injector di.Injector) env.EnvPrinter { + return env.NewOmniEnvPrinter(injector) + }, + NewTalosEnvPrinter: func(injector di.Injector) env.EnvPrinter { + return env.NewTalosEnvPrinter(injector) + }, + NewTerraformEnvPrinter: func(injector di.Injector) env.EnvPrinter { + return env.NewTerraformEnvPrinter(injector) + }, + NewWindsorEnvPrinter: func(injector di.Injector) env.EnvPrinter { + return env.NewWindsorEnvPrinter(injector) + }, + + // Service components + NewDNSService: func(injector di.Injector) services.Service { + return services.NewDNSService(injector) + }, + NewGitLivereloadService: func(injector di.Injector) services.Service { + return services.NewGitLivereloadService(injector) + }, + NewLocalstackService: func(injector di.Injector) services.Service { + return services.NewLocalstackService(injector) + }, + NewRegistryService: func(injector di.Injector) services.Service { + return services.NewRegistryService(injector) + }, + NewTalosService: func(injector di.Injector, nodeType string) services.Service { + return services.NewTalosService(injector, nodeType) + }, + + // Virtualization components + NewSSHClient: func() *ssh.SSHClient { + return ssh.NewSSHClient() + }, + NewColimaVirt: func(injector di.Injector) virt.VirtualMachine { + return virt.NewColimaVirt(injector) + }, + NewColimaNetworkManager: func(injector di.Injector) network.NetworkManager { + return network.NewColimaNetworkManager(injector) + }, + NewBaseNetworkManager: func(injector di.Injector) network.NetworkManager { + return network.NewBaseNetworkManager(injector) + }, + NewDockerVirt: func(injector di.Injector) virt.ContainerRuntime { + return virt.NewDockerVirt(injector) + }, + NewNetworkInterfaceProvider: func() network.NetworkInterfaceProvider { + return network.NewNetworkInterfaceProvider() + }, + + // Secrets providers + NewSopsSecretsProvider: func(secretsFile string, injector di.Injector) secrets.SecretsProvider { + return secrets.NewSopsSecretsProvider(secretsFile, injector) + }, + NewOnePasswordSDKSecretsProvider: func(vault secretsConfigType.OnePasswordVault, injector di.Injector) secrets.SecretsProvider { + return secrets.NewOnePasswordSDKSecretsProvider(vault, injector) + }, + NewOnePasswordCLISecretsProvider: func(vault secretsConfigType.OnePasswordVault, injector di.Injector) secrets.SecretsProvider { + return secrets.NewOnePasswordCLISecretsProvider(vault, injector) + }, + + // Stack components + NewWindsorStack: func(injector di.Injector) stack.Stack { + return stack.NewWindsorStack(injector) + }, + } +} + +// MockConstructors returns a ComponentConstructors with all factory functions set to return mocks +// useful for testing +func MockConstructors() ComponentConstructors { + return ComponentConstructors{ + // Common components + NewYamlConfigHandler: func(injector di.Injector) config.ConfigHandler { + return config.NewMockConfigHandler() + }, + NewDefaultShell: func(injector di.Injector) shell.Shell { + return shell.NewMockShell() + }, + NewSecureShell: func(injector di.Injector) shell.Shell { + return shell.NewMockShell() + }, + + // Project components + NewGitGenerator: func(injector di.Injector) generators.Generator { + return generators.NewMockGenerator() + }, + NewBlueprintHandler: func(injector di.Injector) blueprint.BlueprintHandler { + return blueprint.NewMockBlueprintHandler(injector) + }, + NewTerraformGenerator: func(injector di.Injector) generators.Generator { + return generators.NewMockGenerator() + }, + NewKustomizeGenerator: func(injector di.Injector) generators.Generator { + return generators.NewMockGenerator() + }, + NewToolsManager: func(injector di.Injector) tools.ToolsManager { + return tools.NewMockToolsManager() + }, + + // Environment printers + NewAwsEnvPrinter: func(injector di.Injector) env.EnvPrinter { + return env.NewMockEnvPrinter() + }, + NewDockerEnvPrinter: func(injector di.Injector) env.EnvPrinter { + return env.NewMockEnvPrinter() + }, + NewKubeEnvPrinter: func(injector di.Injector) env.EnvPrinter { + return env.NewMockEnvPrinter() + }, + NewOmniEnvPrinter: func(injector di.Injector) env.EnvPrinter { + return env.NewMockEnvPrinter() + }, + NewTalosEnvPrinter: func(injector di.Injector) env.EnvPrinter { + return env.NewMockEnvPrinter() + }, + NewTerraformEnvPrinter: func(injector di.Injector) env.EnvPrinter { + return env.NewMockEnvPrinter() + }, + NewWindsorEnvPrinter: func(injector di.Injector) env.EnvPrinter { + return env.NewMockEnvPrinter() + }, + + // Service components + NewDNSService: func(injector di.Injector) services.Service { + return services.NewMockService() + }, + NewGitLivereloadService: func(injector di.Injector) services.Service { + return services.NewMockService() + }, + NewLocalstackService: func(injector di.Injector) services.Service { + return services.NewMockService() + }, + NewRegistryService: func(injector di.Injector) services.Service { + return services.NewMockService() + }, + NewTalosService: func(injector di.Injector, nodeType string) services.Service { + return services.NewMockService() + }, + + // Virtualization components + NewSSHClient: func() *ssh.SSHClient { + return ssh.NewSSHClient() + }, + NewColimaVirt: func(injector di.Injector) virt.VirtualMachine { + return virt.NewMockVirt() + }, + NewColimaNetworkManager: func(injector di.Injector) network.NetworkManager { + return network.NewMockNetworkManager() + }, + NewBaseNetworkManager: func(injector di.Injector) network.NetworkManager { + return network.NewMockNetworkManager() + }, + NewDockerVirt: func(_ di.Injector) virt.ContainerRuntime { + return virt.NewMockVirt() + }, + NewNetworkInterfaceProvider: func() network.NetworkInterfaceProvider { + return &network.MockNetworkInterfaceProvider{ + InterfacesFunc: func() ([]net.Interface, error) { + return nil, nil + }, + InterfaceAddrsFunc: func(iface net.Interface) ([]net.Addr, error) { + return nil, nil + }, + } + }, + + // Secrets providers + NewSopsSecretsProvider: func(configRoot string, injector di.Injector) secrets.SecretsProvider { + return secrets.NewMockSecretsProvider(injector) + }, + NewOnePasswordSDKSecretsProvider: func(vault secretsConfigType.OnePasswordVault, injector di.Injector) secrets.SecretsProvider { + return secrets.NewMockSecretsProvider(injector) + }, + NewOnePasswordCLISecretsProvider: func(vault secretsConfigType.OnePasswordVault, injector di.Injector) secrets.SecretsProvider { + return secrets.NewMockSecretsProvider(injector) + }, + + // Stack components + NewWindsorStack: func(injector di.Injector) stack.Stack { + return stack.NewMockStack(injector) + }, + } } // ============================================================================= @@ -77,8 +358,18 @@ type BaseController struct { // ============================================================================= // NewController creates a new controller. -func NewController(injector di.Injector) *BaseController { - return &BaseController{injector: injector} +func NewController(injector di.Injector, constructors ...ComponentConstructors) *BaseController { + var c ComponentConstructors + if len(constructors) > 0 { + c = constructors[0] + } else { + c = DefaultConstructors() + } + + return &BaseController{ + injector: injector, + constructors: c, + } } // ============================================================================= @@ -209,43 +500,262 @@ func (c *BaseController) InitializeComponents() error { // CreateCommonComponents creates the common components. func (c *BaseController) CreateCommonComponents() error { - // no-op + if c.injector == nil { + return fmt.Errorf("injector is nil") + } + if c.constructors.NewYamlConfigHandler == nil || c.constructors.NewDefaultShell == nil { + return fmt.Errorf("required constructors are nil") + } + configHandler := c.constructors.NewYamlConfigHandler(c.injector) + c.injector.Register("configHandler", configHandler) + c.configHandler = configHandler + + shell := c.constructors.NewDefaultShell(c.injector) + c.injector.Register("shell", shell) + + // Initialize the config handler + if err := configHandler.Initialize(); err != nil { + return fmt.Errorf("error initializing config handler: %w", err) + } + + // Initialize the shell + if err := shell.Initialize(); err != nil { + return fmt.Errorf("error initializing shell: %w", err) + } + return nil } -// CreateSecretsProvider creates the secrets provider. +// CreateSecretsProviders sets up the secrets provider based on config settings. +// It supports SOPS and 1Password CLI for decryption. +// Registers the appropriate secrets provider with the injector and config handler. func (c *BaseController) CreateSecretsProviders() error { - // no-op + contextName := c.configHandler.GetContext() + configRoot, err := c.configHandler.GetConfigRoot() + if err != nil { + return fmt.Errorf("error getting config root: %w", err) + } + + secretsFilePaths := []string{"secrets.enc.yaml", "secrets.enc.yml"} + for _, filePath := range secretsFilePaths { + if _, err := osStat(filepath.Join(configRoot, filePath)); err == nil { + sopsSecretsProvider := c.constructors.NewSopsSecretsProvider(configRoot, c.injector) + c.injector.Register("sopsSecretsProvider", sopsSecretsProvider) + c.configHandler.SetSecretsProvider(sopsSecretsProvider) + } + } + + vaults, ok := c.configHandler.Get(fmt.Sprintf("contexts.%s.secrets.onepassword.vaults", contextName)).(map[string]secretsConfigType.OnePasswordVault) + if ok && len(vaults) > 0 { + useSDK := os.Getenv("OP_SERVICE_ACCOUNT_TOKEN") != "" + + for key, vault := range vaults { + vault.ID = key + var opSecretsProvider secrets.SecretsProvider + + if useSDK { + opSecretsProvider = c.constructors.NewOnePasswordSDKSecretsProvider(vault, c.injector) + } else { + opSecretsProvider = c.constructors.NewOnePasswordCLISecretsProvider(vault, c.injector) + } + + c.injector.Register(fmt.Sprintf("op%sSecretsProvider", strings.ToUpper(key[:1])+key[1:]), opSecretsProvider) + c.configHandler.SetSecretsProvider(opSecretsProvider) + } + } + return nil } // CreateProjectComponents creates the project components. func (c *BaseController) CreateProjectComponents() error { - // no-op + if c.injector == nil { + return fmt.Errorf("injector is nil") + } + if c.configHandler == nil { + return fmt.Errorf("config handler is nil") + } + + gitGenerator := c.constructors.NewGitGenerator(c.injector) + c.injector.Register("gitGenerator", gitGenerator) + + blueprintHandler := c.constructors.NewBlueprintHandler(c.injector) + c.injector.Register("blueprintHandler", blueprintHandler) + + terraformGenerator := c.constructors.NewTerraformGenerator(c.injector) + c.injector.Register("terraformGenerator", terraformGenerator) + + kustomizeGenerator := c.constructors.NewKustomizeGenerator(c.injector) + c.injector.Register("kustomizeGenerator", kustomizeGenerator) + + toolsManagerType := c.configHandler.GetString("toolsManager") + var toolsManager tools.ToolsManager + + if toolsManagerType == "" { + var err error + toolsManagerType, err = tools.CheckExistingToolsManager(c.configHandler.GetString("projectRoot")) + if err != nil { + return fmt.Errorf("error checking existing tools manager: %w", err) + } + } + + switch toolsManagerType { + // Future implementations for different tools managers can go here + default: + toolsManager = c.constructors.NewToolsManager(c.injector) + } + + c.injector.Register("toolsManager", toolsManager) + return nil } // CreateEnvComponents creates the env components. func (c *BaseController) CreateEnvComponents() error { - // no-op + if c.injector == nil { + return fmt.Errorf("injector is nil") + } + if c.configHandler == nil { + return fmt.Errorf("config handler is nil") + } + + envPrinters := map[string]func(di.Injector) env.EnvPrinter{ + "awsEnv": c.constructors.NewAwsEnvPrinter, + "dockerEnv": c.constructors.NewDockerEnvPrinter, + "kubeEnv": c.constructors.NewKubeEnvPrinter, + "omniEnv": c.constructors.NewOmniEnvPrinter, + "talosEnv": c.constructors.NewTalosEnvPrinter, + "terraformEnv": c.constructors.NewTerraformEnvPrinter, + "windsorEnv": c.constructors.NewWindsorEnvPrinter, + } + + for key, constructor := range envPrinters { + if key == "awsEnv" && !c.configHandler.GetBool("aws.enabled") { + continue + } + if key == "dockerEnv" && !c.configHandler.GetBool("docker.enabled") { + continue + } + envPrinter := constructor(c.injector) + c.injector.Register(key, envPrinter) + } + return nil } // CreateServiceComponents creates the service components. func (c *BaseController) CreateServiceComponents() error { - // no-op + configHandler := c.configHandler + contextConfig := configHandler.GetConfig() + + if !configHandler.GetBool("docker.enabled") { + return nil + } + + dnsEnabled := configHandler.GetBool("dns.enabled") + if dnsEnabled { + dnsService := c.constructors.NewDNSService(c.injector) + dnsService.SetName("dns") + c.injector.Register("dnsService", dnsService) + } + + gitLivereloadEnabled := configHandler.GetBool("git.livereload.enabled") + if gitLivereloadEnabled { + gitLivereloadService := c.constructors.NewGitLivereloadService(c.injector) + gitLivereloadService.SetName("git") + c.injector.Register("gitLivereloadService", gitLivereloadService) + } + + localstackEnabled := configHandler.GetBool("aws.localstack.enabled") + if localstackEnabled { + localstackService := c.constructors.NewLocalstackService(c.injector) + localstackService.SetName("aws") + c.injector.Register("localstackService", localstackService) + } + + if contextConfig.Docker != nil && contextConfig.Docker.Registries != nil { + for key := range contextConfig.Docker.Registries { + service := c.constructors.NewRegistryService(c.injector) + service.SetName(key) + serviceName := fmt.Sprintf("registryService.%s", key) + c.injector.Register(serviceName, service) + } + } + + clusterEnabled := configHandler.GetBool("cluster.enabled") + if clusterEnabled { + controlPlaneCount := configHandler.GetInt("cluster.controlplanes.count") + workerCount := configHandler.GetInt("cluster.workers.count") + + clusterDriver := configHandler.GetString("cluster.driver") + + if clusterDriver == "talos" { + for i := 1; i <= controlPlaneCount; i++ { + controlPlaneService := c.constructors.NewTalosService(c.injector, "controlplane") + controlPlaneService.SetName(fmt.Sprintf("controlplane-%d", i)) + serviceName := fmt.Sprintf("clusterNode.controlplane-%d", i) + c.injector.Register(serviceName, controlPlaneService) + } + for i := 1; i <= workerCount; i++ { + workerService := c.constructors.NewTalosService(c.injector, "worker") + workerService.SetName(fmt.Sprintf("worker-%d", i)) + serviceName := fmt.Sprintf("clusterNode.worker-%d", i) + c.injector.Register(serviceName, workerService) + } + } + } + return nil } -// CreateVirtualizationComponents creates the virtualization components. +// CreateVirtualizationComponents creates virtualization components based on configuration. func (c *BaseController) CreateVirtualizationComponents() error { - // no-op + configHandler := c.ResolveConfigHandler() + + vmDriver := configHandler.GetString("vm.driver") + dockerEnabled := configHandler.GetBool("docker.enabled") + + if vmDriver == "colima" { + // Create and register NetworkInterfaceProvider + networkInterfaceProvider := c.constructors.NewNetworkInterfaceProvider() + c.injector.Register("networkInterfaceProvider", networkInterfaceProvider) + + // Create secure shell + secureShell := c.constructors.NewSecureShell(c.injector) + c.injector.Register("secureShell", secureShell) + + // Create SSH client + sshClient := c.constructors.NewSSHClient() + c.injector.Register("sshClient", sshClient) + + // Create Colima virtual machine + colimaVirtualMachine := c.constructors.NewColimaVirt(c.injector) + c.injector.Register("virtualMachine", colimaVirtualMachine) + + // Create Colima network manager + networkManager := c.constructors.NewColimaNetworkManager(c.injector) + c.injector.Register("networkManager", networkManager) + } else { + // Create base network manager + networkManager := c.constructors.NewBaseNetworkManager(c.injector) + c.injector.Register("networkManager", networkManager) + } + + if dockerEnabled { + // Create Docker virtualization + containerRuntime := c.constructors.NewDockerVirt(c.injector) + c.injector.Register("containerRuntime", containerRuntime) + } + return nil } // CreateStackComponents creates the stack components. func (c *BaseController) CreateStackComponents() error { - // no-op + // Create Windsor stack + stackInstance := c.constructors.NewWindsorStack(c.injector) + c.injector.Register("stack", stackInstance) + return nil } diff --git a/pkg/controller/controller_test.go b/pkg/controller/controller_test.go index 4ded20fbf..e77356464 100644 --- a/pkg/controller/controller_test.go +++ b/pkg/controller/controller_test.go @@ -7,6 +7,7 @@ import ( "strings" "testing" + secretsConfigType "github.com/windsorcli/cli/api/v1alpha1/secrets" "github.com/windsorcli/cli/pkg/blueprint" "github.com/windsorcli/cli/pkg/config" "github.com/windsorcli/cli/pkg/di" @@ -21,6 +22,9 @@ import ( "github.com/windsorcli/cli/pkg/virt" ) +// Package level variable for mocking +var checkExistingToolsManager = tools.CheckExistingToolsManager + // ============================================================================= // Test Setup // ============================================================================= @@ -261,18 +265,56 @@ contexts: // ============================================================================= func TestNewController(t *testing.T) { - t.Run("Success", func(t *testing.T) { - // Given a new test setup - mocks := setupMocks(t) + t.Run("WithDefaultConstructors", func(t *testing.T) { + // Given a new injector + injector := di.NewInjector() - // Given a new controller - controller := NewController(mocks.Injector) + // When creating a new controller without custom constructors + controller := NewController(injector) // Then the controller should not be nil if controller == nil { - t.Fatalf("expected controller, got nil") - } else { - t.Logf("Success: controller created") + t.Fatal("expected controller, got nil") + } + + // And it should use default constructors + if controller.constructors.NewYamlConfigHandler == nil { + t.Error("expected NewYamlConfigHandler constructor, got nil") + } + if controller.constructors.NewDefaultShell == nil { + t.Error("expected NewDefaultShell constructor, got nil") + } + }) + + t.Run("WithCustomConstructors", func(t *testing.T) { + // Given a new injector and custom constructors + injector := di.NewInjector() + customConstructors := ComponentConstructors{ + NewYamlConfigHandler: func(di.Injector) config.ConfigHandler { + return config.NewMockConfigHandler() + }, + NewDefaultShell: func(di.Injector) shell.Shell { + return shell.NewMockShell() + }, + } + + // When creating a new controller with custom constructors + controller := NewController(injector, customConstructors) + + // Then the controller should not be nil + if controller == nil { + t.Fatal("expected controller, got nil") + } + + // And it should use the custom constructors + configHandler := controller.constructors.NewYamlConfigHandler(injector) + if _, ok := configHandler.(*config.MockConfigHandler); !ok { + t.Errorf("expected *config.MockConfigHandler, got %T", configHandler) + } + + shellInstance := controller.constructors.NewDefaultShell(injector) + if _, ok := shellInstance.(*shell.MockShell); !ok { + t.Errorf("expected *shell.MockShell, got %T", shellInstance) } }) } @@ -591,12 +633,92 @@ func TestController_CreateCommonComponents(t *testing.T) { t.Fatalf("expected no error, got %v", err) } }) + + t.Run("WithNilInjector", func(t *testing.T) { + // Given a controller with nil injector + controller, _ := setup(t) + controller.(*BaseController).injector = nil + + // When creating common components + err := controller.CreateCommonComponents() + + // Then there should be an error + if err == nil { + t.Fatal("expected error with nil injector, got nil") + } + if !strings.Contains(err.Error(), "injector is nil") { + t.Errorf("expected error to contain 'injector is nil', got %v", err) + } + }) + + t.Run("WithNilConfigHandler", func(t *testing.T) { + // Given a controller with a mock config handler that fails to initialize + controller, _ := setup(t) + mockConfigHandler := config.NewMockConfigHandler() + mockConfigHandler.InitializeFunc = func() error { + return fmt.Errorf("config handler initialization error") + } + controller.(*BaseController).constructors.NewYamlConfigHandler = func(di.Injector) config.ConfigHandler { + return mockConfigHandler + } + + // When creating common components + err := controller.CreateCommonComponents() + + // Then there should be an error + if err == nil { + t.Fatal("expected error with config handler initialization error, got nil") + } + if !strings.Contains(err.Error(), "error initializing config handler") { + t.Errorf("expected error to contain 'error initializing config handler', got %v", err) + } + }) + + t.Run("WithNilConstructors", func(t *testing.T) { + // Given a controller with nil constructors + controller, _ := setup(t) + controller.(*BaseController).constructors = ComponentConstructors{} + + // When creating common components + err := controller.CreateCommonComponents() + + // Then there should be an error + if err == nil { + t.Fatal("expected error with nil constructors, got nil") + } + if !strings.Contains(err.Error(), "required constructors are nil") { + t.Errorf("expected error to contain 'required constructors are nil', got %v", err) + } + }) + + t.Run("WithShellInitializationError", func(t *testing.T) { + // Given a controller with a mock shell that fails to initialize + controller, _ := setup(t) + mockShell := shell.NewMockShell() + mockShell.InitializeFunc = func() error { + return fmt.Errorf("shell initialization error") + } + controller.(*BaseController).constructors.NewDefaultShell = func(di.Injector) shell.Shell { + return mockShell + } + + // When creating common components + err := controller.CreateCommonComponents() + + // Then there should be an error + if err == nil { + t.Fatal("expected error with shell initialization error, got nil") + } + if !strings.Contains(err.Error(), "error initializing shell") { + t.Errorf("expected error to contain 'error initializing shell', got %v", err) + } + }) } func TestController_CreateSecretsProviders(t *testing.T) { - setup := func(t *testing.T) (Controller, *Mocks) { + setup := func(t *testing.T, opts *SetupOptions) (Controller, *Mocks) { t.Helper() - mocks := setupMocks(t) + mocks := setupMocks(t, opts) controller := NewController(mocks.Injector) err := controller.Initialize() if err != nil { @@ -605,18 +727,133 @@ func TestController_CreateSecretsProviders(t *testing.T) { return controller, mocks } - t.Run("Success", func(t *testing.T) { - // Given a new controller - controller, _ := setup(t) + t.Run("NoSecretsEnabled", func(t *testing.T) { + // Given a controller with no secrets configured + controller, _ := setup(t, &SetupOptions{ConfigStr: ` +contexts: + mock-context: + secrets: + enabled: false`}) - // When creating secrets provider + // When creating secrets providers err := controller.CreateSecretsProviders() - // Then there should be no error + // Then no error should occur if err != nil { t.Fatalf("expected no error, got %v", err) } }) + + t.Run("SOPSProviderEnabled", func(t *testing.T) { + // Given a controller with SOPS secrets file + tmpDir := t.TempDir() + secretsFile := filepath.Join(tmpDir, "secrets.enc.yaml") + if err := os.WriteFile(secretsFile, []byte("test"), 0644); err != nil { + t.Fatalf("Failed to create test secrets file: %v", err) + } + + // Create a mock config handler + mockConfigHandler := config.NewMockConfigHandler() + mockConfigHandler.GetConfigRootFunc = func() (string, error) { + return tmpDir, nil + } + + controller, _ := setup(t, &SetupOptions{ + ConfigStr: ` +contexts: + mock-context: + secrets: + enabled: true`, + ConfigHandler: mockConfigHandler, + }) + + // When creating secrets providers + err := controller.CreateSecretsProviders() + + // Then no error should occur + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + }) + + t.Run("OnePasswordProviderWithSDK", func(t *testing.T) { + // Given a controller with 1Password vault configured + controller, _ := setup(t, &SetupOptions{ConfigStr: ` +contexts: + mock-context: + secrets: + enabled: true + onepassword: + vaults: + dev: + title: Development + url: https://dev.1password.com`}) + + // And 1Password SDK token is set + os.Setenv("OP_SERVICE_ACCOUNT_TOKEN", "test-token") + defer os.Unsetenv("OP_SERVICE_ACCOUNT_TOKEN") + + // When creating secrets providers + err := controller.CreateSecretsProviders() + + // Then no error should occur + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + }) + + t.Run("OnePasswordProviderWithCLI", func(t *testing.T) { + // Given a controller with 1Password vault configured + controller, _ := setup(t, &SetupOptions{ConfigStr: ` +contexts: + mock-context: + secrets: + enabled: true + onepassword: + vaults: + dev: + title: Development + url: https://dev.1password.com`}) + + // And 1Password SDK token is not set (forcing CLI usage) + os.Unsetenv("OP_SERVICE_ACCOUNT_TOKEN") + + // When creating secrets providers + err := controller.CreateSecretsProviders() + + // Then no error should occur + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + }) + + t.Run("ConfigRootError", func(t *testing.T) { + // Given a controller with a failing config handler + mockConfigHandler := config.NewMockConfigHandler() + mockConfigHandler.GetConfigRootFunc = func() (string, error) { + return "", fmt.Errorf("config root error") + } + + controller, _ := setup(t, &SetupOptions{ + ConfigStr: ` +contexts: + mock-context: + secrets: + enabled: true`, + ConfigHandler: mockConfigHandler, + }) + + // When creating secrets providers + err := controller.CreateSecretsProviders() + + // Then an error should occur + if err == nil { + t.Fatal("expected error, got nil") + } + if !strings.Contains(err.Error(), "config root error") { + t.Fatalf("expected error to contain 'config root error', got %v", err) + } + }) } func TestController_CreateProjectComponents(t *testing.T) { @@ -631,7 +868,7 @@ func TestController_CreateProjectComponents(t *testing.T) { return controller, mocks } - t.Run("Success", func(t *testing.T) { + t.Run("CreatesDefaultComponents", func(t *testing.T) { // Given a new controller controller, _ := setup(t) @@ -643,6 +880,64 @@ func TestController_CreateProjectComponents(t *testing.T) { t.Fatalf("expected no error, got %v", err) } }) + + t.Run("WithNilInjector", func(t *testing.T) { + // Given a controller with nil injector + controller, _ := setup(t) + controller.(*BaseController).injector = nil + + // When creating project components + err := controller.CreateProjectComponents() + + // Then there should be an error + if err == nil { + t.Fatal("expected error with nil injector, got nil") + } + if !strings.Contains(err.Error(), "injector is nil") { + t.Errorf("expected error to contain 'injector is nil', got %v", err) + } + }) + + t.Run("WithNilConfigHandler", func(t *testing.T) { + // Given a controller with nil config handler + controller, _ := setup(t) + controller.(*BaseController).configHandler = nil + + // When creating project components + err := controller.CreateProjectComponents() + + // Then there should be an error + if err == nil { + t.Fatal("expected error with nil config handler, got nil") + } + if !strings.Contains(err.Error(), "config handler is nil") { + t.Errorf("expected error to contain 'config handler is nil', got %v", err) + } + }) + + t.Run("WithCustomToolsManagerType", func(t *testing.T) { + // Given a controller with a custom tools manager type + controller, _ := setup(t) + mockConfigHandler := config.NewMockConfigHandler() + mockConfigHandler.GetStringFunc = func(key string, defaultValue ...string) string { + if key == "toolsManager" { + return "custom" + } + if len(defaultValue) > 0 { + return defaultValue[0] + } + return "" + } + controller.(*BaseController).configHandler = mockConfigHandler + + // When creating project components + err := controller.CreateProjectComponents() + + // Then there should be no error + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + }) } func TestController_CreateEnvComponents(t *testing.T) { @@ -657,7 +952,7 @@ func TestController_CreateEnvComponents(t *testing.T) { return controller, mocks } - t.Run("Success", func(t *testing.T) { + t.Run("CreatesEnvironmentComponents", func(t *testing.T) { // Given a new controller controller, _ := setup(t) @@ -669,12 +964,89 @@ func TestController_CreateEnvComponents(t *testing.T) { t.Fatalf("expected no error, got %v", err) } }) + + t.Run("WithNilInjector", func(t *testing.T) { + // Given a controller with nil injector + controller, _ := setup(t) + controller.(*BaseController).injector = nil + + // When creating env components + err := controller.CreateEnvComponents() + + // Then there should be an error + if err == nil { + t.Fatal("expected error with nil injector, got nil") + } + if !strings.Contains(err.Error(), "injector is nil") { + t.Errorf("expected error to contain 'injector is nil', got %v", err) + } + }) + + t.Run("WithNilConfigHandler", func(t *testing.T) { + // Given a controller with nil config handler + controller, _ := setup(t) + controller.(*BaseController).configHandler = nil + + // When creating env components + err := controller.CreateEnvComponents() + + // Then there should be an error + if err == nil { + t.Fatal("expected error with nil config handler, got nil") + } + if !strings.Contains(err.Error(), "config handler is nil") { + t.Errorf("expected error to contain 'config handler is nil', got %v", err) + } + }) + + t.Run("WithAwsEnabled", func(t *testing.T) { + // Given a controller with AWS enabled + controller, mocks := setup(t) + mocks.ConfigHandler.Set("contexts.mock-context.aws.enabled", true) + + // When creating env components + err := controller.CreateEnvComponents() + + // Then there should be no error + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + }) + + t.Run("WithDockerEnabled", func(t *testing.T) { + // Given a controller with Docker enabled + controller, mocks := setup(t) + mocks.ConfigHandler.Set("contexts.mock-context.docker.enabled", true) + + // When creating env components + err := controller.CreateEnvComponents() + + // Then there should be no error + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + }) + + t.Run("WithBothAwsAndDockerEnabled", func(t *testing.T) { + // Given a controller with both AWS and Docker enabled + controller, mocks := setup(t) + mocks.ConfigHandler.Set("contexts.mock-context.aws.enabled", true) + mocks.ConfigHandler.Set("contexts.mock-context.docker.enabled", true) + + // When creating env components + err := controller.CreateEnvComponents() + + // Then there should be no error + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + }) } func TestController_CreateServiceComponents(t *testing.T) { - setup := func(t *testing.T) (Controller, *Mocks) { + setup := func(t *testing.T, configStr string) (Controller, *Mocks) { t.Helper() - mocks := setupMocks(t) + mocks := setupMocks(t, &SetupOptions{ConfigStr: configStr}) controller := NewController(mocks.Injector) err := controller.Initialize() if err != nil { @@ -683,14 +1055,123 @@ func TestController_CreateServiceComponents(t *testing.T) { return controller, mocks } - t.Run("Success", func(t *testing.T) { - // Given a new controller - controller, _ := setup(t) + t.Run("NoServicesEnabled", func(t *testing.T) { + // Given a controller with no services enabled + controller, _ := setup(t, ` +contexts: + mock-context: + docker: + enabled: false`) // When creating service components err := controller.CreateServiceComponents() - // Then there should be no error + // Then no error should occur + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + }) + + t.Run("DNSServiceEnabled", func(t *testing.T) { + // Given a controller with DNS service enabled + controller, _ := setup(t, ` +contexts: + mock-context: + docker: + enabled: true + dns: + enabled: true`) + + // When creating service components + err := controller.CreateServiceComponents() + + // Then no error should occur + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + }) + + t.Run("GitLivereloadEnabled", func(t *testing.T) { + // Given a controller with Git livereload enabled + controller, _ := setup(t, ` +contexts: + mock-context: + docker: + enabled: true + git: + livereload: + enabled: true`) + + // When creating service components + err := controller.CreateServiceComponents() + + // Then no error should occur + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + }) + + t.Run("LocalstackEnabled", func(t *testing.T) { + // Given a controller with Localstack enabled + controller, _ := setup(t, ` +contexts: + mock-context: + docker: + enabled: true + aws: + localstack: + enabled: true`) + + // When creating service components + err := controller.CreateServiceComponents() + + // Then no error should occur + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + }) + + t.Run("DockerRegistriesEnabled", func(t *testing.T) { + // Given a controller with Docker registries configured + controller, _ := setup(t, ` +contexts: + mock-context: + docker: + enabled: true + registries: + registry1: + remote: registry1.example.com + registry2: + remote: registry2.example.com`) + + // When creating service components + err := controller.CreateServiceComponents() + + // Then no error should occur + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + }) + + t.Run("ClusterEnabled", func(t *testing.T) { + // Given a controller with cluster enabled + controller, _ := setup(t, ` +contexts: + mock-context: + docker: + enabled: true + cluster: + enabled: true + driver: talos + controlplanes: + count: 3 + workers: + count: 5`) + + // When creating service components + err := controller.CreateServiceComponents() + + // Then no error should occur if err != nil { t.Fatalf("expected no error, got %v", err) } @@ -709,7 +1190,7 @@ func TestController_CreateVirtualizationComponents(t *testing.T) { return controller, mocks } - t.Run("Success", func(t *testing.T) { + t.Run("CreatesVirtualizationComponents", func(t *testing.T) { // Given a new controller controller, _ := setup(t) @@ -735,7 +1216,7 @@ func TestController_CreateStackComponents(t *testing.T) { return controller, mocks } - t.Run("Success", func(t *testing.T) { + t.Run("CreatesStackComponents", func(t *testing.T) { // Given a new controller controller, _ := setup(t) @@ -761,7 +1242,7 @@ func TestController_WriteConfigurationFiles(t *testing.T) { return controller, mocks } - t.Run("Success", func(t *testing.T) { + t.Run("WritesConfigurationFiles", func(t *testing.T) { // Given a new controller controller, _ := setup(t) @@ -774,7 +1255,7 @@ func TestController_WriteConfigurationFiles(t *testing.T) { } }) - t.Run("ErrorWritingToolsManifest", func(t *testing.T) { + t.Run("FailsWritingToolsManifest", func(t *testing.T) { // Given a mock tools manager that returns an error controller, mocks := setup(t) mocks.ToolsManager.WriteManifestFunc = func() error { @@ -1210,7 +1691,6 @@ func TestController_ResolveNetworkManager(t *testing.T) { } return controller, mocks } - t.Run("Success", func(t *testing.T) { // Given a new controller and injector controller, mocks := setup(t) @@ -1448,3 +1928,570 @@ func TestController_SetEnvironmentVariables(t *testing.T) { } }) } + +func TestDefaultConstructors(t *testing.T) { + t.Run("ReturnsAllRequiredConstructors", func(t *testing.T) { + // Given a new set of default constructors + constructors := DefaultConstructors() + + // When checking each constructor field + // Then each constructor should be non-nil + if constructors.NewYamlConfigHandler == nil { + t.Error("NewYamlConfigHandler constructor is nil") + } + if constructors.NewDefaultShell == nil { + t.Error("NewDefaultShell constructor is nil") + } + if constructors.NewSecureShell == nil { + t.Error("NewSecureShell constructor is nil") + } + if constructors.NewGitGenerator == nil { + t.Error("NewGitGenerator constructor is nil") + } + if constructors.NewBlueprintHandler == nil { + t.Error("NewBlueprintHandler constructor is nil") + } + if constructors.NewTerraformGenerator == nil { + t.Error("NewTerraformGenerator constructor is nil") + } + if constructors.NewKustomizeGenerator == nil { + t.Error("NewKustomizeGenerator constructor is nil") + } + if constructors.NewToolsManager == nil { + t.Error("NewToolsManager constructor is nil") + } + if constructors.NewDNSService == nil { + t.Error("NewDNSService constructor is nil") + } + if constructors.NewGitLivereloadService == nil { + t.Error("NewGitLivereloadService constructor is nil") + } + if constructors.NewLocalstackService == nil { + t.Error("NewLocalstackService constructor is nil") + } + if constructors.NewRegistryService == nil { + t.Error("NewRegistryService constructor is nil") + } + if constructors.NewTalosService == nil { + t.Error("NewTalosService constructor is nil") + } + if constructors.NewSSHClient == nil { + t.Error("NewSSHClient constructor is nil") + } + if constructors.NewColimaVirt == nil { + t.Error("NewColimaVirt constructor is nil") + } + if constructors.NewColimaNetworkManager == nil { + t.Error("NewColimaNetworkManager constructor is nil") + } + if constructors.NewBaseNetworkManager == nil { + t.Error("NewBaseNetworkManager constructor is nil") + } + if constructors.NewDockerVirt == nil { + t.Error("NewDockerVirt constructor is nil") + } + if constructors.NewNetworkInterfaceProvider == nil { + t.Error("NewNetworkInterfaceProvider constructor is nil") + } + if constructors.NewSopsSecretsProvider == nil { + t.Error("NewSopsSecretsProvider constructor is nil") + } + if constructors.NewOnePasswordSDKSecretsProvider == nil { + t.Error("NewOnePasswordSDKSecretsProvider constructor is nil") + } + if constructors.NewOnePasswordCLISecretsProvider == nil { + t.Error("NewOnePasswordCLISecretsProvider constructor is nil") + } + if constructors.NewWindsorStack == nil { + t.Error("NewWindsorStack constructor is nil") + } + }) + + t.Run("CreatesCorrectConcreteTypes", func(t *testing.T) { + // Given a new injector and constructors + injector := di.NewInjector() + constructors := DefaultConstructors() + + // When creating components + configHandler := constructors.NewYamlConfigHandler(injector) + defaultShell := constructors.NewDefaultShell(injector) + secureShell := constructors.NewSecureShell(injector) + gitGenerator := constructors.NewGitGenerator(injector) + blueprintHandler := constructors.NewBlueprintHandler(injector) + terraformGenerator := constructors.NewTerraformGenerator(injector) + kustomizeGenerator := constructors.NewKustomizeGenerator(injector) + toolsManager := constructors.NewToolsManager(injector) + dnsService := constructors.NewDNSService(injector) + gitLivereloadService := constructors.NewGitLivereloadService(injector) + localstackService := constructors.NewLocalstackService(injector) + registryService := constructors.NewRegistryService(injector) + talosService := constructors.NewTalosService(injector, "controlplane") + sshClient := constructors.NewSSHClient() + colimaVirt := constructors.NewColimaVirt(injector) + colimaNetworkManager := constructors.NewColimaNetworkManager(injector) + baseNetworkManager := constructors.NewBaseNetworkManager(injector) + dockerVirt := constructors.NewDockerVirt(injector) + networkInterfaceProvider := constructors.NewNetworkInterfaceProvider() + sopsSecretsProvider := constructors.NewSopsSecretsProvider("test.yaml", injector) + onePasswordSDKSecretsProvider := constructors.NewOnePasswordSDKSecretsProvider(secretsConfigType.OnePasswordVault{}, injector) + onePasswordCLISecretsProvider := constructors.NewOnePasswordCLISecretsProvider(secretsConfigType.OnePasswordVault{}, injector) + windsorStack := constructors.NewWindsorStack(injector) + + // Then they should be of the correct concrete type + if _, ok := configHandler.(*config.YamlConfigHandler); !ok { + t.Error("NewYamlConfigHandler did not create YamlConfigHandler") + } + if _, ok := defaultShell.(*shell.DefaultShell); !ok { + t.Error("NewDefaultShell did not create DefaultShell") + } + if _, ok := secureShell.(*shell.SecureShell); !ok { + t.Error("NewSecureShell did not create SecureShell") + } + if _, ok := gitGenerator.(*generators.GitGenerator); !ok { + t.Error("NewGitGenerator did not create GitGenerator") + } + if _, ok := blueprintHandler.(blueprint.BlueprintHandler); !ok { + t.Error("NewBlueprintHandler did not create BlueprintHandler") + } + if _, ok := terraformGenerator.(*generators.TerraformGenerator); !ok { + t.Error("NewTerraformGenerator did not create TerraformGenerator") + } + if _, ok := kustomizeGenerator.(*generators.KustomizeGenerator); !ok { + t.Error("NewKustomizeGenerator did not create KustomizeGenerator") + } + if _, ok := toolsManager.(tools.ToolsManager); !ok { + t.Error("NewToolsManager did not create ToolsManager") + } + if _, ok := dnsService.(*services.DNSService); !ok { + t.Error("NewDNSService did not create DNSService") + } + if _, ok := gitLivereloadService.(*services.GitLivereloadService); !ok { + t.Error("NewGitLivereloadService did not create GitLivereloadService") + } + if _, ok := localstackService.(*services.LocalstackService); !ok { + t.Error("NewLocalstackService did not create LocalstackService") + } + if _, ok := registryService.(*services.RegistryService); !ok { + t.Error("NewRegistryService did not create RegistryService") + } + if _, ok := talosService.(*services.TalosService); !ok { + t.Error("NewTalosService did not create TalosService") + } + if sshClient == nil { + t.Error("NewSSHClient did not create SSHClient") + } + if _, ok := colimaVirt.(*virt.ColimaVirt); !ok { + t.Error("NewColimaVirt did not create ColimaVirt") + } + if _, ok := colimaNetworkManager.(*network.ColimaNetworkManager); !ok { + t.Error("NewColimaNetworkManager did not create ColimaNetworkManager") + } + if _, ok := baseNetworkManager.(*network.BaseNetworkManager); !ok { + t.Error("NewBaseNetworkManager did not create BaseNetworkManager") + } + if _, ok := dockerVirt.(*virt.DockerVirt); !ok { + t.Error("NewDockerVirt did not create DockerVirt") + } + if _, ok := networkInterfaceProvider.(network.NetworkInterfaceProvider); !ok { + t.Error("NewNetworkInterfaceProvider did not create NetworkInterfaceProvider") + } + if _, ok := sopsSecretsProvider.(*secrets.SopsSecretsProvider); !ok { + t.Error("NewSopsSecretsProvider did not create SopsSecretsProvider") + } + if _, ok := onePasswordSDKSecretsProvider.(*secrets.OnePasswordSDKSecretsProvider); !ok { + t.Error("NewOnePasswordSDKSecretsProvider did not create OnePasswordSDKSecretsProvider") + } + if _, ok := onePasswordCLISecretsProvider.(*secrets.OnePasswordCLISecretsProvider); !ok { + t.Error("NewOnePasswordCLISecretsProvider did not create OnePasswordCLISecretsProvider") + } + if _, ok := windsorStack.(*stack.WindsorStack); !ok { + t.Error("NewWindsorStack did not create WindsorStack") + } + }) +} + +func TestMockConstructors(t *testing.T) { + t.Run("ReturnsAllRequiredMockConstructors", func(t *testing.T) { + // Given a new set of mock constructors + constructors := MockConstructors() + + // When checking each constructor field + // Then each constructor should be non-nil + if constructors.NewYamlConfigHandler == nil { + t.Error("NewYamlConfigHandler mock constructor is nil") + } + if constructors.NewDefaultShell == nil { + t.Error("NewDefaultShell mock constructor is nil") + } + if constructors.NewSecureShell == nil { + t.Error("NewSecureShell mock constructor is nil") + } + if constructors.NewGitGenerator == nil { + t.Error("NewGitGenerator mock constructor is nil") + } + if constructors.NewBlueprintHandler == nil { + t.Error("NewBlueprintHandler mock constructor is nil") + } + if constructors.NewTerraformGenerator == nil { + t.Error("NewTerraformGenerator mock constructor is nil") + } + if constructors.NewKustomizeGenerator == nil { + t.Error("NewKustomizeGenerator mock constructor is nil") + } + if constructors.NewToolsManager == nil { + t.Error("NewToolsManager mock constructor is nil") + } + if constructors.NewAwsEnvPrinter == nil { + t.Error("NewAwsEnvPrinter mock constructor is nil") + } + if constructors.NewDockerEnvPrinter == nil { + t.Error("NewDockerEnvPrinter mock constructor is nil") + } + if constructors.NewKubeEnvPrinter == nil { + t.Error("NewKubeEnvPrinter mock constructor is nil") + } + if constructors.NewOmniEnvPrinter == nil { + t.Error("NewOmniEnvPrinter mock constructor is nil") + } + if constructors.NewTalosEnvPrinter == nil { + t.Error("NewTalosEnvPrinter mock constructor is nil") + } + if constructors.NewTerraformEnvPrinter == nil { + t.Error("NewTerraformEnvPrinter mock constructor is nil") + } + if constructors.NewWindsorEnvPrinter == nil { + t.Error("NewWindsorEnvPrinter mock constructor is nil") + } + if constructors.NewDNSService == nil { + t.Error("NewDNSService mock constructor is nil") + } + if constructors.NewGitLivereloadService == nil { + t.Error("NewGitLivereloadService mock constructor is nil") + } + if constructors.NewLocalstackService == nil { + t.Error("NewLocalstackService mock constructor is nil") + } + if constructors.NewRegistryService == nil { + t.Error("NewRegistryService mock constructor is nil") + } + if constructors.NewTalosService == nil { + t.Error("NewTalosService mock constructor is nil") + } + if constructors.NewSSHClient == nil { + t.Error("NewSSHClient mock constructor is nil") + } + if constructors.NewColimaVirt == nil { + t.Error("NewColimaVirt mock constructor is nil") + } + if constructors.NewColimaNetworkManager == nil { + t.Error("NewColimaNetworkManager mock constructor is nil") + } + if constructors.NewBaseNetworkManager == nil { + t.Error("NewBaseNetworkManager mock constructor is nil") + } + if constructors.NewDockerVirt == nil { + t.Error("NewDockerVirt mock constructor is nil") + } + if constructors.NewNetworkInterfaceProvider == nil { + t.Error("NewNetworkInterfaceProvider mock constructor is nil") + } + if constructors.NewSopsSecretsProvider == nil { + t.Error("NewSopsSecretsProvider mock constructor is nil") + } + if constructors.NewOnePasswordSDKSecretsProvider == nil { + t.Error("NewOnePasswordSDKSecretsProvider mock constructor is nil") + } + if constructors.NewOnePasswordCLISecretsProvider == nil { + t.Error("NewOnePasswordCLISecretsProvider mock constructor is nil") + } + if constructors.NewWindsorStack == nil { + t.Error("NewWindsorStack mock constructor is nil") + } + }) + + t.Run("CreatesCorrectMockTypes", func(t *testing.T) { + // Given a new injector and mock constructors + injector := di.NewInjector() + constructors := MockConstructors() + + // When creating components + configHandler := constructors.NewYamlConfigHandler(injector) + defaultShell := constructors.NewDefaultShell(injector) + secureShell := constructors.NewSecureShell(injector) + gitGenerator := constructors.NewGitGenerator(injector) + blueprintHandler := constructors.NewBlueprintHandler(injector) + terraformGenerator := constructors.NewTerraformGenerator(injector) + kustomizeGenerator := constructors.NewKustomizeGenerator(injector) + toolsManager := constructors.NewToolsManager(injector) + awsEnvPrinter := constructors.NewAwsEnvPrinter(injector) + dockerEnvPrinter := constructors.NewDockerEnvPrinter(injector) + kubeEnvPrinter := constructors.NewKubeEnvPrinter(injector) + omniEnvPrinter := constructors.NewOmniEnvPrinter(injector) + talosEnvPrinter := constructors.NewTalosEnvPrinter(injector) + terraformEnvPrinter := constructors.NewTerraformEnvPrinter(injector) + windsorEnvPrinter := constructors.NewWindsorEnvPrinter(injector) + dnsService := constructors.NewDNSService(injector) + gitLivereloadService := constructors.NewGitLivereloadService(injector) + localstackService := constructors.NewLocalstackService(injector) + registryService := constructors.NewRegistryService(injector) + talosService := constructors.NewTalosService(injector, "controlplane") + sshClient := constructors.NewSSHClient() + colimaVirt := constructors.NewColimaVirt(injector) + colimaNetworkManager := constructors.NewColimaNetworkManager(injector) + baseNetworkManager := constructors.NewBaseNetworkManager(injector) + dockerVirt := constructors.NewDockerVirt(injector) + networkInterfaceProvider := constructors.NewNetworkInterfaceProvider() + sopsSecretsProvider := constructors.NewSopsSecretsProvider("test.yaml", injector) + onePasswordSDKSecretsProvider := constructors.NewOnePasswordSDKSecretsProvider(secretsConfigType.OnePasswordVault{}, injector) + onePasswordCLISecretsProvider := constructors.NewOnePasswordCLISecretsProvider(secretsConfigType.OnePasswordVault{}, injector) + windsorStack := constructors.NewWindsorStack(injector) + + // Then they should be of the correct mock type + if _, ok := configHandler.(*config.MockConfigHandler); !ok { + t.Error("NewYamlConfigHandler did not create MockConfigHandler") + } + if _, ok := defaultShell.(*shell.MockShell); !ok { + t.Error("NewDefaultShell did not create MockShell") + } + if _, ok := secureShell.(*shell.MockShell); !ok { + t.Error("NewSecureShell did not create MockShell") + } + if _, ok := gitGenerator.(*generators.MockGenerator); !ok { + t.Error("NewGitGenerator did not create MockGenerator") + } + if _, ok := blueprintHandler.(*blueprint.MockBlueprintHandler); !ok { + t.Error("NewBlueprintHandler did not create MockBlueprintHandler") + } + if _, ok := terraformGenerator.(*generators.MockGenerator); !ok { + t.Error("NewTerraformGenerator did not create MockGenerator") + } + if _, ok := kustomizeGenerator.(*generators.MockGenerator); !ok { + t.Error("NewKustomizeGenerator did not create MockGenerator") + } + if _, ok := toolsManager.(*tools.MockToolsManager); !ok { + t.Error("NewToolsManager did not create MockToolsManager") + } + if _, ok := awsEnvPrinter.(*env.MockEnvPrinter); !ok { + t.Error("NewAwsEnvPrinter did not create MockEnvPrinter") + } + if _, ok := dockerEnvPrinter.(*env.MockEnvPrinter); !ok { + t.Error("NewDockerEnvPrinter did not create MockEnvPrinter") + } + if _, ok := kubeEnvPrinter.(*env.MockEnvPrinter); !ok { + t.Error("NewKubeEnvPrinter did not create MockEnvPrinter") + } + if _, ok := omniEnvPrinter.(*env.MockEnvPrinter); !ok { + t.Error("NewOmniEnvPrinter did not create MockEnvPrinter") + } + if _, ok := talosEnvPrinter.(*env.MockEnvPrinter); !ok { + t.Error("NewTalosEnvPrinter did not create MockEnvPrinter") + } + if _, ok := terraformEnvPrinter.(*env.MockEnvPrinter); !ok { + t.Error("NewTerraformEnvPrinter did not create MockEnvPrinter") + } + if _, ok := windsorEnvPrinter.(*env.MockEnvPrinter); !ok { + t.Error("NewWindsorEnvPrinter did not create MockEnvPrinter") + } + if _, ok := dnsService.(*services.MockService); !ok { + t.Error("NewDNSService did not create MockService") + } + if _, ok := gitLivereloadService.(*services.MockService); !ok { + t.Error("NewGitLivereloadService did not create MockService") + } + if _, ok := localstackService.(*services.MockService); !ok { + t.Error("NewLocalstackService did not create MockService") + } + if _, ok := registryService.(*services.MockService); !ok { + t.Error("NewRegistryService did not create MockService") + } + if _, ok := talosService.(*services.MockService); !ok { + t.Error("NewTalosService did not create MockService") + } + if sshClient == nil { + t.Error("NewSSHClient did not create SSHClient") + } + if _, ok := colimaVirt.(*virt.MockVirt); !ok { + t.Error("NewColimaVirt did not create MockVirt") + } + if _, ok := colimaNetworkManager.(*network.MockNetworkManager); !ok { + t.Error("NewColimaNetworkManager did not create MockNetworkManager") + } + if _, ok := baseNetworkManager.(*network.MockNetworkManager); !ok { + t.Error("NewBaseNetworkManager did not create MockNetworkManager") + } + if _, ok := dockerVirt.(*virt.MockVirt); !ok { + t.Error("NewDockerVirt did not create MockVirt") + } + if _, ok := networkInterfaceProvider.(*network.MockNetworkInterfaceProvider); !ok { + t.Error("NewNetworkInterfaceProvider did not create MockNetworkInterfaceProvider") + } + if _, ok := sopsSecretsProvider.(*secrets.MockSecretsProvider); !ok { + t.Error("NewSopsSecretsProvider did not create MockSecretsProvider") + } + if _, ok := onePasswordSDKSecretsProvider.(*secrets.MockSecretsProvider); !ok { + t.Error("NewOnePasswordSDKSecretsProvider did not create MockSecretsProvider") + } + if _, ok := onePasswordCLISecretsProvider.(*secrets.MockSecretsProvider); !ok { + t.Error("NewOnePasswordCLISecretsProvider did not create MockSecretsProvider") + } + if _, ok := windsorStack.(*stack.MockStack); !ok { + t.Error("NewWindsorStack did not create MockStack") + } + }) +} + +func TestMockConstructors_EnvPrinters(t *testing.T) { + t.Run("AwsEnvPrinter", func(t *testing.T) { + // Given a new injector and mock constructors + injector := di.NewInjector() + constructors := MockConstructors() + + // When creating an AWS environment printer + awsEnvPrinter := constructors.NewAwsEnvPrinter(injector) + + // Then it should not be nil + if awsEnvPrinter == nil { + t.Fatal("expected non-nil AwsEnvPrinter") + } + + // And it should be of the correct mock type + if _, ok := awsEnvPrinter.(*env.MockEnvPrinter); !ok { + t.Errorf("expected *env.MockEnvPrinter, got %T", awsEnvPrinter) + } + + // And it should implement the EnvPrinter interface + var _ env.EnvPrinter = awsEnvPrinter + }) + + t.Run("DockerEnvPrinter", func(t *testing.T) { + // Given a new injector and mock constructors + injector := di.NewInjector() + constructors := MockConstructors() + + // When creating a Docker environment printer + dockerEnvPrinter := constructors.NewDockerEnvPrinter(injector) + + // Then it should not be nil + if dockerEnvPrinter == nil { + t.Fatal("expected non-nil DockerEnvPrinter") + } + + // And it should be of the correct mock type + if _, ok := dockerEnvPrinter.(*env.MockEnvPrinter); !ok { + t.Errorf("expected *env.MockEnvPrinter, got %T", dockerEnvPrinter) + } + + // And it should implement the EnvPrinter interface + var _ env.EnvPrinter = dockerEnvPrinter + }) + + t.Run("KubeEnvPrinter", func(t *testing.T) { + // Given a new injector and mock constructors + injector := di.NewInjector() + constructors := MockConstructors() + + // When creating a Kubernetes environment printer + kubeEnvPrinter := constructors.NewKubeEnvPrinter(injector) + + // Then it should not be nil + if kubeEnvPrinter == nil { + t.Fatal("expected non-nil KubeEnvPrinter") + } + + // And it should be of the correct mock type + if _, ok := kubeEnvPrinter.(*env.MockEnvPrinter); !ok { + t.Errorf("expected *env.MockEnvPrinter, got %T", kubeEnvPrinter) + } + + // And it should implement the EnvPrinter interface + var _ env.EnvPrinter = kubeEnvPrinter + }) + + t.Run("OmniEnvPrinter", func(t *testing.T) { + // Given a new injector and mock constructors + injector := di.NewInjector() + constructors := MockConstructors() + + // When creating an Omni environment printer + omniEnvPrinter := constructors.NewOmniEnvPrinter(injector) + + // Then it should not be nil + if omniEnvPrinter == nil { + t.Fatal("expected non-nil OmniEnvPrinter") + } + + // And it should be of the correct mock type + if _, ok := omniEnvPrinter.(*env.MockEnvPrinter); !ok { + t.Errorf("expected *env.MockEnvPrinter, got %T", omniEnvPrinter) + } + + // And it should implement the EnvPrinter interface + var _ env.EnvPrinter = omniEnvPrinter + }) + + t.Run("TalosEnvPrinter", func(t *testing.T) { + // Given a new injector and mock constructors + injector := di.NewInjector() + constructors := MockConstructors() + + // When creating a Talos environment printer + talosEnvPrinter := constructors.NewTalosEnvPrinter(injector) + + // Then it should not be nil + if talosEnvPrinter == nil { + t.Fatal("expected non-nil TalosEnvPrinter") + } + + // And it should be of the correct mock type + if _, ok := talosEnvPrinter.(*env.MockEnvPrinter); !ok { + t.Errorf("expected *env.MockEnvPrinter, got %T", talosEnvPrinter) + } + + // And it should implement the EnvPrinter interface + var _ env.EnvPrinter = talosEnvPrinter + }) + + t.Run("TerraformEnvPrinter", func(t *testing.T) { + // Given a new injector and mock constructors + injector := di.NewInjector() + constructors := MockConstructors() + + // When creating a Terraform environment printer + terraformEnvPrinter := constructors.NewTerraformEnvPrinter(injector) + + // Then it should not be nil + if terraformEnvPrinter == nil { + t.Fatal("expected non-nil TerraformEnvPrinter") + } + + // And it should be of the correct mock type + if _, ok := terraformEnvPrinter.(*env.MockEnvPrinter); !ok { + t.Errorf("expected *env.MockEnvPrinter, got %T", terraformEnvPrinter) + } + + // And it should implement the EnvPrinter interface + var _ env.EnvPrinter = terraformEnvPrinter + }) + + t.Run("WindsorEnvPrinter", func(t *testing.T) { + // Given a new injector and mock constructors + injector := di.NewInjector() + constructors := MockConstructors() + + // When creating a Windsor environment printer + windsorEnvPrinter := constructors.NewWindsorEnvPrinter(injector) + + // Then it should not be nil + if windsorEnvPrinter == nil { + t.Fatal("expected non-nil WindsorEnvPrinter") + } + + // And it should be of the correct mock type + if _, ok := windsorEnvPrinter.(*env.MockEnvPrinter); !ok { + t.Errorf("expected *env.MockEnvPrinter, got %T", windsorEnvPrinter) + } + + // And it should implement the EnvPrinter interface + var _ env.EnvPrinter = windsorEnvPrinter + }) +} diff --git a/pkg/controller/mock_controller.go b/pkg/controller/mock_controller.go index 09e00801c..853350cfc 100644 --- a/pkg/controller/mock_controller.go +++ b/pkg/controller/mock_controller.go @@ -1,8 +1,6 @@ package controller import ( - "fmt" - "github.com/windsorcli/cli/pkg/blueprint" "github.com/windsorcli/cli/pkg/config" "github.com/windsorcli/cli/pkg/di" @@ -12,7 +10,6 @@ import ( "github.com/windsorcli/cli/pkg/secrets" "github.com/windsorcli/cli/pkg/services" "github.com/windsorcli/cli/pkg/shell" - "github.com/windsorcli/cli/pkg/ssh" "github.com/windsorcli/cli/pkg/stack" "github.com/windsorcli/cli/pkg/tools" "github.com/windsorcli/cli/pkg/virt" @@ -54,10 +51,13 @@ type MockController struct { // Constructor // ============================================================================= +// NewMockController creates a new MockController with the given injector func NewMockController(injector di.Injector) *MockController { + // Create with mock constructors from controller.go return &MockController{ BaseController: BaseController{ - injector: injector, + injector: injector, + constructors: MockConstructors(), }, } } @@ -66,410 +66,220 @@ func NewMockController(injector di.Injector) *MockController { // Public Methods // ============================================================================= -// Initialize calls the mock InitializeFunc if set, otherwise calls the parent function +// Initialize implements the Controller interface func (m *MockController) Initialize() error { if m.InitializeFunc != nil { return m.InitializeFunc() } - return m.BaseController.Initialize() + return nil } -// InitializeComponents calls the mock InitializeComponentsFunc if set, otherwise calls the parent function +// InitializeComponents implements the Controller interface func (m *MockController) InitializeComponents() error { if m.InitializeComponentsFunc != nil { return m.InitializeComponentsFunc() } - return m.BaseController.InitializeComponents() + return nil } -// CreateCommonComponents calls the mock CreateCommonComponentsFunc if set, otherwise creates mock components +// CreateCommonComponents implements the Controller interface func (m *MockController) CreateCommonComponents() error { if m.CreateCommonComponentsFunc != nil { return m.CreateCommonComponentsFunc() } - - // Create a new mock configHandler - configHandler := config.NewMockConfigHandler() - m.injector.Register("configHandler", configHandler) - - // Set the configHandler - m.configHandler = configHandler - - // Create a new mock shell - shellInstance := shell.NewMockShell() - m.injector.Register("shell", shellInstance) - - // Testing Note: The following is hard to test as these are registered - // above and can't be mocked externally. There may be a better way to - // organize this in the future but this works for now, so we don't expect - // these lines to be covered by tests. - - // Initialize the config handler - if err := configHandler.Initialize(); err != nil { - return fmt.Errorf("error initializing config handler: %w", err) - } - - // Initialize the shell - resolvedShell := m.injector.Resolve("shell").(*shell.MockShell) - if err := resolvedShell.Initialize(); err != nil { - return fmt.Errorf("error initializing shell: %w", err) - } - return nil } -// CreateSecretsProviders calls the mock CreateSecretsProvidersFunc if set, otherwise creates mock components +// CreateSecretsProviders implements the Controller interface func (m *MockController) CreateSecretsProviders() error { if m.CreateSecretsProvidersFunc != nil { return m.CreateSecretsProvidersFunc() } - - // Create a new mock secrets provider - secretsProvider := secrets.NewMockSecretsProvider(m.injector) - m.injector.Register("secretsProvider", secretsProvider) - return nil } -// CreateProjectComponents calls the mock CreateProjectComponentsFunc if set, otherwise creates mock components +// CreateProjectComponents implements the Controller interface func (m *MockController) CreateProjectComponents() error { if m.CreateProjectComponentsFunc != nil { return m.CreateProjectComponentsFunc() } - - // Create a new mock tools manager - toolsManager := tools.NewMockToolsManager() - m.injector.Register("toolsManager", toolsManager) - - // Create a new mock blueprint handler - blueprintHandler := blueprint.NewMockBlueprintHandler(m.injector) - m.injector.Register("blueprintHandler", blueprintHandler) - - // Create a new git generator - gitGenerator := generators.NewMockGenerator() - m.injector.Register("gitGenerator", gitGenerator) - - // Create a new mock terraform generator - terraformGenerator := generators.NewMockGenerator() - m.injector.Register("terraformGenerator", terraformGenerator) - - // Create a new mock kustomize generator - kustomizeGenerator := generators.NewMockGenerator() - m.injector.Register("kustomizeGenerator", kustomizeGenerator) - return nil } -// CreateEnvComponents calls the mock CreateEnvComponentsFunc if set, otherwise creates mock components +// CreateEnvComponents implements the Controller interface func (m *MockController) CreateEnvComponents() error { if m.CreateEnvComponentsFunc != nil { return m.CreateEnvComponentsFunc() } - - // Create mock aws env printer only if aws.enabled is true - if m.configHandler.GetBool("aws.enabled") { - awsEnv := env.NewMockEnvPrinter() - m.injector.Register("awsEnv", awsEnv) - } - - // Create mock docker env printer only if docker.enabled is true - if m.configHandler.GetBool("docker.enabled") { - dockerEnv := env.NewMockEnvPrinter() - m.injector.Register("dockerEnv", dockerEnv) - } - - // Create mock kube env printer - kubeEnv := env.NewMockEnvPrinter() - m.injector.Register("kubeEnv", kubeEnv) - - // Create mock omni env printer - omniEnv := env.NewMockEnvPrinter() - m.injector.Register("omniEnv", omniEnv) - - // Create mock talos env printer - talosEnv := env.NewMockEnvPrinter() - m.injector.Register("talosEnv", talosEnv) - - // Create mock terraform env printer - terraformEnv := env.NewMockEnvPrinter() - m.injector.Register("terraformEnv", terraformEnv) - - // Create mock windsor env printer - windsorEnv := env.NewMockEnvPrinter() - m.injector.Register("windsorEnv", windsorEnv) - - // Create mock custom env printer - customEnv := env.NewMockEnvPrinter() - m.injector.Register("customEnv", customEnv) - return nil } -// CreateServiceComponents calls the mock CreateServiceComponentsFunc if set, otherwise creates mock components +// CreateServiceComponents implements the Controller interface func (m *MockController) CreateServiceComponents() error { if m.CreateServiceComponentsFunc != nil { return m.CreateServiceComponentsFunc() } - - contextConfig := m.configHandler.GetConfig() - - // Create mock dns service - dnsEnabled := m.configHandler.GetBool("dns.enabled") - if dnsEnabled { - dnsService := services.NewMockService() - m.injector.Register("dnsService", dnsService) - } - - // Create mock git livereload service - gitLivereloadEnabled := m.configHandler.GetBool("git.livereload.enabled") - if gitLivereloadEnabled { - gitLivereloadService := services.NewMockService() - m.injector.Register("gitLivereloadService", gitLivereloadService) - } - - // Create mock localstack service - localstackEnabled := m.configHandler.GetBool("aws.localstack.enabled") - if localstackEnabled { - localstackService := services.NewMockService() - m.injector.Register("localstackService", localstackService) - } - - // Create mock registry services if Docker and Registries are defined - if contextConfig.Docker != nil && contextConfig.Docker.Registries != nil { - for key := range contextConfig.Docker.Registries { - service := services.NewMockService() - service.SetName(key) - serviceName := fmt.Sprintf("registryService.%s", key) - m.injector.Register(serviceName, service) - } - } - - // Create mock cluster services - clusterEnabled := m.configHandler.GetBool("cluster.enabled") - if clusterEnabled { - controlPlaneCount := m.configHandler.GetInt("cluster.controlplanes.count") - workerCount := m.configHandler.GetInt("cluster.workers.count") - - clusterDriver := m.configHandler.GetString("cluster.driver") - - // Create mock talos cluster - if clusterDriver == "talos" { - for i := 1; i <= controlPlaneCount; i++ { - controlPlaneService := services.NewMockService() - controlPlaneService.SetName(fmt.Sprintf("controlplane-%d", i)) - serviceName := fmt.Sprintf("clusterNode.controlplane-%d", i) - m.injector.Register(serviceName, controlPlaneService) - } - for i := 1; i <= workerCount; i++ { - workerService := services.NewMockService() - workerService.SetName(fmt.Sprintf("worker-%d", i)) - serviceName := fmt.Sprintf("clusterNode.worker-%d", i) - m.injector.Register(serviceName, workerService) - } - } - } - return nil } -// CreateVirtualizationComponents calls the mock CreateVirtualizationComponentsFunc if set, otherwise creates mock components -func (c *MockController) CreateVirtualizationComponents() error { - if c.CreateVirtualizationComponentsFunc != nil { - return c.CreateVirtualizationComponentsFunc() - } - - vmDriver := c.configHandler.GetString("vm.driver") - dockerEnabled := c.configHandler.GetBool("docker.enabled") - - if vmDriver != "" { - // Create and register the RealNetworkInterfaceProvider instance - networkInterfaceProvider := &network.MockNetworkInterfaceProvider{} - c.injector.Register("networkInterfaceProvider", networkInterfaceProvider) - - // Create and register the ssh client - sshClient := ssh.NewMockSSHClient() - c.injector.Register("sshClient", sshClient) - - // Create and register the secure shell - secureShell := shell.NewSecureShell(c.injector) - c.injector.Register("secureShell", secureShell) - } - - // Create mock colima components - if vmDriver == "colima" { - // Create mock colima virtual machine - colimaVirtualMachine := virt.NewMockVirt() - c.injector.Register("virtualMachine", colimaVirtualMachine) - - // Create mock colima network manager - networkManager := network.NewMockNetworkManager() - c.injector.Register("networkManager", networkManager) +// CreateVirtualizationComponents implements the Controller interface +func (m *MockController) CreateVirtualizationComponents() error { + if m.CreateVirtualizationComponentsFunc != nil { + return m.CreateVirtualizationComponentsFunc() } - - // Create mock docker container runtime - if dockerEnabled { - containerRuntime := virt.NewMockVirt() - c.injector.Register("containerRuntime", containerRuntime) - } - return nil } -// CreateStackComponents calls the mock CreateStackComponentsFunc if set, otherwise creates mock components -func (c *MockController) CreateStackComponents() error { - if c.CreateStackComponentsFunc != nil { - return c.CreateStackComponentsFunc() +// CreateStackComponents implements the Controller interface +func (m *MockController) CreateStackComponents() error { + if m.CreateStackComponentsFunc != nil { + return m.CreateStackComponentsFunc() } - - // Create a new stack - stackInstance := stack.NewMockStack(c.injector) - c.injector.Register("stack", stackInstance) - return nil } -// WriteConfigurationFiles calls the mock WriteConfigurationFilesFunc if set, otherwise calls the parent function -func (c *MockController) WriteConfigurationFiles() error { - if c.WriteConfigurationFilesFunc != nil { - return c.WriteConfigurationFilesFunc() +// WriteConfigurationFiles implements the Controller interface +func (m *MockController) WriteConfigurationFiles() error { + if m.WriteConfigurationFilesFunc != nil { + return m.WriteConfigurationFilesFunc() } return nil } -// ResolveInjector calls the mock ResolveInjectorFunc if set, otherwise returns a mock injector -func (c *MockController) ResolveInjector() di.Injector { - if c.ResolveInjectorFunc != nil { - return c.ResolveInjectorFunc() +// ResolveInjector implements the Controller interface +func (m *MockController) ResolveInjector() di.Injector { + if m.ResolveInjectorFunc != nil { + return m.ResolveInjectorFunc() } - return c.BaseController.ResolveInjector() + return nil } -// ResolveConfigHandler calls the mock ResolveConfigHandlerFunc if set, otherwise calls the parent function -func (c *MockController) ResolveConfigHandler() config.ConfigHandler { - if c.ResolveConfigHandlerFunc != nil { - return c.ResolveConfigHandlerFunc() +// ResolveConfigHandler implements the Controller interface +func (m *MockController) ResolveConfigHandler() config.ConfigHandler { + if m.ResolveConfigHandlerFunc != nil { + return m.ResolveConfigHandlerFunc() } - return c.BaseController.ResolveConfigHandler() + return nil } -// ResolveEnvPrinter calls the mock ResolveEnvPrinterFunc if set, otherwise calls the parent function -func (c *MockController) ResolveEnvPrinter(name string) env.EnvPrinter { - if c.ResolveEnvPrinterFunc != nil { - return c.ResolveEnvPrinterFunc(name) +// ResolveEnvPrinter implements the Controller interface +func (m *MockController) ResolveEnvPrinter(name string) env.EnvPrinter { + if m.ResolveEnvPrinterFunc != nil { + return m.ResolveEnvPrinterFunc(name) } - return c.BaseController.ResolveEnvPrinter(name) + return nil } -// ResolveAllEnvPrinters calls the mock ResolveAllEnvPrintersFunc if set, otherwise calls the parent function -func (c *MockController) ResolveAllEnvPrinters() []env.EnvPrinter { - if c.ResolveAllEnvPrintersFunc != nil { - return c.ResolveAllEnvPrintersFunc() +// ResolveAllEnvPrinters implements the Controller interface +func (m *MockController) ResolveAllEnvPrinters() []env.EnvPrinter { + if m.ResolveAllEnvPrintersFunc != nil { + return m.ResolveAllEnvPrintersFunc() } - return c.BaseController.ResolveAllEnvPrinters() + return nil } -// ResolveShell calls the mock ResolveShellFunc if set, otherwise calls the parent function -func (c *MockController) ResolveShell() shell.Shell { - if c.ResolveShellFunc != nil { - return c.ResolveShellFunc() +// ResolveShell implements the Controller interface +func (m *MockController) ResolveShell() shell.Shell { + if m.ResolveShellFunc != nil { + return m.ResolveShellFunc() } - return c.BaseController.ResolveShell() + return nil } -// ResolveSecureShell calls the mock ResolveSecureShellFunc if set, otherwise calls the parent function -func (c *MockController) ResolveSecureShell() shell.Shell { - if c.ResolveSecureShellFunc != nil { - return c.ResolveSecureShellFunc() +// ResolveSecureShell implements the Controller interface +func (m *MockController) ResolveSecureShell() shell.Shell { + if m.ResolveSecureShellFunc != nil { + return m.ResolveSecureShellFunc() } - return c.BaseController.ResolveSecureShell() + return nil } -// ResolveToolsManager calls the mock ResolveToolsManagerFunc if set, otherwise calls the parent function -func (c *MockController) ResolveToolsManager() tools.ToolsManager { - if c.ResolveToolsManagerFunc != nil { - return c.ResolveToolsManagerFunc() +// ResolveToolsManager implements the Controller interface +func (m *MockController) ResolveToolsManager() tools.ToolsManager { + if m.ResolveToolsManagerFunc != nil { + return m.ResolveToolsManagerFunc() } - return c.BaseController.ResolveToolsManager() + return nil } -// ResolveNetworkManager calls the mock ResolveNetworkManagerFunc if set, otherwise calls the parent function -func (c *MockController) ResolveNetworkManager() network.NetworkManager { - if c.ResolveNetworkManagerFunc != nil { - return c.ResolveNetworkManagerFunc() +// ResolveNetworkManager implements the Controller interface +func (m *MockController) ResolveNetworkManager() network.NetworkManager { + if m.ResolveNetworkManagerFunc != nil { + return m.ResolveNetworkManagerFunc() } - return c.BaseController.ResolveNetworkManager() + return nil } -// ResolveService calls the mock ResolveServiceFunc if set, otherwise calls the parent function -func (c *MockController) ResolveService(name string) services.Service { - if c.ResolveServiceFunc != nil { - return c.ResolveServiceFunc(name) +// ResolveService implements the Controller interface +func (m *MockController) ResolveService(name string) services.Service { + if m.ResolveServiceFunc != nil { + return m.ResolveServiceFunc(name) } - return c.BaseController.ResolveService(name) + return nil } -// ResolveAllServices calls the mock ResolveAllServicesFunc if set, otherwise calls the parent function -func (c *MockController) ResolveAllServices() []services.Service { - if c.ResolveAllServicesFunc != nil { - return c.ResolveAllServicesFunc() +// ResolveAllServices implements the Controller interface +func (m *MockController) ResolveAllServices() []services.Service { + if m.ResolveAllServicesFunc != nil { + return m.ResolveAllServicesFunc() } - return c.BaseController.ResolveAllServices() + return nil } -// ResolveVirtualMachine calls the mock ResolveVirtualMachineFunc if set, otherwise calls the parent function -func (c *MockController) ResolveVirtualMachine() virt.VirtualMachine { - if c.ResolveVirtualMachineFunc != nil { - return c.ResolveVirtualMachineFunc() +// ResolveVirtualMachine implements the Controller interface +func (m *MockController) ResolveVirtualMachine() virt.VirtualMachine { + if m.ResolveVirtualMachineFunc != nil { + return m.ResolveVirtualMachineFunc() } - return c.BaseController.ResolveVirtualMachine() + return nil } -// ResolveContainerRuntime calls the mock ResolveContainerRuntimeFunc if set, otherwise calls the parent function -func (c *MockController) ResolveContainerRuntime() virt.ContainerRuntime { - if c.ResolveContainerRuntimeFunc != nil { - return c.ResolveContainerRuntimeFunc() +// ResolveContainerRuntime implements the Controller interface +func (m *MockController) ResolveContainerRuntime() virt.ContainerRuntime { + if m.ResolveContainerRuntimeFunc != nil { + return m.ResolveContainerRuntimeFunc() } - return c.BaseController.ResolveContainerRuntime() + return nil } -// ResolveAllGenerators calls the mock ResolveAllGeneratorsFunc if set, otherwise calls the parent function -func (c *MockController) ResolveAllGenerators() []generators.Generator { - if c.ResolveAllGeneratorsFunc != nil { - return c.ResolveAllGeneratorsFunc() +// ResolveAllGenerators implements the Controller interface +func (m *MockController) ResolveAllGenerators() []generators.Generator { + if m.ResolveAllGeneratorsFunc != nil { + return m.ResolveAllGeneratorsFunc() } - return c.BaseController.ResolveAllGenerators() + return nil } -// ResolveStack calls the mock ResolveStackFunc if set, otherwise calls the parent function -func (c *MockController) ResolveStack() stack.Stack { - if c.ResolveStackFunc != nil { - return c.ResolveStackFunc() +// ResolveStack implements the Controller interface +func (m *MockController) ResolveStack() stack.Stack { + if m.ResolveStackFunc != nil { + return m.ResolveStackFunc() } - return c.BaseController.ResolveStack() + return nil } -// ResolveBlueprintHandler calls the mock ResolveBlueprintHandlerFunc if set, otherwise calls the parent function -func (c *MockController) ResolveBlueprintHandler() blueprint.BlueprintHandler { - if c.ResolveBlueprintHandlerFunc != nil { - return c.ResolveBlueprintHandlerFunc() +// ResolveBlueprintHandler implements the Controller interface +func (m *MockController) ResolveBlueprintHandler() blueprint.BlueprintHandler { + if m.ResolveBlueprintHandlerFunc != nil { + return m.ResolveBlueprintHandlerFunc() } - return c.BaseController.ResolveBlueprintHandler() + return nil } -// ResolveAllSecretsProviders calls the mock ResolveAllSecretsProvidersFunc if set, otherwise calls the parent function -func (c *MockController) ResolveAllSecretsProviders() []secrets.SecretsProvider { - if c.ResolveAllSecretsProvidersFunc != nil { - return c.ResolveAllSecretsProvidersFunc() +// ResolveAllSecretsProviders implements the Controller interface +func (m *MockController) ResolveAllSecretsProviders() []secrets.SecretsProvider { + if m.ResolveAllSecretsProvidersFunc != nil { + return m.ResolveAllSecretsProvidersFunc() } - return c.BaseController.ResolveAllSecretsProviders() + return nil } -// SetEnvironmentVariables calls the mock SetEnvironmentVariablesFunc if set, otherwise calls the parent function -func (c *MockController) SetEnvironmentVariables() error { - if c.SetEnvironmentVariablesFunc != nil { - return c.SetEnvironmentVariablesFunc() +// SetEnvironmentVariables implements the Controller interface +func (m *MockController) SetEnvironmentVariables() error { + if m.SetEnvironmentVariablesFunc != nil { + return m.SetEnvironmentVariablesFunc() } - return c.BaseController.SetEnvironmentVariables() + return nil } // Ensure MockController implements Controller diff --git a/pkg/controller/mock_controller_test.go b/pkg/controller/mock_controller_test.go index 57a5e06ad..9adb587d4 100644 --- a/pkg/controller/mock_controller_test.go +++ b/pkg/controller/mock_controller_test.go @@ -1,6 +1,8 @@ package controller import ( + "fmt" + "strings" "testing" "github.com/windsorcli/cli/api/v1alpha1" @@ -52,21 +54,57 @@ func TestMockController_Initialize(t *testing.T) { } func TestMockController_InitializeComponents(t *testing.T) { - t.Run("InitializeComponents", func(t *testing.T) { + t.Run("WithInitializeComponentsFunc", func(t *testing.T) { // Given a new injector and mock controller mocks := setupMocks(t) mockCtrl := NewMockController(mocks.Injector) - // Initialize the controller - mockCtrl.Initialize() - // And the InitializeComponentsFunc is set to return nil mockCtrl.InitializeComponentsFunc = func() error { return nil } + // When InitializeComponents is called - if err := mockCtrl.InitializeComponents(); err != nil { - // Then no error should be returned + err := mockCtrl.InitializeComponents() + + // Then no error should be returned + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + }) + + t.Run("WithInitializeComponentsFuncError", func(t *testing.T) { + // Given a new injector and mock controller + mocks := setupMocks(t) + mockCtrl := NewMockController(mocks.Injector) + + // And the InitializeComponentsFunc is set to return an error + mockCtrl.InitializeComponentsFunc = func() error { + return fmt.Errorf("initialize components error") + } + + // When InitializeComponents is called + err := mockCtrl.InitializeComponents() + + // Then the error should be returned + if err == nil { + t.Fatal("expected error, got nil") + } + if !strings.Contains(err.Error(), "initialize components error") { + t.Errorf("expected error to contain 'initialize components error', got %v", err) + } + }) + + t.Run("WithoutInitializeComponentsFunc", func(t *testing.T) { + // Given a new injector and mock controller + mocks := setupMocks(t) + mockCtrl := NewMockController(mocks.Injector) + + // When InitializeComponents is called without setting InitializeComponentsFunc + err := mockCtrl.InitializeComponents() + + // Then no error should be returned + if err != nil { t.Fatalf("expected no error, got %v", err) } }) @@ -394,16 +432,16 @@ func TestMockController_ResolveInjector(t *testing.T) { mocks := setupMocks(t) mockCtrl := NewMockController(mocks.Injector) // When ResolveInjector is called without setting ResolveInjectorFunc - if injector := mockCtrl.ResolveInjector(); injector != mocks.Injector { - // Then the returned injector should be the same as the created injector - t.Fatalf("expected %v, got %v", mocks.Injector, injector) + if injector := mockCtrl.ResolveInjector(); injector != nil { + // Then the returned injector should be nil + t.Fatalf("expected nil, got %v", injector) } }) } func TestMockController_ResolveConfigHandler(t *testing.T) { t.Run("Success", func(t *testing.T) { - // Given a new mock config handler, mock injector, and mock controller + // Given a new mock injector and mock controller mocks := setupMocks(t) mockCtrl := NewMockController(mocks.Injector) // And the ResolveConfigHandlerFunc is set to return the expected config handler @@ -424,9 +462,9 @@ func TestMockController_ResolveConfigHandler(t *testing.T) { mockCtrl := NewMockController(mocks.Injector) // When ResolveConfigHandler is called without setting ResolveConfigHandlerFunc configHandler := mockCtrl.ResolveConfigHandler() - if configHandler != mocks.ConfigHandler { - // Then the returned config handler should be the same as the created config handler - t.Fatalf("expected %v, got %v", mocks.ConfigHandler, configHandler) + // Then the returned config handler should be nil + if configHandler != nil { + t.Fatalf("expected nil, got %v", configHandler) } }) } @@ -458,16 +496,16 @@ func TestMockController_ResolveAllSecretsProviders(t *testing.T) { mockCtrl := NewMockController(mocks.Injector) // When ResolveAllSecretsProviders is called without setting ResolveAllSecretsProvidersFunc secretsProviders := mockCtrl.ResolveAllSecretsProviders() - if len(secretsProviders) != 1 { - // Then the returned secrets provider should be the same as the created secrets provider - t.Fatalf("expected %v, got %v", 1, len(secretsProviders)) + if len(secretsProviders) != 0 { + // Then the returned secrets provider should be nil + t.Fatalf("expected 0, got %v", len(secretsProviders)) } }) } func TestMockController_ResolveEnvPrinter(t *testing.T) { t.Run("Success", func(t *testing.T) { - // Given a new mock env printer, mock injector, and mock controller + // Given a new mock injector and mock controller mocks := setupMocks(t) mockCtrl := NewMockController(mocks.Injector) // And the ResolveEnvPrinterFunc is set to return the expected env printer @@ -488,9 +526,9 @@ func TestMockController_ResolveEnvPrinter(t *testing.T) { mockCtrl := NewMockController(mocks.Injector) // When ResolveEnvPrinter is called without setting ResolveEnvPrinterFunc envPrinter := mockCtrl.ResolveEnvPrinter("envPrinter1") - if envPrinter != mocks.EnvPrinter { - // Then the returned env printer should be the same as the created env printer - t.Fatalf("expected %v, got %v", mocks.EnvPrinter, envPrinter) + // Then the returned env printer should be nil + if envPrinter != nil { + t.Fatalf("expected nil, got %v", envPrinter) } }) } @@ -507,9 +545,15 @@ func TestMockController_ResolveAllEnvPrinters(t *testing.T) { // When ResolveAllEnvPrinters is called envPrinters := mockCtrl.ResolveAllEnvPrinters() if len(envPrinters) != 2 { - // Then the length of the returned env printers list should be 2 + // Then the length of the returned env printers list should be the same as the expected services list t.Fatalf("expected %v, got %v", 2, len(envPrinters)) } + for _, envPrinter := range envPrinters { + if envPrinter != mocks.EnvPrinter { + // Then each env printer in the returned list should match the expected env printer + t.Fatalf("expected %v, got %v", mocks.EnvPrinter, envPrinter) + } + } }) t.Run("NoResolveAllEnvPrintersFunc", func(t *testing.T) { @@ -518,9 +562,9 @@ func TestMockController_ResolveAllEnvPrinters(t *testing.T) { mockCtrl := NewMockController(mocks.Injector) // When ResolveAllEnvPrinters is called without setting ResolveAllEnvPrintersFunc envPrinters := mockCtrl.ResolveAllEnvPrinters() - if len(envPrinters) != 3 { - // Then the length of the returned env printers list should be 0 - t.Fatalf("expected %v, got %v", 0, len(envPrinters)) + // Then the returned env printers list should be nil + if len(envPrinters) != 0 { + t.Fatalf("expected 0, got %v", len(envPrinters)) } }) } @@ -548,9 +592,9 @@ func TestMockController_ResolveShell(t *testing.T) { mockCtrl := NewMockController(mocks.Injector) // When ResolveShell is called without setting ResolveShellFunc shellInstance := mockCtrl.ResolveShell() - if shellInstance != mocks.Shell { - // Then the returned shell should be the same as the created shell - t.Fatalf("expected %v, got %v", mocks.Shell, shellInstance) + // Then the returned shell should be nil + if shellInstance != nil { + t.Fatalf("expected nil, got %v", shellInstance) } }) } @@ -578,9 +622,9 @@ func TestMockController_ResolveSecureShell(t *testing.T) { mockCtrl := NewMockController(mocks.Injector) // When ResolveSecureShell is called without setting ResolveSecureShellFunc secureShell := mockCtrl.ResolveSecureShell() - if secureShell != mocks.SecureShell { - // Then the returned secure shell should be the same as the created secure shell - t.Fatalf("expected %v, got %v", mocks.SecureShell, secureShell) + // Then the returned secure shell should be nil + if secureShell != nil { + t.Fatalf("expected nil, got %v", secureShell) } }) } @@ -608,9 +652,9 @@ func TestMockController_ResolveToolsManager(t *testing.T) { mockCtrl := NewMockController(mocks.Injector) // When ResolveToolsManager is called without setting ResolveToolsManagerFunc toolsManager := mockCtrl.ResolveToolsManager() - if toolsManager != mocks.ToolsManager { - // Then the returned tools manager should be the same as the created tools manager - t.Fatalf("expected %v, got %v", mocks.ToolsManager, toolsManager) + // Then the returned tools manager should be nil + if toolsManager != nil { + t.Fatalf("expected nil, got %v", toolsManager) } }) } @@ -638,9 +682,9 @@ func TestMockController_ResolveNetworkManager(t *testing.T) { mockCtrl := NewMockController(mocks.Injector) // When ResolveNetworkManager is called without setting ResolveNetworkManagerFunc networkManager := mockCtrl.ResolveNetworkManager() - if networkManager != mocks.NetworkManager { - // Then the returned network manager should be the same as the created network manager - t.Fatalf("expected %v, got %v", mocks.NetworkManager, networkManager) + // Then the returned network manager should be nil + if networkManager != nil { + t.Fatalf("expected nil, got %v", networkManager) } }) } @@ -668,10 +712,9 @@ func TestMockController_ResolveService(t *testing.T) { mockCtrl := NewMockController(mocks.Injector) // When ResolveService is called without setting ResolveServiceFunc service := mockCtrl.ResolveService("service1") - // Then the returned service should be the one resolved by the base controller - expectedService := mockCtrl.BaseController.ResolveService("service1") - if service != expectedService { - t.Fatalf("expected %v, got %v", expectedService, service) + // Then the returned service should be nil + if service != nil { + t.Fatalf("expected nil, got %v", service) } }) } @@ -703,9 +746,11 @@ func TestMockController_ResolveAllServices(t *testing.T) { // Given a new mock injector and mock controller mocks := setupMocks(t) mockCtrl := NewMockController(mocks.Injector) + // When ResolveAllServices is called without setting ResolveAllServicesFunc services := mockCtrl.ResolveAllServices() - if len(services) != 2 { - t.Fatalf("expected %v, got %v", 0, len(services)) + // Then the returned services list should be nil + if len(services) != 0 { + t.Fatalf("expected 0, got %v", len(services)) } }) } @@ -733,9 +778,9 @@ func TestMockController_ResolveVirtualMachine(t *testing.T) { mockCtrl := NewMockController(mocks.Injector) // When ResolveVirtualMachine is called without setting ResolveVirtualMachineFunc virtualMachine := mockCtrl.ResolveVirtualMachine() - // Then the returned virtual machine should be the same as the created virtual machine - if virtualMachine != mocks.VirtualMachine { - t.Fatalf("expected %v, got %v", mocks.VirtualMachine, virtualMachine) + // Then the returned virtual machine should be nil + if virtualMachine != nil { + t.Fatalf("expected nil, got %v", virtualMachine) } }) } @@ -763,9 +808,9 @@ func TestMockController_ResolveContainerRuntime(t *testing.T) { mockCtrl := NewMockController(mocks.Injector) // When ResolveContainerRuntime is called without setting ResolveContainerRuntimeFunc containerRuntime := mockCtrl.ResolveContainerRuntime() - // Then the returned container runtime should be the same as the created container runtime - if containerRuntime != mocks.ContainerRuntime { - t.Fatalf("expected %v, got %v", mocks.ContainerRuntime, containerRuntime) + // Then the returned container runtime should be nil + if containerRuntime != nil { + t.Fatalf("expected nil, got %v", containerRuntime) } }) } @@ -794,8 +839,8 @@ func TestMockController_ResolveAllGenerators(t *testing.T) { // When ResolveAllGenerators is called without setting ResolveAllGeneratorsFunc generators := mockCtrl.ResolveAllGenerators() // Then the length of the returned generators list should be 0 - if len(generators) != 1 { - t.Fatalf("expected %v, got %v", 0, len(generators)) + if len(generators) != 0 { + t.Fatalf("expected 0, got %v", len(generators)) } }) } @@ -820,16 +865,12 @@ func TestMockController_ResolveStack(t *testing.T) { t.Run("NoResolveStackFunc", func(t *testing.T) { // Given a new mock injector and mock controller mocks := setupMocks(t) - // Register a nil stack with the injector - mocks.Injector.Register("stack", nil) mockCtrl := NewMockController(mocks.Injector) // When ResolveStack is called without setting ResolveStackFunc stackInstance := mockCtrl.ResolveStack() // Then the returned stack instance should be nil if stackInstance != nil { t.Fatalf("expected nil, got %v", stackInstance) - } else { - t.Logf("expected nil, got nil") } }) } @@ -854,16 +895,12 @@ func TestMockController_ResolveBlueprintHandler(t *testing.T) { t.Run("NoResolveBlueprintHandlerFunc", func(t *testing.T) { // Given a new mock injector and mock controller mocks := setupMocks(t) - // Register a nil blueprint handler with the injector - mocks.Injector.Register("blueprintHandler", nil) mockCtrl := NewMockController(mocks.Injector) // When ResolveBlueprintHandler is called without setting ResolveBlueprintHandlerFunc blueprintHandler := mockCtrl.ResolveBlueprintHandler() // Then the returned blueprint handler should be nil if blueprintHandler != nil { t.Fatalf("expected nil, got %v", blueprintHandler) - } else { - t.Logf("expected nil, got nil") } }) } @@ -872,39 +909,10 @@ func TestMockController_SetEnvironmentVariables(t *testing.T) { t.Run("Success", func(t *testing.T) { // Given a mock controller with a SetEnvironmentVariables function mockInjector := di.NewInjector() - - // Set up the environment printer mock with complete implementation - mockEnvPrinter := env.NewMockEnvPrinter() - mockEnvPrinter.GetEnvVarsFunc = func() (map[string]string, error) { - return map[string]string{ - "TEST_VAR": "test_value", - "WINDSOR_SESSION_TOKEN": "mock-token", - }, nil - } - mockInjector.Register("env", mockEnvPrinter) - - // Set up a proper shell mock with GetSessionToken implementation - mockShell := shell.NewMockShell() - mockShell.GetSessionTokenFunc = func() (string, error) { - return "mock-token", nil - } - // Mock WriteResetToken to prevent file operations - mockShell.WriteResetTokenFunc = func() (string, error) { - // Just pretend it worked without creating any files - return "/mock/project/root/.windsor/.session.mock-token", nil - } - mockInjector.Register("shell", mockShell) - mockController := NewMockController(mockInjector) - // Create a map to track what environment variables were set - setEnvCalls := make(map[string]string) - - // Mock the osSetenv function - originalSetenv := osSetenv - defer func() { osSetenv = originalSetenv }() - osSetenv = func(key, value string) error { - setEnvCalls[key] = value + // Set up the mock function to return nil + mockController.SetEnvironmentVariablesFunc = func() error { return nil } @@ -915,52 +923,13 @@ func TestMockController_SetEnvironmentVariables(t *testing.T) { if err != nil { t.Errorf("expected no error, got %v", err) } - - // Verify that environment variables were set - if len(setEnvCalls) == 0 { - t.Errorf("expected environment variables to be set") - } }) t.Run("NoSetEnvironmentVariablesFunc", func(t *testing.T) { // Given a new injector and mock controller mockInjector := di.NewInjector() - - // Set up the environment printer mock - mockEnvPrinter := env.NewMockEnvPrinter() - mockEnvPrinter.GetEnvVarsFunc = func() (map[string]string, error) { - return map[string]string{ - "TEST_VAR": "test_value", - "WINDSOR_SESSION_TOKEN": "mock-token", - }, nil - } - mockInjector.Register("env", mockEnvPrinter) - - // Set up the shell mock with GetSessionToken implementation - mockShell := shell.NewMockShell() - mockShell.GetSessionTokenFunc = func() (string, error) { - return "mock-token", nil - } - // Mock WriteResetToken to prevent file operations - mockShell.WriteResetTokenFunc = func() (string, error) { - // Just pretend it worked without creating any files - return "/mock/project/root/.windsor/.session.mock-token", nil - } - mockInjector.Register("shell", mockShell) - mockCtrl := NewMockController(mockInjector) - // Create a map to track what environment variables were set - setEnvCalls := make(map[string]string) - - // Mock the osSetenv function - originalSetenv := osSetenv - defer func() { osSetenv = originalSetenv }() - osSetenv = func(key, value string) error { - setEnvCalls[key] = value - return nil - } - // When SetEnvironmentVariables is called without setting SetEnvironmentVariablesFunc err := mockCtrl.SetEnvironmentVariables() @@ -968,10 +937,5 @@ func TestMockController_SetEnvironmentVariables(t *testing.T) { if err != nil { t.Errorf("expected no error, got %v", err) } - - // Verify that environment variables were set - if len(setEnvCalls) == 0 { - t.Errorf("expected environment variables to be set") - } }) } diff --git a/pkg/controller/real_controller.go b/pkg/controller/real_controller.go deleted file mode 100644 index bdb8266df..000000000 --- a/pkg/controller/real_controller.go +++ /dev/null @@ -1,300 +0,0 @@ -package controller - -import ( - "fmt" - "os" - "path/filepath" - "strings" - - "github.com/windsorcli/cli/pkg/blueprint" - "github.com/windsorcli/cli/pkg/config" - "github.com/windsorcli/cli/pkg/di" - "github.com/windsorcli/cli/pkg/env" - "github.com/windsorcli/cli/pkg/generators" - "github.com/windsorcli/cli/pkg/network" - "github.com/windsorcli/cli/pkg/secrets" - "github.com/windsorcli/cli/pkg/services" - "github.com/windsorcli/cli/pkg/shell" - "github.com/windsorcli/cli/pkg/ssh" - "github.com/windsorcli/cli/pkg/stack" - "github.com/windsorcli/cli/pkg/tools" - "github.com/windsorcli/cli/pkg/virt" - - secretsConfigType "github.com/windsorcli/cli/api/v1alpha1/secrets" -) - -// RealController struct implements the RealController interface. -type RealController struct { - BaseController -} - -// ============================================================================= -// Constructor -// ============================================================================= - -// NewRealController creates a new controller. -func NewRealController(injector di.Injector) *RealController { - return &RealController{ - BaseController: BaseController{ - injector: injector, - }, - } -} - -// ============================================================================= -// Public Methods -// ============================================================================= - -// CreateCommonComponents sets up config and shell for command execution. -// It registers and initializes these components. -func (c *RealController) CreateCommonComponents() error { - configHandler := config.NewYamlConfigHandler(c.injector) - c.injector.Register("configHandler", configHandler) - c.configHandler = configHandler - - shell := shell.NewDefaultShell(c.injector) - c.injector.Register("shell", shell) - - // Testing Note: The following is hard to test as these are registered - // above and can't be mocked externally. There may be a better way to - // organize this in the future but this works for now, so we don't expect - // these lines to be covered by tests. - - // Initialize the config handler - if err := configHandler.Initialize(); err != nil { - return fmt.Errorf("error initializing config handler: %w", err) - } - - // Initialize the shell - if err := shell.Initialize(); err != nil { - return fmt.Errorf("error initializing shell: %w", err) - } - - return nil -} - -// Initializes project components like generators and tools manager. Registers -// and initializes blueprint, terraform, and kustomize generators. Determines -// and sets the tools manager: aqua, asdf, or default, based on config or setup. -func (c *RealController) CreateProjectComponents() error { - gitGenerator := generators.NewGitGenerator(c.injector) - c.injector.Register("gitGenerator", gitGenerator) - - blueprintHandler := blueprint.NewBlueprintHandler(c.injector) - c.injector.Register("blueprintHandler", blueprintHandler) - - terraformGenerator := generators.NewTerraformGenerator(c.injector) - c.injector.Register("terraformGenerator", terraformGenerator) - - kustomizeGenerator := generators.NewKustomizeGenerator(c.injector) - c.injector.Register("kustomizeGenerator", kustomizeGenerator) - - toolsManagerType := c.configHandler.GetString("toolsManager") - var toolsManager tools.ToolsManager - - if toolsManagerType == "" { - var err error - toolsManagerType, err = tools.CheckExistingToolsManager(c.configHandler.GetString("projectRoot")) - if err != nil { - // Not tested as this is a static function and we can't mock it - return fmt.Errorf("error checking existing tools manager: %w", err) - } - } - - switch toolsManagerType { - // case "aqua": - // TODO: Implement aqua tools manager - // case "asdf": - // TODO: Implement asdf tools manager - default: - toolsManager = tools.NewToolsManager(c.injector) - } - - c.injector.Register("toolsManager", toolsManager) - - return nil -} - -// CreateEnvComponents registers environment printers for various services (AWS, Docker, Kube, etc). -// AWS and Docker printers are only registered if their respective services are enabled. -// Windsor printer handles custom environment variables and secrets. -func (c *RealController) CreateEnvComponents() error { - envPrinters := map[string]func(di.Injector) env.EnvPrinter{ - "awsEnv": func(injector di.Injector) env.EnvPrinter { return env.NewAwsEnvPrinter(injector) }, - "dockerEnv": func(injector di.Injector) env.EnvPrinter { return env.NewDockerEnvPrinter(injector) }, - "kubeEnv": func(injector di.Injector) env.EnvPrinter { return env.NewKubeEnvPrinter(injector) }, - "omniEnv": func(injector di.Injector) env.EnvPrinter { return env.NewOmniEnvPrinter(injector) }, - "talosEnv": func(injector di.Injector) env.EnvPrinter { return env.NewTalosEnvPrinter(injector) }, - "terraformEnv": func(injector di.Injector) env.EnvPrinter { return env.NewTerraformEnvPrinter(injector) }, - "windsorEnv": func(injector di.Injector) env.EnvPrinter { return env.NewWindsorEnvPrinter(injector) }, - } - - for key, constructor := range envPrinters { - if key == "awsEnv" && !c.configHandler.GetBool("aws.enabled") { - continue - } - if key == "dockerEnv" && !c.configHandler.GetBool("docker.enabled") { - continue - } - envPrinter := constructor(c.injector) - c.injector.Register(key, envPrinter) - } - - return nil -} - -// CreateServiceComponents sets up services based on config, including DNS, -// Git livereload, Localstack, and Docker registries. If Talos is used, it -// registers control plane and worker services for the cluster. -func (c *RealController) CreateServiceComponents() error { - configHandler := c.configHandler - contextConfig := configHandler.GetConfig() - - if !configHandler.GetBool("docker.enabled") { - return nil - } - - dnsEnabled := configHandler.GetBool("dns.enabled") - if dnsEnabled { - dnsService := services.NewDNSService(c.injector) - dnsService.SetName("dns") - c.injector.Register("dnsService", dnsService) - } - - gitLivereloadEnabled := configHandler.GetBool("git.livereload.enabled") - if gitLivereloadEnabled { - gitLivereloadService := services.NewGitLivereloadService(c.injector) - gitLivereloadService.SetName("git") - c.injector.Register("gitLivereloadService", gitLivereloadService) - } - - localstackEnabled := configHandler.GetBool("aws.localstack.enabled") - if localstackEnabled { - localstackService := services.NewLocalstackService(c.injector) - localstackService.SetName("aws") - c.injector.Register("localstackService", localstackService) - } - - if contextConfig.Docker != nil && contextConfig.Docker.Registries != nil { - // Not unit tested currently as we can't easily create registry entries in tests - for key := range contextConfig.Docker.Registries { - service := services.NewRegistryService(c.injector) - service.SetName(key) - serviceName := fmt.Sprintf("registryService.%s", key) - c.injector.Register(serviceName, service) - } - } - - clusterEnabled := configHandler.GetBool("cluster.enabled") - if clusterEnabled { - controlPlaneCount := configHandler.GetInt("cluster.controlplanes.count") - workerCount := configHandler.GetInt("cluster.workers.count") - - clusterDriver := configHandler.GetString("cluster.driver") - - if clusterDriver == "talos" { - for i := 1; i <= controlPlaneCount; i++ { - controlPlaneService := services.NewTalosService(c.injector, "controlplane") - controlPlaneService.SetName(fmt.Sprintf("controlplane-%d", i)) - serviceName := fmt.Sprintf("clusterNode.controlplane-%d", i) - c.injector.Register(serviceName, controlPlaneService) - } - for i := 1; i <= workerCount; i++ { - workerService := services.NewTalosService(c.injector, "worker") - workerService.SetName(fmt.Sprintf("worker-%d", i)) - serviceName := fmt.Sprintf("clusterNode.worker-%d", i) - c.injector.Register(serviceName, workerService) - } - } - } - - return nil -} - -// CreateVirtualizationComponents sets up virtualization based on config. -// Registers network, SSH, and VM components for Colima. Adds Docker runtime if enabled. -func (c *RealController) CreateVirtualizationComponents() error { - configHandler := c.configHandler - - vmDriver := configHandler.GetString("vm.driver") - dockerEnabled := configHandler.GetBool("docker.enabled") - - if vmDriver == "colima" { - networkInterfaceProvider := &network.RealNetworkInterfaceProvider{} - c.injector.Register("networkInterfaceProvider", networkInterfaceProvider) - - sshClient := ssh.NewSSHClient() - c.injector.Register("sshClient", sshClient) - - secureShell := shell.NewSecureShell(c.injector) - c.injector.Register("secureShell", secureShell) - - colimaVirtualMachine := virt.NewColimaVirt(c.injector) - c.injector.Register("virtualMachine", colimaVirtualMachine) - - networkManager := network.NewColimaNetworkManager(c.injector) - c.injector.Register("networkManager", networkManager) - } else { - networkManager := network.NewBaseNetworkManager(c.injector) - c.injector.Register("networkManager", networkManager) - } - - if dockerEnabled { - containerRuntime := virt.NewDockerVirt(c.injector) - c.injector.Register("containerRuntime", containerRuntime) - } - - return nil -} - -// CreateStackComponents creates stack components -func (c *RealController) CreateStackComponents() error { - stackInstance := stack.NewWindsorStack(c.injector) - c.injector.Register("stack", stackInstance) - - return nil -} - -// CreateSecretsProviders sets up the secrets provider based on config settings. -// It supports SOPS and 1Password CLI for decryption. -// Registers the appropriate secrets provider with the injector and config handler. -func (c *RealController) CreateSecretsProviders() error { - contextName := c.configHandler.GetContext() - configRoot, err := c.configHandler.GetConfigRoot() - if err != nil { - return fmt.Errorf("error getting config root: %w", err) - } - - secretsFilePaths := []string{"secrets.enc.yaml", "secrets.enc.yml"} - for _, filePath := range secretsFilePaths { - if _, err := osStat(filepath.Join(configRoot, filePath)); err == nil { - sopsSecretsProvider := secrets.NewSopsSecretsProvider(configRoot, c.injector) - c.injector.Register("sopsSecretsProvider", sopsSecretsProvider) - c.configHandler.SetSecretsProvider(sopsSecretsProvider) - } - } - - vaults, ok := c.configHandler.Get(fmt.Sprintf("contexts.%s.secrets.onepassword.vaults", contextName)).(map[string]secretsConfigType.OnePasswordVault) - if ok && len(vaults) > 0 { - useSDK := os.Getenv("OP_SERVICE_ACCOUNT_TOKEN") != "" - - for key, vault := range vaults { - vault.ID = key - var opSecretsProvider secrets.SecretsProvider - - if useSDK { - opSecretsProvider = secrets.NewOnePasswordSDKSecretsProvider(vault, c.injector) - } else { - opSecretsProvider = secrets.NewOnePasswordCLISecretsProvider(vault, c.injector) - } - - c.injector.Register(fmt.Sprintf("op%sSecretsProvider", strings.ToUpper(key[:1])+key[1:]), opSecretsProvider) - c.configHandler.SetSecretsProvider(opSecretsProvider) - } - } - - return nil -} - -// Ensure RealController implements the Controller interface -var _ Controller = (*RealController)(nil) diff --git a/pkg/controller/real_controller_test.go b/pkg/controller/real_controller_test.go deleted file mode 100644 index ab04183a3..000000000 --- a/pkg/controller/real_controller_test.go +++ /dev/null @@ -1,620 +0,0 @@ -package controller - -import ( - "fmt" - "os" - "path/filepath" - "strings" - "testing" - - secretsConfigType "github.com/windsorcli/cli/api/v1alpha1/secrets" - "github.com/windsorcli/cli/pkg/config" - "github.com/windsorcli/cli/pkg/secrets" -) - -// ============================================================================= -// Test Constructor -// ============================================================================= - -func TestNewRealController(t *testing.T) { - t.Run("NewRealController", func(t *testing.T) { - // Given a new test setup - mocks := setupMocks(t) - - // When creating a new real controller - controller := NewRealController(mocks.Injector) - - // Then the controller should not be nil - if controller == nil { - t.Fatalf("expected controller, got nil") - } else { - t.Logf("Success: controller created") - } - }) -} - -// ============================================================================= -// Test Public Methods -// ============================================================================= - -func TestRealController_CreateCommonComponents(t *testing.T) { - setup := func(t *testing.T) (Controller, *Mocks) { - t.Helper() - mocks := setupMocks(t) - controller := NewRealController(mocks.Injector) - err := controller.Initialize() - if err != nil { - t.Fatalf("Failed to initialize controller: %v", err) - } - return controller, mocks - } - - t.Run("Success", func(t *testing.T) { - // Given a new controller - controller, mocks := setup(t) - - // When creating common components - err := controller.CreateCommonComponents() - - // Then there should be no error - if err != nil { - t.Fatalf("expected no error, got %v", err) - } - - // And the components should be registered in the injector - if mocks.Injector.Resolve("configHandler") == nil { - t.Fatalf("expected configHandler to be registered, got error") - } - if mocks.Injector.Resolve("shell") == nil { - t.Fatalf("expected shell to be registered, got error") - } - }) -} - -func TestRealController_CreateSecretsProviders(t *testing.T) { - setup := func(t *testing.T) (Controller, *Mocks) { - t.Helper() - mocks := setupMocks(t) - controller := NewRealController(mocks.Injector) - err := controller.Initialize() - if err != nil { - t.Fatalf("Failed to initialize controller: %v", err) - } - return controller, mocks - } - - t.Run("SopsSecretsProviderExists", func(t *testing.T) { - // Given a new controller - controller, mocks := setup(t) - - // And a mock config handler that returns a config root - mockConfigHandler := config.NewMockConfigHandler() - mockConfigHandler.GetConfigRootFunc = func() (string, error) { - return "/mock/config/root", nil - } - mocks.Injector.Register("configHandler", mockConfigHandler) - controller.(*RealController).configHandler = mockConfigHandler - - // And a mock file system that simulates presence of secrets.enc.yaml - originalOsStat := osStat - defer func() { osStat = originalOsStat }() - osStat = func(name string) (os.FileInfo, error) { - if name == filepath.Join("/mock/config/root", "secrets.enc.yaml") { - return nil, nil - } - return nil, os.ErrNotExist - } - - // When creating the secrets provider - err := controller.CreateSecretsProviders() - - // Then there should be no error - if err != nil { - t.Fatalf("expected no error, got %v", err) - } - - // And the Sops secrets provider should be registered - if mocks.Injector.Resolve("sopsSecretsProvider") == nil { - t.Fatalf("expected sopsSecretsProvider to be registered, got error") - } - }) - - t.Run("NoSecretsFile", func(t *testing.T) { - // Given a new controller - controller, mocks := setup(t) - - // And a mock file system that simulates absence of secrets.enc files - originalOsStat := osStat - defer func() { osStat = originalOsStat }() - osStat = func(name string) (os.FileInfo, error) { - return nil, os.ErrNotExist - } - - // When creating the secrets provider - err := controller.CreateSecretsProviders() - - // Then there should be no error - if err != nil { - t.Fatalf("expected no error, got %v", err) - } - - // And the sopsSecretsProvider should not be registered since there are no secrets - if mocks.Injector.Resolve("sopsSecretsProvider") != nil { - t.Fatalf("expected no sopsSecretsProvider to be registered, got one") - } - }) - - t.Run("ErrorGettingConfigRoot", func(t *testing.T) { - // Given a new controller - controller, mocks := setup(t) - - // And a mock config handler that returns an error - mockConfigHandler := config.NewMockConfigHandler() - mockConfigHandler.GetConfigRootFunc = func() (string, error) { - return "", fmt.Errorf("mock error getting config root") - } - mocks.Injector.Register("configHandler", mockConfigHandler) - controller.(*RealController).configHandler = mockConfigHandler - - // When creating the secrets provider - err := controller.CreateSecretsProviders() - - // Then an error should occur - if err == nil || err.Error() != "error getting config root: mock error getting config root" { - t.Fatalf("expected error getting config root, got %v", err) - } - }) - - t.Run("OnePasswordVaultsExist", func(t *testing.T) { - // Given a new controller - controller, mocks := setup(t) - - // And a mock config handler that returns 1Password vaults - mockConfigHandler := config.NewMockConfigHandler() - mockConfigHandler.GetFunc = func(key string) any { - if key == "contexts.mock-context.secrets.onepassword.vaults" { - return map[string]secretsConfigType.OnePasswordVault{ - "vault1": {ID: "vault1"}, - "vault2": {ID: "vault2"}, - } - } - return nil - } - mocks.Injector.Register("configHandler", mockConfigHandler) - controller.(*RealController).configHandler = mockConfigHandler - - // When creating the secrets provider - err := controller.CreateSecretsProviders() - - // Then there should be no error - if err != nil { - t.Fatalf("expected no error, got %v", err) - } - - // Validate the presence of vault1 and vault2 - for _, vaultID := range []string{"vault1", "vault2"} { - providerName := "op" + strings.ToUpper(vaultID[:1]) + vaultID[1:] + "SecretsProvider" - if provider := mocks.Injector.Resolve(providerName); provider == nil { - t.Fatalf("expected %s to be registered, got error", providerName) - } else { - // Validate the provider by checking if it can be initialized - if err := provider.(secrets.SecretsProvider).Initialize(); err != nil { - t.Fatalf("expected %s to be initialized without error, got %v", providerName, err) - } - } - } - }) - - t.Run("OnePasswordSDKProviderUsedWhenTokenSet", func(t *testing.T) { - // Given a new controller - controller, mocks := setup(t) - - // And a mock config handler that returns 1Password vaults - mockConfigHandler := config.NewMockConfigHandler() - mockConfigHandler.GetFunc = func(key string) any { - if key == "contexts.mock-context.secrets.onepassword.vaults" { - return map[string]secretsConfigType.OnePasswordVault{ - "vault1": {ID: "vault1", Name: "test-vault"}, - } - } - return nil - } - mocks.Injector.Register("configHandler", mockConfigHandler) - controller.(*RealController).configHandler = mockConfigHandler - - // And OP_SERVICE_ACCOUNT_TOKEN is set - originalToken := os.Getenv("OP_SERVICE_ACCOUNT_TOKEN") - defer os.Setenv("OP_SERVICE_ACCOUNT_TOKEN", originalToken) - os.Setenv("OP_SERVICE_ACCOUNT_TOKEN", "test-token") - - // When creating the secrets provider - err := controller.CreateSecretsProviders() - - // Then there should be no error - if err != nil { - t.Fatalf("expected no error, got %v", err) - } - - // And the SDK provider should be registered - providerName := "opVault1SecretsProvider" - provider := mocks.Injector.Resolve(providerName) - if provider == nil { - t.Fatalf("expected %s to be registered, got error", providerName) - } - - // And it should be an SDK provider - if _, ok := provider.(*secrets.OnePasswordSDKSecretsProvider); !ok { - t.Fatalf("expected provider to be *secrets.OnePasswordSDKSecretsProvider, got %T", provider) - } - }) -} - -func TestRealController_CreateProjectComponents(t *testing.T) { - setup := func(t *testing.T) (Controller, *Mocks) { - t.Helper() - mocks := setupMocks(t) - controller := NewRealController(mocks.Injector) - err := controller.Initialize() - if err != nil { - t.Fatalf("Failed to initialize controller: %v", err) - } - return controller, mocks - } - - t.Run("Success", func(t *testing.T) { - // Given a new controller - controller, mocks := setup(t) - - // And common components are created - controller.CreateCommonComponents() - - // When creating project components - err := controller.CreateProjectComponents() - - // Then there should be no error - if err != nil { - t.Fatalf("expected no error, got %v", err) - } - - // And the components should be registered in the injector - if mocks.Injector.Resolve("gitGenerator") == nil { - t.Fatalf("expected gitGenerator to be registered, got error") - } - if mocks.Injector.Resolve("blueprintHandler") == nil { - t.Fatalf("expected blueprintHandler to be registered, got error") - } - if mocks.Injector.Resolve("terraformGenerator") == nil { - t.Fatalf("expected terraformGenerator to be registered, got error") - } - }) - - t.Run("DefaultToolsManagerCreation", func(t *testing.T) { - // Given a new controller - controller, mocks := setup(t) - - // And a mock config handler that returns empty tools manager - mockConfigHandler := config.NewMockConfigHandler() - mockConfigHandler.GetStringFunc = func(key string, defaultValue ...string) string { - if key == "toolsManager" { - return "" - } - return "" - } - mocks.Injector.Register("configHandler", mockConfigHandler) - controller.(*RealController).configHandler = mockConfigHandler - - // When creating project components - err := controller.CreateProjectComponents() - - // Then there should be no error - if err != nil { - t.Fatalf("expected no error, got %v", err) - } - - // And the default tools manager should be registered - if mocks.Injector.Resolve("toolsManager") == nil { - t.Fatalf("expected default toolsManager to be registered, got error") - } - }) - - t.Run("ToolsManagerCreation", func(t *testing.T) { - // Given a new controller - controller, mocks := setup(t) - - // And common components are created - controller.CreateCommonComponents() - - // And the configuration is set for Tools Manager to be enabled - controller.(*RealController).configHandler.SetContext("test") - controller.(*RealController).configHandler.SetContextValue("toolsManager.enabled", true) - - // When creating project components - err := controller.CreateProjectComponents() - - // Then there should be no error - if err != nil { - t.Fatalf("expected no error, got %v", err) - } - - // And the tools manager should be registered - if mocks.Injector.Resolve("toolsManager") == nil { - t.Fatalf("expected toolsManager to be registered, got error") - } - }) -} - -func TestRealController_CreateEnvComponents(t *testing.T) { - setup := func(t *testing.T) (Controller, *Mocks) { - t.Helper() - mocks := setupMocks(t) - controller := NewRealController(mocks.Injector) - err := controller.Initialize() - if err != nil { - t.Fatalf("Failed to initialize controller: %v", err) - } - return controller, mocks - } - - t.Run("Success", func(t *testing.T) { - // Given a new controller - controller, _ := setup(t) - - // And common components are created - controller.CreateCommonComponents() - - // And the configuration is set for AWS and Docker to be enabled - controller.(*RealController).configHandler.SetContext("test") - controller.(*RealController).configHandler.SetContextValue("aws.enabled", true) - controller.(*RealController).configHandler.SetContextValue("docker.enabled", true) - - // When creating environment components - err := controller.CreateEnvComponents() - - // Then no error should occur - if err != nil { - t.Fatalf("expected no error, got %v", err) - } - }) -} - -func TestRealController_CreateServiceComponents(t *testing.T) { - setup := func(t *testing.T) (Controller, *Mocks) { - t.Helper() - mocks := setupMocks(t) - controller := NewRealController(mocks.Injector) - err := controller.Initialize() - if err != nil { - t.Fatalf("Failed to initialize controller: %v", err) - } - return controller, mocks - } - - t.Run("Success", func(t *testing.T) { - // Given a new controller - controller, mocks := setup(t) - - // And common components are created - controller.CreateCommonComponents() - - // And the configuration is set for various services to be enabled - controller.(*RealController).configHandler.SetContext("test") - controller.(*RealController).configHandler.SetContextValue("docker.enabled", true) - controller.(*RealController).configHandler.SetContextValue("dns.enabled", true) - controller.(*RealController).configHandler.SetContextValue("git.livereload.enabled", true) - controller.(*RealController).configHandler.SetContextValue("aws.localstack.enabled", true) - controller.(*RealController).configHandler.SetContextValue("cluster.enabled", true) - controller.(*RealController).configHandler.SetContextValue("cluster.driver", "talos") - controller.(*RealController).configHandler.SetContextValue("cluster.controlplanes.count", 2) - controller.(*RealController).configHandler.SetContextValue("cluster.workers.count", 3) - - // When creating service components - err := controller.CreateServiceComponents() - - // Then no error should occur - if err != nil { - t.Fatalf("expected no error, got %v", err) - } - - // And the DNS service should be registered - if mocks.Injector.Resolve("dnsService") == nil { - t.Fatalf("expected dnsService to be registered, got error") - } - - // And the Git livereload service should be registered - if mocks.Injector.Resolve("gitLivereloadService") == nil { - t.Fatalf("expected gitLivereloadService to be registered, got error") - } - - // And the Localstack service should be registered - if mocks.Injector.Resolve("localstackService") == nil { - t.Fatalf("expected localstackService to be registered, got error") - } - - // And the registry services should be registered if Docker registries are configured - contextConfig := controller.(*RealController).configHandler.GetConfig() - if contextConfig.Docker != nil && contextConfig.Docker.Registries != nil { - for key := range contextConfig.Docker.Registries { - serviceName := fmt.Sprintf("registryService.%s", key) - if mocks.Injector.Resolve(serviceName) == nil { - t.Fatalf("expected %s to be registered, got error", serviceName) - } - } - } - - // And the Talos cluster services should be registered - controlPlaneCount := controller.(*RealController).configHandler.GetInt("cluster.controlplanes.count") - workerCount := controller.(*RealController).configHandler.GetInt("cluster.workers.count") - - for i := 1; i <= controlPlaneCount; i++ { - serviceName := fmt.Sprintf("clusterNode.controlplane-%d", i) - if mocks.Injector.Resolve(serviceName) == nil { - t.Fatalf("expected %s to be registered, got error", serviceName) - } - } - for i := 1; i <= workerCount; i++ { - serviceName := fmt.Sprintf("clusterNode.worker-%d", i) - if mocks.Injector.Resolve(serviceName) == nil { - t.Fatalf("expected %s to be registered, got error", serviceName) - } - } - }) - - t.Run("DockerDisabled", func(t *testing.T) { - // Given a new controller - controller, _ := setup(t) - - // And common components are created - controller.CreateCommonComponents() - - // And Docker is disabled in the configuration - controller.(*RealController).configHandler.SetContext("test") - controller.(*RealController).configHandler.SetContextValue("docker.enabled", false) - - // When creating service components - err := controller.CreateServiceComponents() - - // Then no error should occur - if err != nil { - t.Fatalf("expected no error, got %v", err) - } - }) -} - -func TestRealController_CreateVirtualizationComponents(t *testing.T) { - setup := func(t *testing.T) (Controller, *Mocks) { - t.Helper() - mocks := setupMocks(t) - controller := NewRealController(mocks.Injector) - err := controller.Initialize() - if err != nil { - t.Fatalf("Failed to initialize controller: %v", err) - } - return controller, mocks - } - - t.Run("SuccessWithColima", func(t *testing.T) { - // Given a new controller - controller, mocks := setup(t) - - // And common components are created - controller.CreateCommonComponents() - - // And the configuration is set for VM driver to be colima and Docker to be enabled - controller.(*RealController).configHandler.SetContext("test") - controller.(*RealController).configHandler.SetContextValue("vm.driver", "colima") - controller.(*RealController).configHandler.SetContextValue("docker.enabled", true) - - // When creating virtualization components - err := controller.CreateVirtualizationComponents() - - // Then no error should occur - if err != nil { - t.Fatalf("expected no error, got %v", err) - } - - // And the network interface provider should be registered - if mocks.Injector.Resolve("networkInterfaceProvider") == nil { - t.Fatalf("expected networkInterfaceProvider to be registered, got error") - } - - // And the SSH client should be registered - if mocks.Injector.Resolve("sshClient") == nil { - t.Fatalf("expected sshClient to be registered, got error") - } - - // And the secure shell should be registered - if mocks.Injector.Resolve("secureShell") == nil { - t.Fatalf("expected secureShell to be registered, got error") - } - - // And the virtual machine should be registered - if mocks.Injector.Resolve("virtualMachine") == nil { - t.Fatalf("expected virtualMachine to be registered, got error") - } - - // And the network manager should be registered - if mocks.Injector.Resolve("networkManager") == nil { - t.Fatalf("expected networkManager to be registered, got error") - } - - // And the container runtime should be registered - if mocks.Injector.Resolve("containerRuntime") == nil { - t.Fatalf("expected containerRuntime to be registered, got error") - } - }) - - t.Run("SuccessWithBaseNetworkManager", func(t *testing.T) { - // Given a new controller - controller, mocks := setup(t) - - // And common components are created - controller.CreateCommonComponents() - - // And the configuration is set for VM driver to be something other than colima - controller.(*RealController).configHandler.SetContext("test") - controller.(*RealController).configHandler.SetContextValue("vm.driver", "other") - controller.(*RealController).configHandler.SetContextValue("docker.enabled", true) - - // When creating virtualization components - err := controller.CreateVirtualizationComponents() - - // Then no error should occur - if err != nil { - t.Fatalf("expected no error, got %v", err) - } - - // And the base network manager should be registered - if mocks.Injector.Resolve("networkManager") == nil { - t.Fatalf("expected networkManager to be registered, got error") - } - - // And the container runtime should be registered - if mocks.Injector.Resolve("containerRuntime") == nil { - t.Fatalf("expected containerRuntime to be registered, got error") - } - }) - - t.Run("ErrorCreatingNetworkManager", func(t *testing.T) { - // Given a new controller setup - _, mocks := setup(t) - - // Register a nil network manager - mocks.Injector.Register("networkManager", nil) - - // Verify that the network manager is registered as nil - if mocks.Injector.Resolve("networkManager") != nil { - t.Fatalf("expected networkManager to be nil, got non-nil") - } - }) -} - -func TestRealController_CreateStackComponents(t *testing.T) { - setup := func(t *testing.T) (Controller, *Mocks) { - t.Helper() - mocks := setupMocks(t) - controller := NewRealController(mocks.Injector) - err := controller.Initialize() - if err != nil { - t.Fatalf("Failed to initialize controller: %v", err) - } - return controller, mocks - } - - t.Run("Success", func(t *testing.T) { - // Given a new controller - controller, mocks := setup(t) - - // When creating stack components - err := controller.CreateStackComponents() - - // Then there should be no error - if err != nil { - t.Fatalf("expected no error, got %v", err) - } - - // And the stack should be registered in the injector - if mocks.Injector.Resolve("stack") == nil { - t.Fatalf("expected stack to be registered, got error") - } - }) -} diff --git a/pkg/network/mock_network.go b/pkg/network/mock_network.go index be408937d..406b5be7f 100644 --- a/pkg/network/mock_network.go +++ b/pkg/network/mock_network.go @@ -83,6 +83,22 @@ type MockNetworkInterfaceProvider struct { InterfaceAddrsFunc func(iface net.Interface) ([]net.Addr, error) } +// ============================================================================= +// Constructor +// ============================================================================= + +// NewMockNetworkInterfaceProvider creates a new instance of MockNetworkInterfaceProvider with default implementations. +func NewMockNetworkInterfaceProvider() *MockNetworkInterfaceProvider { + return &MockNetworkInterfaceProvider{ + InterfacesFunc: func() ([]net.Interface, error) { + return []net.Interface{}, nil + }, + InterfaceAddrsFunc: func(iface net.Interface) ([]net.Addr, error) { + return []net.Addr{}, nil + }, + } +} + // ============================================================================= // Public Methods // ============================================================================= diff --git a/pkg/network/shims.go b/pkg/network/shims.go index 3cf704e13..4c9cf4fbd 100644 --- a/pkg/network/shims.go +++ b/pkg/network/shims.go @@ -34,6 +34,15 @@ type NetworkInterfaceProvider interface { // RealNetworkInterfaceProvider is the real implementation of NetworkInterfaceProvider type RealNetworkInterfaceProvider struct{} +// ============================================================================= +// Constructors +// ============================================================================= + +// NewNetworkInterfaceProvider creates a new real implementation of NetworkInterfaceProvider +func NewNetworkInterfaceProvider() NetworkInterfaceProvider { + return &RealNetworkInterfaceProvider{} +} + // ============================================================================= // Shims // =============================================================================