From e59d76098c716b313e0a1eb35a7ec86463cc0973 Mon Sep 17 00:00:00 2001 From: Ryan VanGundy <85766511+rmvangun@users.noreply.github.com> Date: Thu, 13 Nov 2025 20:26:05 -0500 Subject: [PATCH] refactor(cmd): Factor out runtime and pipeline from down We no longer need pipeline or runtime constructs in the down command. These have been factored out to use newer components. Signed-off-by: Ryan VanGundy <85766511+rmvangun@users.noreply.github.com> --- cmd/down.go | 85 ++++----- cmd/down_test.go | 339 +++++++++++++++++++-------------- pkg/project/project.go | 33 ++++ pkg/project/project_test.go | 51 +++++ pkg/workstation/workstation.go | 6 + 5 files changed, 329 insertions(+), 185 deletions(-) diff --git a/cmd/down.go b/cmd/down.go index c35600ddd..0d1522b2f 100644 --- a/cmd/down.go +++ b/cmd/down.go @@ -1,13 +1,12 @@ package cmd import ( - "context" "fmt" + "os" "github.com/spf13/cobra" "github.com/windsorcli/cli/pkg/di" - "github.com/windsorcli/cli/pkg/pipelines" - "github.com/windsorcli/cli/pkg/runtime" + "github.com/windsorcli/cli/pkg/project" ) var ( @@ -23,61 +22,57 @@ var downCmd = &cobra.Command{ Long: "Tear down the Windsor environment by executing necessary shell commands.", SilenceUsage: true, RunE: func(cmd *cobra.Command, args []string) error { - // Get shared dependency injector from context injector := cmd.Context().Value(injectorKey).(di.Injector) - // First, set up environment variables using runtime - deps := &runtime.Dependencies{ - Injector: injector, - } - if err := runtime.NewRuntime(deps). - LoadShell(). - CheckTrustedDirectory(). - LoadConfig(). - LoadSecretsProviders(). - LoadEnvVars(runtime.EnvVarsOptions{ - Decrypt: true, - Verbose: verbose, - }). - ExecutePostEnvHook(verbose). - Do(); err != nil { - return fmt.Errorf("failed to set up environment: %w", err) - } - - // Then, run the init pipeline to initialize the environment - var initPipeline pipelines.Pipeline - initPipeline, err := pipelines.WithPipeline(injector, cmd.Context(), "initPipeline") + proj, err := project.NewProject(injector, "") if err != nil { - return fmt.Errorf("failed to set up init pipeline: %w", err) + return err } - if err := initPipeline.Execute(cmd.Context()); err != nil { - return fmt.Errorf("failed to initialize environment: %w", err) + + if err := proj.Context.Shell.CheckTrustedDirectory(); err != nil { + return fmt.Errorf("not in a trusted directory. If you are in a Windsor project, run 'windsor init' to approve") } - // Finally, run the down pipeline for infrastructure teardown - downPipeline, err := pipelines.WithPipeline(injector, cmd.Context(), "downPipeline") - if err != nil { - return fmt.Errorf("failed to set up down pipeline: %w", err) + if err := proj.Configure(nil); err != nil { + return err } - // Create execution context with flags - ctx := cmd.Context() - if cleanFlag { - ctx = context.WithValue(ctx, "clean", true) + if err := proj.Initialize(false); err != nil { + if !verbose { + return nil + } + return err } - if skipK8sFlag { - ctx = context.WithValue(ctx, "skipK8s", true) + + if !skipK8sFlag { + if err := proj.Composer.BlueprintHandler.Down(); err != nil { + return fmt.Errorf("error running blueprint cleanup: %w", err) + } + } else { + fmt.Fprintln(os.Stderr, "Skipping Kubernetes cleanup (--skip-k8s set)") } - if skipTerraformFlag { - ctx = context.WithValue(ctx, "skipTerraform", true) + + if !skipTerraformFlag { + blueprint := proj.Composer.BlueprintHandler.Generate() + if err := proj.Provisioner.Down(blueprint); err != nil { + return fmt.Errorf("error tearing down infrastructure: %w", err) + } + } else { + fmt.Fprintln(os.Stderr, "Skipping Terraform cleanup (--skip-tf set)") } - if skipDockerFlag { - ctx = context.WithValue(ctx, "skipDocker", true) + + if proj.Workstation != nil && !skipDockerFlag { + if err := proj.Workstation.Down(); err != nil { + return fmt.Errorf("error tearing down workstation: %w", err) + } + } else if skipDockerFlag { + fmt.Fprintln(os.Stderr, "Skipping Docker container cleanup (--skip-docker set)") } - // Execute the down pipeline - if err := downPipeline.Execute(ctx); err != nil { - return fmt.Errorf("Error executing down pipeline: %w", err) + if cleanFlag { + if err := proj.PerformCleanup(); err != nil { + return fmt.Errorf("error performing cleanup: %w", err) + } } return nil diff --git a/cmd/down_test.go b/cmd/down_test.go index 6017a55b9..fb95893eb 100644 --- a/cmd/down_test.go +++ b/cmd/down_test.go @@ -8,237 +8,296 @@ import ( "github.com/spf13/cobra" "github.com/spf13/pflag" + blueprintv1alpha1 "github.com/windsorcli/cli/api/v1alpha1" + "github.com/windsorcli/cli/pkg/composer" + "github.com/windsorcli/cli/pkg/composer/blueprint" "github.com/windsorcli/cli/pkg/context/config" - "github.com/windsorcli/cli/pkg/di" - "github.com/windsorcli/cli/pkg/pipelines" - "github.com/windsorcli/cli/pkg/context/shell" + execcontext "github.com/windsorcli/cli/pkg/context" + "github.com/windsorcli/cli/pkg/provisioner" + terraforminfra "github.com/windsorcli/cli/pkg/provisioner/terraform" + "github.com/windsorcli/cli/pkg/workstation" + "github.com/windsorcli/cli/pkg/workstation/virt" ) -// ============================================================================= -// Types -// ============================================================================= - -// DownMocks contains mock dependencies for down command tests -type DownMocks struct { - Injector di.Injector - ConfigHandler config.ConfigHandler - Shell *shell.MockShell - Shims *Shims -} - -// ============================================================================= -// Helpers -// ============================================================================= - -func setupDownMocks(t *testing.T, opts ...*SetupOptions) *DownMocks { - t.Helper() - - // Get base mocks (includes trusted directory setup) - baseMocks := setupMocks(t, opts...) - - // Note: env pipeline is no longer used - environment setup is handled by runtime - - // Register mock init pipeline in injector (needed since down runs init pipeline second) - mockInitPipeline := pipelines.NewMockBasePipeline() - mockInitPipeline.InitializeFunc = func(injector di.Injector, ctx context.Context) error { return nil } - mockInitPipeline.ExecuteFunc = func(ctx context.Context) error { return nil } - baseMocks.Injector.Register("initPipeline", mockInitPipeline) - - // Register mock down pipeline in injector - mockDownPipeline := pipelines.NewMockBasePipeline() - mockDownPipeline.InitializeFunc = func(injector di.Injector, ctx context.Context) error { return nil } - mockDownPipeline.ExecuteFunc = func(ctx context.Context) error { return nil } - baseMocks.Injector.Register("downPipeline", mockDownPipeline) - - return &DownMocks{ - Injector: baseMocks.Injector, - ConfigHandler: baseMocks.ConfigHandler, - Shell: baseMocks.Shell, - Shims: baseMocks.Shims, - } -} - -// ============================================================================= -// Test Cases -// ============================================================================= - func TestDownCmd(t *testing.T) { createTestDownCmd := func() *cobra.Command { - // Create a new command with the same RunE as downCmd cmd := &cobra.Command{ Use: "down", Short: "Tear down the Windsor environment", RunE: downCmd.RunE, } - // Copy all flags from downCmd to ensure they're available downCmd.Flags().VisitAll(func(flag *pflag.Flag) { cmd.Flags().AddFlag(flag) }) - // Disable help text printing cmd.SetHelpFunc(func(*cobra.Command, []string) {}) return cmd } t.Run("Success", func(t *testing.T) { - // Given a down command with mocks - mocks := setupDownMocks(t) - cmd := createTestDownCmd() + mocks := setupMocks(t) + + mockBlueprintHandler := blueprint.NewMockBlueprintHandler(mocks.Injector) + mockBlueprintHandler.DownFunc = func() error { return nil } + mockBlueprintHandler.GenerateFunc = func() *blueprintv1alpha1.Blueprint { + return &blueprintv1alpha1.Blueprint{} + } + mocks.Injector.Register("blueprintHandler", mockBlueprintHandler) + + mockStack := &terraforminfra.MockStack{} + mockStack.InitializeFunc = func() error { return nil } + mockStack.DownFunc = func(blueprint *blueprintv1alpha1.Blueprint) error { return nil } + mocks.Injector.Register("stack", mockStack) + + mockContainerRuntime := &virt.MockVirt{} + mockContainerRuntime.InitializeFunc = func() error { return nil } + mockContainerRuntime.DownFunc = func() error { return nil } + mocks.Injector.Register("containerRuntime", mockContainerRuntime) - // When executing the command + cmd := createTestDownCmd() ctx := context.WithValue(context.Background(), injectorKey, mocks.Injector) cmd.SetContext(ctx) err := cmd.Execute() - // Then no error should be returned if err != nil { t.Errorf("Expected no error, got %v", err) } }) - // Note: ErrorSettingUpEnvironment test removed - runtime is self-healing and creates missing dependencies - - // Note: ErrorExecutingEnvPipeline test removed - env pipeline no longer used + t.Run("ErrorCheckingTrustedDirectory", func(t *testing.T) { + mocks := setupMocks(t) - t.Run("ErrorExecutingInitPipeline", func(t *testing.T) { - // Given a down command with failing init pipeline execution - mocks := setupDownMocks(t) - mockInitPipeline := pipelines.NewMockBasePipeline() - mockInitPipeline.InitializeFunc = func(injector di.Injector, ctx context.Context) error { return nil } - mockInitPipeline.ExecuteFunc = func(ctx context.Context) error { - return fmt.Errorf("init pipeline execution failed") + mockShell := mocks.Shell + mockShell.CheckTrustedDirectoryFunc = func() error { + return fmt.Errorf("not in trusted directory") } - mocks.Injector.Register("initPipeline", mockInitPipeline) - cmd := createTestDownCmd() - // When executing the command + cmd := createTestDownCmd() ctx := context.WithValue(context.Background(), injectorKey, mocks.Injector) cmd.SetContext(ctx) err := cmd.Execute() - // Then an error should be returned if err == nil { t.Error("Expected error, got nil") } - if !strings.Contains(err.Error(), "failed to initialize environment") { - t.Errorf("Expected error about init pipeline execution, got: %v", err) + if !strings.Contains(err.Error(), "not in a trusted directory") { + t.Errorf("Expected trusted directory error, got: %v", err) } }) - t.Run("ErrorExecutingDownPipeline", func(t *testing.T) { - // Given a down command with failing down pipeline execution - mocks := setupDownMocks(t) - mockDownPipeline := pipelines.NewMockBasePipeline() - mockDownPipeline.InitializeFunc = func(injector di.Injector, ctx context.Context) error { return nil } - mockDownPipeline.ExecuteFunc = func(ctx context.Context) error { - return fmt.Errorf("down pipeline execution failed") + t.Run("ErrorLoadingConfig", func(t *testing.T) { + mockConfigHandler := config.NewMockConfigHandler() + mockConfigHandler.LoadConfigFunc = func() error { + return fmt.Errorf("config load failed") } - mocks.Injector.Register("downPipeline", mockDownPipeline) - cmd := createTestDownCmd() - // When executing the command + opts := &SetupOptions{ + ConfigHandler: mockConfigHandler, + } + mocks := setupMocks(t, opts) + + cmd := createTestDownCmd() ctx := context.WithValue(context.Background(), injectorKey, mocks.Injector) cmd.SetContext(ctx) err := cmd.Execute() - // Then an error should be returned if err == nil { t.Error("Expected error, got nil") } - if !strings.Contains(err.Error(), "Error executing down pipeline") { - t.Errorf("Expected error about down pipeline execution, got: %v", err) + if !strings.Contains(err.Error(), "failed to load config") { + t.Errorf("Expected config load error, got: %v", err) } }) - t.Run("FlagsPassedToContext", func(t *testing.T) { - // Given a down command with mocks - mocks := setupDownMocks(t) - var executedContext context.Context - mockDownPipeline := pipelines.NewMockBasePipeline() - mockDownPipeline.InitializeFunc = func(injector di.Injector, ctx context.Context) error { return nil } - mockDownPipeline.ExecuteFunc = func(ctx context.Context) error { - executedContext = ctx + t.Run("SkipK8sFlag", func(t *testing.T) { + mocks := setupMocks(t) + + blueprintDownCalled := false + mockBlueprintHandler := blueprint.NewMockBlueprintHandler(mocks.Injector) + mockBlueprintHandler.DownFunc = func() error { + blueprintDownCalled = true return nil } - mocks.Injector.Register("downPipeline", mockDownPipeline) - cmd := createTestDownCmd() + mockBlueprintHandler.GenerateFunc = func() *blueprintv1alpha1.Blueprint { + return &blueprintv1alpha1.Blueprint{} + } + mocks.Injector.Register("blueprintHandler", mockBlueprintHandler) + + mockStack := &terraforminfra.MockStack{} + mockStack.InitializeFunc = func() error { return nil } + mockStack.DownFunc = func(blueprint *blueprintv1alpha1.Blueprint) error { return nil } + mocks.Injector.Register("stack", mockStack) - // When executing the command with flags - cmd.SetArgs([]string{"--clean", "--skip-k8s", "--skip-tf"}) + mockContainerRuntime := &virt.MockVirt{} + mockContainerRuntime.InitializeFunc = func() error { return nil } + mockContainerRuntime.DownFunc = func() error { return nil } + mocks.Injector.Register("containerRuntime", mockContainerRuntime) + + cmd := createTestDownCmd() + cmd.SetArgs([]string{"--skip-k8s"}) ctx := context.WithValue(context.Background(), injectorKey, mocks.Injector) cmd.SetContext(ctx) err := cmd.Execute() - // Then no error should be returned if err != nil { t.Errorf("Expected no error, got %v", err) } - // And flags should be passed to context - if executedContext == nil { - t.Fatal("Expected context to be passed to pipeline") - } - if cleanValue := executedContext.Value("clean"); cleanValue != true { - t.Errorf("Expected clean flag to be true, got %v", cleanValue) - } - if skipK8sValue := executedContext.Value("skipK8s"); skipK8sValue != true { - t.Errorf("Expected skipK8s flag to be true, got %v", skipK8sValue) - } - if skipTerraformValue := executedContext.Value("skipTerraform"); skipTerraformValue != true { - t.Errorf("Expected skipTerraform flag to be true, got %v", skipTerraformValue) + if blueprintDownCalled { + t.Error("Expected blueprint Down to be skipped, but it was called") } }) - // Note: EnvPipelineContextFlags test removed - env pipeline no longer used + t.Run("SkipTerraformFlag", func(t *testing.T) { + mocks := setupMocks(t) - t.Run("PipelineOrchestrationOrder", func(t *testing.T) { - // Given a down command with mocks - mocks := setupDownMocks(t) + mockBlueprintHandler := blueprint.NewMockBlueprintHandler(mocks.Injector) + mockBlueprintHandler.DownFunc = func() error { return nil } + mockBlueprintHandler.GenerateFunc = func() *blueprintv1alpha1.Blueprint { + return &blueprintv1alpha1.Blueprint{} + } + mocks.Injector.Register("blueprintHandler", mockBlueprintHandler) - // And pipelines that track execution order - executionOrder := []string{} + stackDownCalled := false + mockStack := &terraforminfra.MockStack{} + mockStack.InitializeFunc = func() error { return nil } + mockStack.DownFunc = func(blueprint *blueprintv1alpha1.Blueprint) error { + stackDownCalled = true + return nil + } + mocks.Injector.Register("stack", mockStack) - // Note: env pipeline no longer used - environment setup handled by runtime + mockContainerRuntime := &virt.MockVirt{} + mockContainerRuntime.InitializeFunc = func() error { return nil } + mockContainerRuntime.DownFunc = func() error { return nil } + mocks.Injector.Register("containerRuntime", mockContainerRuntime) - mockInitPipeline := pipelines.NewMockBasePipeline() - mockInitPipeline.InitializeFunc = func(injector di.Injector, ctx context.Context) error { return nil } - mockInitPipeline.ExecuteFunc = func(ctx context.Context) error { - executionOrder = append(executionOrder, "init") - return nil + cmd := createTestDownCmd() + cmd.SetArgs([]string{"--skip-tf"}) + ctx := context.WithValue(context.Background(), injectorKey, mocks.Injector) + cmd.SetContext(ctx) + err := cmd.Execute() + + if err != nil { + t.Errorf("Expected no error, got %v", err) } - mocks.Injector.Register("initPipeline", mockInitPipeline) - mockDownPipeline := pipelines.NewMockBasePipeline() - mockDownPipeline.InitializeFunc = func(injector di.Injector, ctx context.Context) error { return nil } - mockDownPipeline.ExecuteFunc = func(ctx context.Context) error { - executionOrder = append(executionOrder, "down") + if stackDownCalled { + t.Error("Expected stack Down to be skipped, but it was called") + } + }) + + t.Run("SkipDockerFlag", func(t *testing.T) { + mocks := setupMocks(t) + + mockBlueprintHandler := blueprint.NewMockBlueprintHandler(mocks.Injector) + mockBlueprintHandler.DownFunc = func() error { return nil } + mockBlueprintHandler.GenerateFunc = func() *blueprintv1alpha1.Blueprint { + return &blueprintv1alpha1.Blueprint{} + } + mocks.Injector.Register("blueprintHandler", mockBlueprintHandler) + + mockStack := &terraforminfra.MockStack{} + mockStack.InitializeFunc = func() error { return nil } + mockStack.DownFunc = func(blueprint *blueprintv1alpha1.Blueprint) error { return nil } + mocks.Injector.Register("stack", mockStack) + + containerDownCalled := false + mockContainerRuntime := &virt.MockVirt{} + mockContainerRuntime.InitializeFunc = func() error { return nil } + mockContainerRuntime.DownFunc = func() error { + containerDownCalled = true return nil } - mocks.Injector.Register("downPipeline", mockDownPipeline) + mocks.Injector.Register("containerRuntime", mockContainerRuntime) - // When executing the down command cmd := createTestDownCmd() + cmd.SetArgs([]string{"--skip-docker"}) ctx := context.WithValue(context.Background(), injectorKey, mocks.Injector) cmd.SetContext(ctx) err := cmd.Execute() - // Then no error should occur and pipelines should execute in correct order if err != nil { t.Errorf("Expected no error, got %v", err) } - expectedOrder := []string{"init", "down"} - if len(executionOrder) != len(expectedOrder) { - t.Errorf("Expected %d pipelines to execute, got %d", len(expectedOrder), len(executionOrder)) + if containerDownCalled { + t.Error("Expected container runtime Down to be skipped, but it was called") } + }) + + t.Run("DevModeWithoutWorkstation", func(t *testing.T) { + mockConfigHandler := config.NewMockConfigHandler() + mockConfigHandler.IsDevModeFunc = func(contextName string) bool { return false } - for i, expected := range expectedOrder { - if i >= len(executionOrder) || executionOrder[i] != expected { - t.Errorf("Expected pipeline %d to be %s, got %v", i, expected, executionOrder) - } + opts := &SetupOptions{ + ConfigHandler: mockConfigHandler, } + mocks := setupMocks(t, opts) + + mockBlueprintHandler := blueprint.NewMockBlueprintHandler(mocks.Injector) + mockBlueprintHandler.DownFunc = func() error { return nil } + mockBlueprintHandler.GenerateFunc = func() *blueprintv1alpha1.Blueprint { + return &blueprintv1alpha1.Blueprint{} + } + mocks.Injector.Register("blueprintHandler", mockBlueprintHandler) + + mockStack := &terraforminfra.MockStack{} + mockStack.InitializeFunc = func() error { return nil } + mockStack.DownFunc = func(blueprint *blueprintv1alpha1.Blueprint) error { return nil } + mocks.Injector.Register("stack", mockStack) + + cmd := createTestDownCmd() + ctx := context.WithValue(context.Background(), injectorKey, mocks.Injector) + cmd.SetContext(ctx) + err := cmd.Execute() + + if err != nil { + t.Errorf("Expected no error, got %v", err) + } + }) +} + +func setupDownMocksWithProject(t *testing.T) (*Mocks, *provisioner.Provisioner, *composer.Composer, *workstation.Workstation) { + t.Helper() + + mocks := setupMocks(t) + + mockBlueprintHandler := blueprint.NewMockBlueprintHandler(mocks.Injector) + mockBlueprintHandler.GenerateFunc = func() *blueprintv1alpha1.Blueprint { + return &blueprintv1alpha1.Blueprint{} + } + mocks.Injector.Register("blueprintHandler", mockBlueprintHandler) + + mockStack := &terraforminfra.MockStack{} + mockStack.InitializeFunc = func() error { return nil } + mocks.Injector.Register("stack", mockStack) + + mockContainerRuntime := &virt.MockVirt{} + mockContainerRuntime.InitializeFunc = func() error { return nil } + mocks.Injector.Register("containerRuntime", mockContainerRuntime) + + mockExecCtx := mocks.Injector.Resolve("executionContext") + if mockExecCtx == nil { + t.Fatal("executionContext not found in injector") + } + + prov := provisioner.NewProvisioner(&provisioner.ProvisionerExecutionContext{ + ExecutionContext: *mockExecCtx.(*execcontext.ExecutionContext), + }) + + comp := composer.NewComposer(&composer.ComposerExecutionContext{ + ExecutionContext: *mockExecCtx.(*execcontext.ExecutionContext), }) + ws, err := workstation.NewWorkstation(&workstation.WorkstationExecutionContext{ + ExecutionContext: *mockExecCtx.(*execcontext.ExecutionContext), + }, mocks.Injector) + if err != nil { + t.Fatalf("Failed to create workstation: %v", err) + } + + return mocks, prov, comp, ws } diff --git a/pkg/project/project.go b/pkg/project/project.go index 1741bde73..d6de26669 100644 --- a/pkg/project/project.go +++ b/pkg/project/project.go @@ -2,6 +2,7 @@ package project import ( "fmt" + "os" "path/filepath" "github.com/windsorcli/cli/pkg/composer" @@ -163,3 +164,35 @@ func (p *Project) Initialize(overwrite bool) error { return nil } + +// PerformCleanup removes context-specific artifacts including volumes, terraform modules, +// and generated configuration files. It calls the config handler's Clean method to remove +// saved state, then deletes the .volumes directory, .windsor/.tf_modules directory, +// .windsor/Corefile, and .windsor/docker-compose.yaml. Returns an error if any cleanup step fails. +func (p *Project) PerformCleanup() error { + if err := p.Context.ConfigHandler.Clean(); err != nil { + return fmt.Errorf("error cleaning up context specific artifacts: %w", err) + } + + volumesPath := filepath.Join(p.Context.ProjectRoot, ".volumes") + if err := os.RemoveAll(volumesPath); err != nil { + return fmt.Errorf("error deleting .volumes folder: %w", err) + } + + tfModulesPath := filepath.Join(p.Context.ProjectRoot, ".windsor", ".tf_modules") + if err := os.RemoveAll(tfModulesPath); err != nil { + return fmt.Errorf("error deleting .windsor/.tf_modules folder: %w", err) + } + + corefilePath := filepath.Join(p.Context.ProjectRoot, ".windsor", "Corefile") + if err := os.RemoveAll(corefilePath); err != nil { + return fmt.Errorf("error deleting .windsor/Corefile: %w", err) + } + + dockerComposePath := filepath.Join(p.Context.ProjectRoot, ".windsor", "docker-compose.yaml") + if err := os.RemoveAll(dockerComposePath); err != nil { + return fmt.Errorf("error deleting .windsor/docker-compose.yaml: %w", err) + } + + return nil +} diff --git a/pkg/project/project_test.go b/pkg/project/project_test.go index 0cf1cdc6b..afeda9f62 100644 --- a/pkg/project/project_test.go +++ b/pkg/project/project_test.go @@ -569,3 +569,54 @@ func TestProject_Initialize(t *testing.T) { }) } + +func TestProject_PerformCleanup(t *testing.T) { + t.Run("Success", func(t *testing.T) { + mocks := setupMocks(t) + proj, err := NewProject(mocks.Injector, "test-context") + if err != nil { + t.Fatalf("Failed to create project: %v", err) + } + + mockConfig := mocks.ConfigHandler.(*config.MockConfigHandler) + cleanCalled := false + mockConfig.CleanFunc = func() error { + cleanCalled = true + return nil + } + + err = proj.PerformCleanup() + + if err != nil { + t.Errorf("Expected no error, got: %v", err) + } + + if !cleanCalled { + t.Error("Expected Clean to be called") + } + }) + + t.Run("ErrorOnCleanFailure", func(t *testing.T) { + mocks := setupMocks(t) + proj, err := NewProject(mocks.Injector, "test-context") + if err != nil { + t.Fatalf("Failed to create project: %v", err) + } + + mockConfig := mocks.ConfigHandler.(*config.MockConfigHandler) + mockConfig.CleanFunc = func() error { + return fmt.Errorf("clean failed") + } + + err = proj.PerformCleanup() + + if err == nil { + t.Error("Expected error for Clean failure") + return + } + + if !strings.Contains(err.Error(), "error cleaning up context specific artifacts") { + t.Errorf("Expected specific error message, got: %v", err) + } + }) +} diff --git a/pkg/workstation/workstation.go b/pkg/workstation/workstation.go index 3f4be4215..266f67849 100644 --- a/pkg/workstation/workstation.go +++ b/pkg/workstation/workstation.go @@ -202,12 +202,18 @@ func (w *Workstation) Up() error { // step fails, it returns an error describing the issue. func (w *Workstation) Down() error { if w.ContainerRuntime != nil { + if err := w.ContainerRuntime.Initialize(); err != nil { + return fmt.Errorf("failed to initialize container runtime: %w", err) + } if err := w.ContainerRuntime.Down(); err != nil { return fmt.Errorf("Error running container runtime Down command: %w", err) } } if w.VirtualMachine != nil { + if err := w.VirtualMachine.Initialize(); err != nil { + return fmt.Errorf("failed to initialize virtual machine: %w", err) + } if err := w.VirtualMachine.Down(); err != nil { return fmt.Errorf("Error running virtual machine Down command: %w", err) }