diff --git a/cmd/up_test.go b/cmd/up_test.go index 6f370b423..8fa7c5a0e 100644 --- a/cmd/up_test.go +++ b/cmd/up_test.go @@ -11,13 +11,13 @@ import ( "github.com/spf13/pflag" blueprintv1alpha1 "github.com/windsorcli/cli/api/v1alpha1" "github.com/windsorcli/cli/pkg/composer/blueprint" + "github.com/windsorcli/cli/pkg/di" + "github.com/windsorcli/cli/pkg/provisioner/kubernetes" + terraforminfra "github.com/windsorcli/cli/pkg/provisioner/terraform" "github.com/windsorcli/cli/pkg/runtime/config" envvars "github.com/windsorcli/cli/pkg/runtime/env" "github.com/windsorcli/cli/pkg/runtime/shell" "github.com/windsorcli/cli/pkg/runtime/tools" - "github.com/windsorcli/cli/pkg/di" - "github.com/windsorcli/cli/pkg/provisioner/kubernetes" - terraforminfra "github.com/windsorcli/cli/pkg/provisioner/terraform" ) // ============================================================================= @@ -82,7 +82,6 @@ func setupUpTest(t *testing.T, opts ...*SetupOptions) *UpMocks { mockBlueprintHandler.InitializeFunc = func() error { return nil } mockBlueprintHandler.LoadBlueprintFunc = func() error { return nil } mockBlueprintHandler.WriteFunc = func(overwrite ...bool) error { return nil } - mockBlueprintHandler.LoadConfigFunc = func() error { return nil } testBlueprint := &blueprintv1alpha1.Blueprint{ Metadata: blueprintv1alpha1.Metadata{Name: "test"}, } diff --git a/pkg/composer/blueprint/blueprint_handler.go b/pkg/composer/blueprint/blueprint_handler.go index 742b23bce..8a5a9c9c7 100644 --- a/pkg/composer/blueprint/blueprint_handler.go +++ b/pkg/composer/blueprint/blueprint_handler.go @@ -34,23 +34,12 @@ import ( type BlueprintHandler interface { Initialize() error LoadBlueprint() error - LoadConfig() error - LoadData(data map[string]any, ociInfo ...*artifact.OCIArtifactInfo) error Write(overwrite ...bool) error - SetRenderedKustomizeData(data map[string]any) - GetMetadata() blueprintv1alpha1.Metadata - GetSources() []blueprintv1alpha1.Source - GetRepository() blueprintv1alpha1.Repository GetTerraformComponents() []blueprintv1alpha1.TerraformComponent - GetKustomizations() []blueprintv1alpha1.Kustomization - GetDefaultTemplateData(contextName string) (map[string][]byte, error) GetLocalTemplateData() (map[string][]byte, error) Generate() *blueprintv1alpha1.Blueprint } -//go:embed templates/default.jsonnet -var defaultJsonnetTemplate string - type BaseBlueprintHandler struct { BlueprintHandler injector di.Injector @@ -146,12 +135,12 @@ func (b *BaseBlueprintHandler) LoadBlueprint() error { for key, value := range templateData { blueprintData[key] = string(value) } - if err := b.LoadData(blueprintData, ociInfo); err != nil { + if err := b.loadData(blueprintData, ociInfo); err != nil { return fmt.Errorf("failed to load default blueprint data: %w", err) } } - sources := b.GetSources() + sources := b.getSources() if len(sources) > 0 { artifactBuilder := b.injector.Resolve("artifactBuilder") if artifactBuilder != nil { @@ -179,7 +168,7 @@ func (b *BaseBlueprintHandler) LoadBlueprint() error { blueprintPath := filepath.Join(configRoot, "blueprint.yaml") if _, err := b.shims.Stat(blueprintPath); err == nil { - if err := b.LoadConfig(); err != nil { + if err := b.loadConfig(); err != nil { return fmt.Errorf("failed to load blueprint config overrides: %w", err) } } @@ -187,54 +176,6 @@ func (b *BaseBlueprintHandler) LoadBlueprint() error { return nil } -// LoadConfig reads blueprint configuration from blueprint.yaml file. -// Returns an error if blueprint.yaml does not exist. -// Template processing is now handled by the pkg/template package. -func (b *BaseBlueprintHandler) LoadConfig() error { - configRoot, err := b.configHandler.GetConfigRoot() - if err != nil { - return fmt.Errorf("error getting config root: %w", err) - } - - yamlPath := filepath.Join(configRoot, "blueprint.yaml") - if _, err := b.shims.Stat(yamlPath); err != nil { - return fmt.Errorf("blueprint.yaml not found at %s", yamlPath) - } - - yamlData, err := b.shims.ReadFile(yamlPath) - if err != nil { - return err - } - - if err := b.processBlueprintData(yamlData, &b.blueprint); err != nil { - return err - } - - b.configLoaded = true - return nil -} - -// LoadData loads blueprint configuration from a map containing blueprint data. -// It marshals the input map to YAML, processes it as a Blueprint object, and updates the handler's blueprint state. -// The ociInfo parameter optionally provides OCI artifact source information for source resolution and tracking. -// If config is already loaded from YAML, this is a no-op to preserve resolved state. -func (b *BaseBlueprintHandler) LoadData(data map[string]any, ociInfo ...*artifact.OCIArtifactInfo) error { - if b.configLoaded { - return nil - } - - yamlData, err := b.shims.YamlMarshal(data) - if err != nil { - return fmt.Errorf("error marshalling blueprint data to yaml: %w", err) - } - - if err := b.processBlueprintData(yamlData, &b.blueprint, ociInfo...); err != nil { - return err - } - - return nil -} - // Write persists the current blueprint state to blueprint.yaml in the configuration root directory. // If overwrite is true, the file is overwritten regardless of existence. If overwrite is false or omitted, // the file is only written if it does not already exist. The method ensures the target directory exists, @@ -284,34 +225,6 @@ func (b *BaseBlueprintHandler) Write(overwrite ...bool) error { return nil } -// GetMetadata retrieves the current blueprint's metadata. -func (b *BaseBlueprintHandler) GetMetadata() blueprintv1alpha1.Metadata { - resolvedBlueprint := b.blueprint - return resolvedBlueprint.Metadata -} - -// GetRepository retrieves the current blueprint's repository configuration, ensuring -// default values are set for empty fields. -func (b *BaseBlueprintHandler) GetRepository() blueprintv1alpha1.Repository { - resolvedBlueprint := b.blueprint - repository := resolvedBlueprint.Repository - - if repository.Url == "" { - repository.Url = "" - } - if repository.Ref == (blueprintv1alpha1.Reference{}) { - repository.Ref = blueprintv1alpha1.Reference{Branch: "main"} - } - - return repository -} - -// GetSources retrieves the current blueprint's source configurations. -func (b *BaseBlueprintHandler) GetSources() []blueprintv1alpha1.Source { - resolvedBlueprint := b.blueprint - return resolvedBlueprint.Sources -} - // GetTerraformComponents retrieves the blueprint's Terraform components after resolving // their sources and paths to full URLs and filesystem paths respectively. func (b *BaseBlueprintHandler) GetTerraformComponents() []blueprintv1alpha1.TerraformComponent { @@ -323,60 +236,13 @@ func (b *BaseBlueprintHandler) GetTerraformComponents() []blueprintv1alpha1.Terr return resolvedBlueprint.TerraformComponents } -// GetKustomizations returns the current blueprint's kustomization configurations with all default values resolved. -// It copies the kustomizations from the blueprint, sets default values for Source, Path, Interval, RetryInterval, -// Timeout, Wait, Force, and Destroy fields if unset, discovers and appends patches, and sets the PostBuild configuration. -// This method ensures all kustomization fields are fully populated for downstream processing. -func (b *BaseBlueprintHandler) GetKustomizations() []blueprintv1alpha1.Kustomization { - resolvedBlueprint := b.blueprint - kustomizations := make([]blueprintv1alpha1.Kustomization, len(resolvedBlueprint.Kustomizations)) - copy(kustomizations, resolvedBlueprint.Kustomizations) - - for i := range kustomizations { - if kustomizations[i].Source == "" { - kustomizations[i].Source = b.blueprint.Metadata.Name - } - - if kustomizations[i].Path == "" { - kustomizations[i].Path = "kustomize" - } else { - kustomizations[i].Path = "kustomize/" + strings.ReplaceAll(kustomizations[i].Path, "\\", "/") - } - - if kustomizations[i].Interval == nil || kustomizations[i].Interval.Duration == 0 { - kustomizations[i].Interval = &metav1.Duration{Duration: constants.DefaultFluxKustomizationInterval} - } - if kustomizations[i].RetryInterval == nil || kustomizations[i].RetryInterval.Duration == 0 { - kustomizations[i].RetryInterval = &metav1.Duration{Duration: constants.DefaultFluxKustomizationRetryInterval} - } - if kustomizations[i].Timeout == nil || kustomizations[i].Timeout.Duration == 0 { - kustomizations[i].Timeout = &metav1.Duration{Duration: constants.DefaultFluxKustomizationTimeout} - } - if kustomizations[i].Wait == nil { - defaultWait := constants.DefaultFluxKustomizationWait - kustomizations[i].Wait = &defaultWait - } - if kustomizations[i].Force == nil { - defaultForce := constants.DefaultFluxKustomizationForce - kustomizations[i].Force = &defaultForce - } - if kustomizations[i].Destroy == nil { - defaultDestroy := true - kustomizations[i].Destroy = &defaultDestroy - } - - } - - return kustomizations -} - // Generate returns the fully processed blueprint with all defaults resolved, // paths processed, and generation logic applied - equivalent to what would be deployed. -// It applies the same processing logic as GetKustomizations() but for the entire blueprint structure. +// It applies the same processing logic as getKustomizations() but for the entire blueprint structure. func (b *BaseBlueprintHandler) Generate() *blueprintv1alpha1.Blueprint { generated := b.blueprint.DeepCopy() - // Process kustomizations with the same logic as GetKustomizations() + // Process kustomizations with the same logic as getKustomizations() for i := range generated.Kustomizations { if generated.Kustomizations[i].Source == "" { generated.Kustomizations[i].Source = generated.Metadata.Name @@ -418,21 +284,6 @@ func (b *BaseBlueprintHandler) Generate() *blueprintv1alpha1.Blueprint { return generated } -// SetRenderedKustomizeData stores rendered kustomize data for use during install. -// This includes values and patches from template processing that should be composed with user-defined files. -func (b *BaseBlueprintHandler) SetRenderedKustomizeData(data map[string]any) { - b.kustomizeData = data -} - -// GetDefaultTemplateData generates default template data based on the provider configuration. -// It uses the embedded default template to create a map of template files that can be -// used by the init pipeline for generating context-specific configurations. -func (b *BaseBlueprintHandler) GetDefaultTemplateData(contextName string) (map[string][]byte, error) { - return map[string][]byte{ - "blueprint.jsonnet": []byte(defaultJsonnetTemplate), - }, nil -} - // GetLocalTemplateData returns template files from contexts/_template, merging values.yaml from // both _template and context dirs. All .jsonnet files are collected recursively with relative // paths preserved. If OCI artifact values exist, they are merged with local values, with local @@ -507,6 +358,129 @@ func (b *BaseBlueprintHandler) GetLocalTemplateData() (map[string][]byte, error) // Private Methods // ============================================================================= +// loadConfig reads blueprint configuration from blueprint.yaml file. +// Returns an error if blueprint.yaml does not exist. +// Template processing is now handled by the pkg/template package. +func (b *BaseBlueprintHandler) loadConfig() error { + configRoot, err := b.configHandler.GetConfigRoot() + if err != nil { + return fmt.Errorf("error getting config root: %w", err) + } + + yamlPath := filepath.Join(configRoot, "blueprint.yaml") + if _, err := b.shims.Stat(yamlPath); err != nil { + return fmt.Errorf("blueprint.yaml not found at %s", yamlPath) + } + + yamlData, err := b.shims.ReadFile(yamlPath) + if err != nil { + return err + } + + if err := b.processBlueprintData(yamlData, &b.blueprint); err != nil { + return err + } + + b.configLoaded = true + return nil +} + +// loadData loads blueprint configuration from a map containing blueprint data. +// It marshals the input map to YAML, processes it as a Blueprint object, and updates the handler's blueprint state. +// The ociInfo parameter optionally provides OCI artifact source information for source resolution and tracking. +// If config is already loaded from YAML, this is a no-op to preserve resolved state. +func (b *BaseBlueprintHandler) loadData(data map[string]any, ociInfo ...*artifact.OCIArtifactInfo) error { + if b.configLoaded { + return nil + } + + yamlData, err := b.shims.YamlMarshal(data) + if err != nil { + return fmt.Errorf("error marshalling blueprint data to yaml: %w", err) + } + + if err := b.processBlueprintData(yamlData, &b.blueprint, ociInfo...); err != nil { + return err + } + + return nil +} + +// getMetadata retrieves the current blueprint's metadata. +func (b *BaseBlueprintHandler) getMetadata() blueprintv1alpha1.Metadata { + resolvedBlueprint := b.blueprint + return resolvedBlueprint.Metadata +} + +// getRepository retrieves the current blueprint's repository configuration, ensuring +// default values are set for empty fields. +func (b *BaseBlueprintHandler) getRepository() blueprintv1alpha1.Repository { + resolvedBlueprint := b.blueprint + repository := resolvedBlueprint.Repository + + if repository.Url == "" { + repository.Url = "" + } + if repository.Ref == (blueprintv1alpha1.Reference{}) { + repository.Ref = blueprintv1alpha1.Reference{Branch: "main"} + } + + return repository +} + +// getSources retrieves the current blueprint's source configurations. +func (b *BaseBlueprintHandler) getSources() []blueprintv1alpha1.Source { + resolvedBlueprint := b.blueprint + return resolvedBlueprint.Sources +} + +// getKustomizations returns the current blueprint's kustomization configurations with all default values resolved. +// It copies the kustomizations from the blueprint, sets default values for Source, Path, Interval, RetryInterval, +// Timeout, Wait, Force, and Destroy fields if unset, discovers and appends patches, and sets the PostBuild configuration. +// This method ensures all kustomization fields are fully populated for downstream processing. +func (b *BaseBlueprintHandler) getKustomizations() []blueprintv1alpha1.Kustomization { + resolvedBlueprint := b.blueprint + kustomizations := make([]blueprintv1alpha1.Kustomization, len(resolvedBlueprint.Kustomizations)) + copy(kustomizations, resolvedBlueprint.Kustomizations) + + for i := range kustomizations { + if kustomizations[i].Source == "" { + kustomizations[i].Source = b.blueprint.Metadata.Name + } + + if kustomizations[i].Path == "" { + kustomizations[i].Path = "kustomize" + } else { + kustomizations[i].Path = "kustomize/" + strings.ReplaceAll(kustomizations[i].Path, "\\", "/") + } + + if kustomizations[i].Interval == nil || kustomizations[i].Interval.Duration == 0 { + kustomizations[i].Interval = &metav1.Duration{Duration: constants.DefaultFluxKustomizationInterval} + } + if kustomizations[i].RetryInterval == nil || kustomizations[i].RetryInterval.Duration == 0 { + kustomizations[i].RetryInterval = &metav1.Duration{Duration: constants.DefaultFluxKustomizationRetryInterval} + } + if kustomizations[i].Timeout == nil || kustomizations[i].Timeout.Duration == 0 { + kustomizations[i].Timeout = &metav1.Duration{Duration: constants.DefaultFluxKustomizationTimeout} + } + if kustomizations[i].Wait == nil { + defaultWait := constants.DefaultFluxKustomizationWait + kustomizations[i].Wait = &defaultWait + } + if kustomizations[i].Force == nil { + defaultForce := constants.DefaultFluxKustomizationForce + kustomizations[i].Force = &defaultForce + } + if kustomizations[i].Destroy == nil { + defaultDestroy := true + kustomizations[i].Destroy = &defaultDestroy + } + + } + + return kustomizations +} + // walkAndCollectTemplates traverses template directories to gather .jsonnet files. // It updates the provided templateData map with the relative paths and content of // the .jsonnet files found. The function handles directory recursion and file reading diff --git a/pkg/composer/blueprint/blueprint_handler_helper_test.go b/pkg/composer/blueprint/blueprint_handler_helper_test.go index f322cb7e4..06a877f17 100644 --- a/pkg/composer/blueprint/blueprint_handler_helper_test.go +++ b/pkg/composer/blueprint/blueprint_handler_helper_test.go @@ -13,7 +13,7 @@ import ( // Test Helper Functions // ============================================================================= -func TestBaseBlueprintHandler_GetKustomizations(t *testing.T) { +func TestBaseBlueprintHandler_getKustomizations(t *testing.T) { t.Run("NoKustomizations", func(t *testing.T) { // Given a blueprint handler with no kustomizations handler := &BaseBlueprintHandler{ @@ -26,7 +26,7 @@ func TestBaseBlueprintHandler_GetKustomizations(t *testing.T) { } // When getting kustomizations - result := handler.GetKustomizations() + result := handler.getKustomizations() // Then it should return empty slice if len(result) != 0 { @@ -53,7 +53,7 @@ func TestBaseBlueprintHandler_GetKustomizations(t *testing.T) { } // When getting kustomizations - result := handler.GetKustomizations() + result := handler.getKustomizations() // Then it should return the kustomization with default values if len(result) != 1 { @@ -98,7 +98,7 @@ func TestBaseBlueprintHandler_GetKustomizations(t *testing.T) { } // When getting kustomizations - result := handler.GetKustomizations() + result := handler.getKustomizations() // Then it should return the kustomization with existing patches preserved if len(result) != 1 { @@ -164,7 +164,7 @@ data: handler.projectRoot = tempDir // When getting kustomizations - result := handler.GetKustomizations() + result := handler.getKustomizations() // Then it should return the kustomization with no patches (auto-discovery disabled) if len(result) != 1 { @@ -225,7 +225,7 @@ data: handler.projectRoot = tempDir // When getting kustomizations - result := handler.GetKustomizations() + result := handler.getKustomizations() // Then it should return the kustomization with only existing patches (auto-discovery disabled) if len(result) != 1 { @@ -282,7 +282,7 @@ data: handler.projectRoot = tempDir // When getting kustomizations - result := handler.GetKustomizations() + result := handler.getKustomizations() // Then it should return the kustomization without patches (auto-discovery disabled) if len(result) != 1 { @@ -342,7 +342,7 @@ data: handler.projectRoot = tempDir // When getting kustomizations - result := handler.GetKustomizations() + result := handler.getKustomizations() // Then it should return both kustomizations with appropriate patches if len(result) != 2 { diff --git a/pkg/composer/blueprint/blueprint_handler_private_test.go b/pkg/composer/blueprint/blueprint_handler_private_test.go index 51d49cd7c..b58f6afa7 100644 --- a/pkg/composer/blueprint/blueprint_handler_private_test.go +++ b/pkg/composer/blueprint/blueprint_handler_private_test.go @@ -2,9 +2,11 @@ package blueprint import ( "fmt" + "os" "strings" "testing" + blueprintv1alpha1 "github.com/windsorcli/cli/api/v1alpha1" "github.com/windsorcli/cli/pkg/di" "github.com/windsorcli/cli/pkg/runtime/config" "github.com/windsorcli/cli/pkg/runtime/shell" @@ -2212,3 +2214,484 @@ func TestBaseBlueprintHandler_getDevelopmentRepositoryURL(t *testing.T) { } }) } + +func TestBlueprintHandler_getSources(t *testing.T) { + setup := func(t *testing.T) (*BaseBlueprintHandler, *Mocks) { + t.Helper() + mocks := setupMocks(t) + handler := NewBlueprintHandler(mocks.Injector) + handler.shims = mocks.Shims + handler.blueprint = blueprintv1alpha1.Blueprint{ + Sources: []blueprintv1alpha1.Source{}, + } + err := handler.Initialize() + if err != nil { + t.Fatalf("Failed to initialize handler: %v", err) + } + return handler, mocks + } + + t.Run("ReturnsExpectedSources", func(t *testing.T) { + // Given a blueprint handler with a set of sources + handler, _ := setup(t) + expectedSources := []blueprintv1alpha1.Source{ + { + Name: "source1", + Url: "git::https://example.com/source1.git", + Ref: blueprintv1alpha1.Reference{Branch: "main"}, + PathPrefix: "/source1", + }, + { + Name: "source2", + Url: "git::https://example.com/source2.git", + Ref: blueprintv1alpha1.Reference{Branch: "develop"}, + PathPrefix: "/source2", + }, + } + handler.blueprint.Sources = expectedSources + + // When getting sources + sources := handler.getSources() + + // Then the returned sources should match the expected sources + if len(sources) != len(expectedSources) { + t.Fatalf("Expected %d sources, got %d", len(expectedSources), len(sources)) + } + for i := range expectedSources { + if sources[i] != expectedSources[i] { + t.Errorf("Source[%d] = %+v, want %+v", i, sources[i], expectedSources[i]) + } + } + }) +} + +func TestBlueprintHandler_getRepository(t *testing.T) { + setup := func(t *testing.T) (*BaseBlueprintHandler, *Mocks) { + t.Helper() + mocks := setupMocks(t) + handler := NewBlueprintHandler(mocks.Injector) + handler.shims = mocks.Shims + err := handler.Initialize() + if err != nil { + t.Fatalf("Failed to initialize handler: %v", err) + } + return handler, mocks + } + + t.Run("ReturnsExpectedRepository", func(t *testing.T) { + // Given a blueprint handler with a set repository + handler, _ := setup(t) + expectedRepo := blueprintv1alpha1.Repository{ + Url: "git::https://example.com/repo.git", + Ref: blueprintv1alpha1.Reference{Branch: "main"}, + } + handler.blueprint.Repository = expectedRepo + + // When getting the repository + repo := handler.getRepository() + + // Then the expected repository should be returned + if repo != expectedRepo { + t.Errorf("Expected repository %+v, got %+v", expectedRepo, repo) + } + }) + + t.Run("ReturnsDefaultValues", func(t *testing.T) { + // Given a blueprint handler with an empty repository + handler, _ := setup(t) + handler.blueprint.Repository = blueprintv1alpha1.Repository{} + + // When getting the repository + repo := handler.getRepository() + + // Then default values should be set + expectedRepo := blueprintv1alpha1.Repository{ + Url: "", + Ref: blueprintv1alpha1.Reference{Branch: "main"}, + } + if repo != expectedRepo { + t.Errorf("Expected repository %+v, got %+v", expectedRepo, repo) + } + }) +} + +func TestBlueprintHandler_loadConfig(t *testing.T) { + setup := func(t *testing.T) (*BaseBlueprintHandler, *Mocks) { + t.Helper() + mocks := setupMocks(t) + handler := NewBlueprintHandler(mocks.Injector) + handler.shims = mocks.Shims + err := handler.Initialize() + if err != nil { + t.Fatalf("Failed to initialize handler: %v", err) + } + return handler, mocks + } + + t.Run("Success", func(t *testing.T) { + // Given a blueprint handler + handler, _ := setup(t) + + // When loading the config + err := handler.loadConfig() + + // Then no error should be returned + if err != nil { + t.Errorf("Expected no error, got %v", err) + } + + // And the metadata should be correctly loaded + metadata := handler.getMetadata() + if metadata.Name != "test-blueprint" { + t.Errorf("Expected name to be test-blueprint, got %s", metadata.Name) + } + }) + + t.Run("CustomPathOverride", func(t *testing.T) { + // Given a blueprint handler + handler, _ := setup(t) + + // And a mock file system that tracks checked paths + var checkedPaths []string + handler.shims.Stat = func(name string) (os.FileInfo, error) { + if strings.HasSuffix(name, ".jsonnet") || strings.HasSuffix(name, ".yaml") { + return nil, nil + } + return nil, os.ErrNotExist + } + handler.shims.ReadFile = func(name string) ([]byte, error) { + checkedPaths = append(checkedPaths, name) + if strings.HasSuffix(name, ".jsonnet") { + return []byte(safeBlueprintJsonnet), nil + } + if strings.HasSuffix(name, ".yaml") { + return []byte(safeBlueprintYAML), nil + } + return nil, os.ErrNotExist + } + + // When loading config + err := handler.loadConfig() + + // Then no error should be returned + if err != nil { + t.Errorf("Expected no error, got %v", err) + } + + // And only yaml path should be checked since it exists + expectedPaths := []string{ + "blueprint.yaml", + } + for _, expected := range expectedPaths { + found := false + for _, checked := range checkedPaths { + if strings.HasSuffix(checked, expected) { + found = true + break + } + } + if !found { + t.Errorf("Expected path %s to be checked, but it wasn't. Checked paths: %v", expected, checkedPaths) + } + } + }) + + t.Run("DefaultBlueprint", func(t *testing.T) { + // Given a blueprint handler + handler, _ := setup(t) + + // And a mock file system that returns no existing files + handler.shims.Stat = func(name string) (os.FileInfo, error) { + return nil, os.ErrNotExist + } + + handler.shims.ReadFile = func(name string) ([]byte, error) { + return nil, os.ErrNotExist + } + + // And a local context + originalContext := os.Getenv("WINDSOR_CONTEXT") + os.Setenv("WINDSOR_CONTEXT", "local") + defer func() { os.Setenv("WINDSOR_CONTEXT", originalContext) }() + + // When loading the config + err := handler.loadConfig() + + // Then an error should be returned since blueprint.yaml doesn't exist + if err == nil { + t.Errorf("Expected error when blueprint.yaml doesn't exist, got nil") + } + + // And the error should indicate blueprint.yaml not found + if !strings.Contains(err.Error(), "blueprint.yaml not found") { + t.Errorf("Expected error about blueprint.yaml not found, got: %v", err) + } + }) + + t.Run("ErrorUnmarshallingLocalJsonnet", func(t *testing.T) { + // Given a blueprint handler with local context + handler, mocks := setup(t) + mocks.ConfigHandler.SetContext("local") + + // And a mock yaml unmarshaller that returns an error + handler.shims.YamlUnmarshal = func(data []byte, obj any) error { + return fmt.Errorf("simulated unmarshalling error") + } + + // When loading the config + err := handler.loadConfig() + + // Then an error should be returned + if err == nil { + t.Errorf("Expected loadConfig to fail due to unmarshalling error, but it succeeded") + } + }) + + t.Run("ErrorGettingConfigRoot", func(t *testing.T) { + // Given a mock config handler that returns an error + mockConfigHandler := config.NewMockConfigHandler() + mockConfigHandler.GetConfigRootFunc = func() (string, error) { + return "", fmt.Errorf("error getting config root") + } + opts := &SetupOptions{ + ConfigHandler: mockConfigHandler, + } + mocks := setupMocks(t, opts) + + // And a blueprint handler using that config handler + handler := NewBlueprintHandler(mocks.Injector) + handler.shims = mocks.Shims + if err := handler.Initialize(); err != nil { + t.Fatalf("Failed to initialize handler: %v", err) + } + + // When loading the config + err := handler.loadConfig() + + // Then an error should be returned + if err == nil || !strings.Contains(err.Error(), "error getting config root") { + t.Errorf("Expected error containing 'error getting config root', got: %v", err) + } + }) + + t.Run("ErrorReadingYamlFile", func(t *testing.T) { + // Given a blueprint handler + handler, _ := setup(t) + + // And a mock file system that finds yaml file but fails to read it + handler.shims.Stat = func(name string) (os.FileInfo, error) { + if strings.HasSuffix(name, "blueprint.yaml") { + return nil, nil // File exists + } + return nil, os.ErrNotExist + } + handler.shims.ReadFile = func(name string) ([]byte, error) { + if strings.HasSuffix(name, "blueprint.yaml") { + return nil, fmt.Errorf("error reading yaml file") + } + return nil, os.ErrNotExist + } + + // When loading the config + err := handler.loadConfig() + + // Then an error should be returned + if err == nil || !strings.Contains(err.Error(), "error reading yaml file") { + t.Errorf("Expected error containing 'error reading yaml file', got: %v", err) + } + }) + + t.Run("ErrorLoadingYamlFile", func(t *testing.T) { + // Given a blueprint handler + handler, _ := setup(t) + + // And a mock file system that returns an error for yaml files + handler.shims.Stat = func(name string) (os.FileInfo, error) { + if strings.HasSuffix(name, ".yaml") { + return nil, nil + } + return nil, os.ErrNotExist + } + handler.shims.ReadFile = func(name string) ([]byte, error) { + if strings.HasSuffix(name, ".yaml") { + return nil, fmt.Errorf("error reading yaml file") + } + return nil, os.ErrNotExist + } + + // When loading the config + err := handler.loadConfig() + + // Then an error should be returned + if err == nil || !strings.Contains(err.Error(), "error reading yaml file") { + t.Errorf("Expected error containing 'error reading yaml file', got: %v", err) + } + }) + + t.Run("ErrorUnmarshallingYamlBlueprint", func(t *testing.T) { + // Given a blueprint handler + handler, _ := setup(t) + + // And a mock file system with a yaml file + handler.shims.Stat = func(name string) (os.FileInfo, error) { + if strings.HasSuffix(name, "blueprint.yaml") { + return nil, nil + } + return nil, os.ErrNotExist + } + + handler.shims.ReadFile = func(name string) ([]byte, error) { + if strings.HasSuffix(name, "blueprint.yaml") { + return []byte("invalid: yaml: content"), nil + } + return nil, os.ErrNotExist + } + + // And a mock yaml unmarshaller that returns an error + handler.shims.YamlUnmarshal = func(data []byte, obj any) error { + return fmt.Errorf("error unmarshalling blueprint data") + } + + // When loading the config + err := handler.loadConfig() + + // Then an error should be returned + if err == nil || !strings.Contains(err.Error(), "error unmarshalling blueprint data") { + t.Errorf("Expected error containing 'error unmarshalling blueprint data', got: %v", err) + } + }) + + t.Run("EmptyEvaluatedJsonnet", func(t *testing.T) { + // Given a blueprint handler with local context + handler, mocks := setup(t) + mocks.ConfigHandler.SetContext("local") + + // And a mock jsonnet VM that returns empty result + + // And a mock file system that returns no files + handler.shims.ReadFile = func(name string) ([]byte, error) { + return nil, fmt.Errorf("file not found") + } + + handler.shims.Stat = func(name string) (os.FileInfo, error) { + return nil, os.ErrNotExist + } + + // When loading the config + err := handler.loadConfig() + + // Then an error should be returned since blueprint.yaml doesn't exist + if err == nil { + t.Errorf("Expected error when blueprint.yaml doesn't exist, got nil") + } + + // And the error should indicate blueprint.yaml not found + if !strings.Contains(err.Error(), "blueprint.yaml not found") { + t.Errorf("Expected error about blueprint.yaml not found, got: %v", err) + } + }) + + t.Run("PathBackslashNormalization", func(t *testing.T) { + handler, _ := setup(t) + handler.blueprint.Kustomizations = []blueprintv1alpha1.Kustomization{ + {Name: "k1", Path: "foo\\bar\\baz"}, + } + ks := handler.getKustomizations() + if ks[0].Path != "kustomize/foo/bar/baz" { + t.Errorf("expected normalized path, got %q", ks[0].Path) + } + }) + + t.Run("SetsRepositoryDefaultsInDevMode", func(t *testing.T) { + handler, mocks := setup(t) + + mockConfigHandler := mocks.ConfigHandler.(*config.MockConfigHandler) + mockConfigHandler.GetBoolFunc = func(key string, defaultValue ...bool) bool { + if key == "dev" { + return true + } + return false + } + mockConfigHandler.GetStringFunc = func(key string, defaultValue ...string) string { + if key == "dns.domain" && len(defaultValue) > 0 { + return defaultValue[0] + } + return "" + } + mockConfigHandler.GetBoolFunc = func(key string, defaultValue ...bool) bool { + if key == "dev" { + return true + } + return false + } + mockConfigHandler.GetConfigRootFunc = func() (string, error) { + return "/tmp/test-config", nil + } + + mocks.Shell.GetProjectRootFunc = func() (string, error) { + return "/Users/test/project/cli", nil + } + + handler.shims.FilepathBase = func(path string) string { + if path == "/Users/test/project/cli" { + return "cli" + } + return "" + } + + handler.shims.Stat = func(name string) (os.FileInfo, error) { + if strings.HasSuffix(name, ".yaml") { + return nil, nil + } + return nil, os.ErrNotExist + } + + blueprintWithoutURL := `kind: Blueprint +apiVersion: v1alpha1 +metadata: + name: test-blueprint + description: A test blueprint +repository: + ref: + branch: main +sources: [] +terraform: [] +kustomize: []` + + handler.shims.ReadFile = func(name string) ([]byte, error) { + if strings.HasSuffix(name, ".yaml") { + return []byte(blueprintWithoutURL), nil + } + return nil, os.ErrNotExist + } + + // Mock WriteFile to allow Write() to succeed + handler.shims.WriteFile = func(name string, data []byte, perm os.FileMode) error { + return nil + } + + err := handler.loadConfig() + + if err != nil { + t.Fatalf("Expected no error, got %v", err) + } + + // Repository defaults are now set during Write(), not loadConfig() + // So the URL should be empty after loadConfig() + if handler.blueprint.Repository.Url != "" { + t.Errorf("Expected repository URL to be empty after loadConfig(), got %s", handler.blueprint.Repository.Url) + } + + // Now test that Write() sets the repository defaults + // Use overwrite=true to ensure setRepositoryDefaults() is called + err = handler.Write(true) + if err != nil { + t.Fatalf("Expected no error during Write(), got %v", err) + } + + expectedURL := "http://git.test/git/cli" + if handler.blueprint.Repository.Url != expectedURL { + t.Errorf("Expected repository URL to be %s after Write(), got %s", expectedURL, handler.blueprint.Repository.Url) + } + }) +} diff --git a/pkg/composer/blueprint/blueprint_handler_public_test.go b/pkg/composer/blueprint/blueprint_handler_public_test.go index 05e042e2e..c0b00dccf 100644 --- a/pkg/composer/blueprint/blueprint_handler_public_test.go +++ b/pkg/composer/blueprint/blueprint_handler_public_test.go @@ -5,7 +5,6 @@ import ( "io/fs" "os" "path/filepath" - "reflect" "strings" "testing" "time" @@ -583,487 +582,6 @@ func TestBlueprintHandler_Initialize(t *testing.T) { } -func TestBlueprintHandler_LoadConfig(t *testing.T) { - setup := func(t *testing.T) (*BaseBlueprintHandler, *Mocks) { - t.Helper() - mocks := setupMocks(t) - handler := NewBlueprintHandler(mocks.Injector) - handler.shims = mocks.Shims - err := handler.Initialize() - if err != nil { - t.Fatalf("Failed to initialize handler: %v", err) - } - return handler, mocks - } - - t.Run("Success", func(t *testing.T) { - // Given a blueprint handler - handler, _ := setup(t) - - // When loading the config - err := handler.LoadConfig() - - // Then no error should be returned - if err != nil { - t.Errorf("Expected no error, got %v", err) - } - - // And the metadata should be correctly loaded - metadata := handler.GetMetadata() - if metadata.Name != "test-blueprint" { - t.Errorf("Expected name to be test-blueprint, got %s", metadata.Name) - } - }) - - t.Run("CustomPathOverride", func(t *testing.T) { - // Given a blueprint handler - handler, _ := setup(t) - - // And a mock file system that tracks checked paths - var checkedPaths []string - handler.shims.Stat = func(name string) (os.FileInfo, error) { - if strings.HasSuffix(name, ".jsonnet") || strings.HasSuffix(name, ".yaml") { - return nil, nil - } - return nil, os.ErrNotExist - } - handler.shims.ReadFile = func(name string) ([]byte, error) { - checkedPaths = append(checkedPaths, name) - if strings.HasSuffix(name, ".jsonnet") { - return []byte(safeBlueprintJsonnet), nil - } - if strings.HasSuffix(name, ".yaml") { - return []byte(safeBlueprintYAML), nil - } - return nil, os.ErrNotExist - } - - // When loading config - err := handler.LoadConfig() - - // Then no error should be returned - if err != nil { - t.Errorf("Expected no error, got %v", err) - } - - // And only yaml path should be checked since it exists - expectedPaths := []string{ - "blueprint.yaml", - } - for _, expected := range expectedPaths { - found := false - for _, checked := range checkedPaths { - if strings.HasSuffix(checked, expected) { - found = true - break - } - } - if !found { - t.Errorf("Expected path %s to be checked, but it wasn't. Checked paths: %v", expected, checkedPaths) - } - } - }) - - t.Run("DefaultBlueprint", func(t *testing.T) { - // Given a blueprint handler - handler, _ := setup(t) - - // And a mock file system that returns no existing files - handler.shims.Stat = func(name string) (os.FileInfo, error) { - return nil, os.ErrNotExist - } - - handler.shims.ReadFile = func(name string) ([]byte, error) { - return nil, os.ErrNotExist - } - - // And a local context - originalContext := os.Getenv("WINDSOR_CONTEXT") - os.Setenv("WINDSOR_CONTEXT", "local") - defer func() { os.Setenv("WINDSOR_CONTEXT", originalContext) }() - - // When loading the config - err := handler.LoadConfig() - - // Then an error should be returned since blueprint.yaml doesn't exist - if err == nil { - t.Errorf("Expected error when blueprint.yaml doesn't exist, got nil") - } - - // And the error should indicate blueprint.yaml not found - if !strings.Contains(err.Error(), "blueprint.yaml not found") { - t.Errorf("Expected error about blueprint.yaml not found, got: %v", err) - } - }) - - t.Run("ErrorUnmarshallingLocalJsonnet", func(t *testing.T) { - // Given a blueprint handler with local context - handler, mocks := setup(t) - mocks.ConfigHandler.SetContext("local") - - // And a mock yaml unmarshaller that returns an error - handler.shims.YamlUnmarshal = func(data []byte, obj any) error { - return fmt.Errorf("simulated unmarshalling error") - } - - // When loading the config - err := handler.LoadConfig() - - // Then an error should be returned - if err == nil { - t.Errorf("Expected LoadConfig to fail due to unmarshalling error, but it succeeded") - } - }) - - t.Run("ErrorGettingConfigRoot", func(t *testing.T) { - // Given a mock config handler that returns an error - mockConfigHandler := config.NewMockConfigHandler() - mockConfigHandler.GetConfigRootFunc = func() (string, error) { - return "", fmt.Errorf("error getting config root") - } - opts := &SetupOptions{ - ConfigHandler: mockConfigHandler, - } - mocks := setupMocks(t, opts) - - // And a blueprint handler using that config handler - handler := NewBlueprintHandler(mocks.Injector) - handler.shims = mocks.Shims - if err := handler.Initialize(); err != nil { - t.Fatalf("Failed to initialize handler: %v", err) - } - - // When loading the config - err := handler.LoadConfig() - - // Then an error should be returned - if err == nil || !strings.Contains(err.Error(), "error getting config root") { - t.Errorf("Expected error containing 'error getting config root', got: %v", err) - } - }) - - t.Run("ErrorReadingYamlFile", func(t *testing.T) { - // Given a blueprint handler - handler, _ := setup(t) - - // And a mock file system that finds yaml file but fails to read it - handler.shims.Stat = func(name string) (os.FileInfo, error) { - if strings.HasSuffix(name, "blueprint.yaml") { - return nil, nil // File exists - } - return nil, os.ErrNotExist - } - handler.shims.ReadFile = func(name string) ([]byte, error) { - if strings.HasSuffix(name, "blueprint.yaml") { - return nil, fmt.Errorf("error reading yaml file") - } - return nil, os.ErrNotExist - } - - // When loading the config - err := handler.LoadConfig() - - // Then an error should be returned - if err == nil || !strings.Contains(err.Error(), "error reading yaml file") { - t.Errorf("Expected error containing 'error reading yaml file', got: %v", err) - } - }) - - t.Run("ErrorLoadingYamlFile", func(t *testing.T) { - // Given a blueprint handler - handler, _ := setup(t) - - // And a mock file system that returns an error for yaml files - handler.shims.Stat = func(name string) (os.FileInfo, error) { - if strings.HasSuffix(name, ".yaml") { - return nil, nil - } - return nil, os.ErrNotExist - } - handler.shims.ReadFile = func(name string) ([]byte, error) { - if strings.HasSuffix(name, ".yaml") { - return nil, fmt.Errorf("error reading yaml file") - } - return nil, os.ErrNotExist - } - - // When loading the config - err := handler.LoadConfig() - - // Then an error should be returned - if err == nil || !strings.Contains(err.Error(), "error reading yaml file") { - t.Errorf("Expected error containing 'error reading yaml file', got: %v", err) - } - }) - - t.Run("ErrorUnmarshallingYamlBlueprint", func(t *testing.T) { - // Given a blueprint handler - handler, _ := setup(t) - - // And a mock file system with a yaml file - handler.shims.Stat = func(name string) (os.FileInfo, error) { - if strings.HasSuffix(name, "blueprint.yaml") { - return nil, nil - } - return nil, os.ErrNotExist - } - - handler.shims.ReadFile = func(name string) ([]byte, error) { - if strings.HasSuffix(name, "blueprint.yaml") { - return []byte("invalid: yaml: content"), nil - } - return nil, os.ErrNotExist - } - - // And a mock yaml unmarshaller that returns an error - handler.shims.YamlUnmarshal = func(data []byte, obj any) error { - return fmt.Errorf("error unmarshalling blueprint data") - } - - // When loading the config - err := handler.LoadConfig() - - // Then an error should be returned - if err == nil || !strings.Contains(err.Error(), "error unmarshalling blueprint data") { - t.Errorf("Expected error containing 'error unmarshalling blueprint data', got: %v", err) - } - }) - - t.Run("EmptyEvaluatedJsonnet", func(t *testing.T) { - // Given a blueprint handler with local context - handler, mocks := setup(t) - mocks.ConfigHandler.SetContext("local") - - // And a mock jsonnet VM that returns empty result - - // And a mock file system that returns no files - handler.shims.ReadFile = func(name string) ([]byte, error) { - return nil, fmt.Errorf("file not found") - } - - handler.shims.Stat = func(name string) (os.FileInfo, error) { - return nil, os.ErrNotExist - } - - // When loading the config - err := handler.LoadConfig() - - // Then an error should be returned since blueprint.yaml doesn't exist - if err == nil { - t.Errorf("Expected error when blueprint.yaml doesn't exist, got nil") - } - - // And the error should indicate blueprint.yaml not found - if !strings.Contains(err.Error(), "blueprint.yaml not found") { - t.Errorf("Expected error about blueprint.yaml not found, got: %v", err) - } - }) - - t.Run("PathBackslashNormalization", func(t *testing.T) { - handler, _ := setup(t) - handler.blueprint.Kustomizations = []blueprintv1alpha1.Kustomization{ - {Name: "k1", Path: "foo\\bar\\baz"}, - } - ks := handler.GetKustomizations() - if ks[0].Path != "kustomize/foo/bar/baz" { - t.Errorf("expected normalized path, got %q", ks[0].Path) - } - }) - - t.Run("SetsRepositoryDefaultsInDevMode", func(t *testing.T) { - handler, mocks := setup(t) - - mockConfigHandler := mocks.ConfigHandler.(*config.MockConfigHandler) - mockConfigHandler.GetBoolFunc = func(key string, defaultValue ...bool) bool { - if key == "dev" { - return true - } - return false - } - mockConfigHandler.GetStringFunc = func(key string, defaultValue ...string) string { - if key == "dns.domain" && len(defaultValue) > 0 { - return defaultValue[0] - } - return "" - } - mockConfigHandler.GetBoolFunc = func(key string, defaultValue ...bool) bool { - if key == "dev" { - return true - } - return false - } - mockConfigHandler.GetConfigRootFunc = func() (string, error) { - return "/tmp/test-config", nil - } - - mocks.Shell.GetProjectRootFunc = func() (string, error) { - return "/Users/test/project/cli", nil - } - - handler.shims.FilepathBase = func(path string) string { - if path == "/Users/test/project/cli" { - return "cli" - } - return "" - } - - handler.shims.Stat = func(name string) (os.FileInfo, error) { - if strings.HasSuffix(name, ".yaml") { - return nil, nil - } - return nil, os.ErrNotExist - } - - blueprintWithoutURL := `kind: Blueprint -apiVersion: v1alpha1 -metadata: - name: test-blueprint - description: A test blueprint -repository: - ref: - branch: main -sources: [] -terraform: [] -kustomize: []` - - handler.shims.ReadFile = func(name string) ([]byte, error) { - if strings.HasSuffix(name, ".yaml") { - return []byte(blueprintWithoutURL), nil - } - return nil, os.ErrNotExist - } - - // Mock WriteFile to allow Write() to succeed - handler.shims.WriteFile = func(name string, data []byte, perm os.FileMode) error { - return nil - } - - err := handler.LoadConfig() - - if err != nil { - t.Fatalf("Expected no error, got %v", err) - } - - // Repository defaults are now set during Write(), not LoadConfig() - // So the URL should be empty after LoadConfig() - if handler.blueprint.Repository.Url != "" { - t.Errorf("Expected repository URL to be empty after LoadConfig(), got %s", handler.blueprint.Repository.Url) - } - - // Now test that Write() sets the repository defaults - // Use overwrite=true to ensure setRepositoryDefaults() is called - err = handler.Write(true) - if err != nil { - t.Fatalf("Expected no error during Write(), got %v", err) - } - - expectedURL := "http://git.test/git/cli" - if handler.blueprint.Repository.Url != expectedURL { - t.Errorf("Expected repository URL to be %s after Write(), got %s", expectedURL, handler.blueprint.Repository.Url) - } - }) -} - -func TestBlueprintHandler_GetRepository(t *testing.T) { - setup := func(t *testing.T) (*BaseBlueprintHandler, *Mocks) { - t.Helper() - mocks := setupMocks(t) - handler := NewBlueprintHandler(mocks.Injector) - handler.shims = mocks.Shims - err := handler.Initialize() - if err != nil { - t.Fatalf("Failed to initialize handler: %v", err) - } - return handler, mocks - } - - t.Run("ReturnsExpectedRepository", func(t *testing.T) { - // Given a blueprint handler with a set repository - handler, _ := setup(t) - expectedRepo := blueprintv1alpha1.Repository{ - Url: "git::https://example.com/repo.git", - Ref: blueprintv1alpha1.Reference{Branch: "main"}, - } - handler.blueprint.Repository = expectedRepo - - // When getting the repository - repo := handler.GetRepository() - - // Then the expected repository should be returned - if repo != expectedRepo { - t.Errorf("Expected repository %+v, got %+v", expectedRepo, repo) - } - }) - - t.Run("ReturnsDefaultValues", func(t *testing.T) { - // Given a blueprint handler with an empty repository - handler, _ := setup(t) - handler.blueprint.Repository = blueprintv1alpha1.Repository{} - - // When getting the repository - repo := handler.GetRepository() - - // Then default values should be set - expectedRepo := blueprintv1alpha1.Repository{ - Url: "", - Ref: blueprintv1alpha1.Reference{Branch: "main"}, - } - if repo != expectedRepo { - t.Errorf("Expected repository %+v, got %+v", expectedRepo, repo) - } - }) -} - -func TestBlueprintHandler_GetSources(t *testing.T) { - setup := func(t *testing.T) (*BaseBlueprintHandler, *Mocks) { - t.Helper() - mocks := setupMocks(t) - handler := NewBlueprintHandler(mocks.Injector) - handler.shims = mocks.Shims - handler.blueprint = blueprintv1alpha1.Blueprint{ - Sources: []blueprintv1alpha1.Source{}, - } - err := handler.Initialize() - if err != nil { - t.Fatalf("Failed to initialize handler: %v", err) - } - return handler, mocks - } - - t.Run("ReturnsExpectedSources", func(t *testing.T) { - // Given a blueprint handler with a set of sources - handler, _ := setup(t) - expectedSources := []blueprintv1alpha1.Source{ - { - Name: "source1", - Url: "git::https://example.com/source1.git", - Ref: blueprintv1alpha1.Reference{Branch: "main"}, - PathPrefix: "/source1", - }, - { - Name: "source2", - Url: "git::https://example.com/source2.git", - Ref: blueprintv1alpha1.Reference{Branch: "develop"}, - PathPrefix: "/source2", - }, - } - handler.blueprint.Sources = expectedSources - - // When getting sources - sources := handler.GetSources() - - // Then the returned sources should match the expected sources - if len(sources) != len(expectedSources) { - t.Fatalf("Expected %d sources, got %d", len(expectedSources), len(sources)) - } - for i := range expectedSources { - if sources[i] != expectedSources[i] { - t.Errorf("Source[%d] = %+v, want %+v", i, sources[i], expectedSources[i]) - } - } - }) -} - func TestBlueprintHandler_GetTerraformComponents(t *testing.T) { setup := func(t *testing.T) (*BaseBlueprintHandler, *Mocks) { t.Helper() @@ -1252,46 +770,6 @@ func TestBlueprintHandler_GetTerraformComponents(t *testing.T) { }) } -func TestBlueprintHandler_GetDefaultTemplateData(t *testing.T) { - setup := func(t *testing.T) (BlueprintHandler, *Mocks) { - t.Helper() - mocks := setupMocks(t) - handler := NewBlueprintHandler(mocks.Injector) - handler.shims = mocks.Shims - err := handler.Initialize() - if err != nil { - t.Fatalf("Failed to initialize handler: %v", err) - } - return handler, mocks - } - - t.Run("ReturnsDefaultTemplate", func(t *testing.T) { - // Given a blueprint handler - handler, _ := setup(t) - - // When getting default template data - result, err := handler.GetDefaultTemplateData("local") - - // Then no error should occur - if err != nil { - t.Fatalf("Expected no error, got: %v", err) - } - - // And result should contain blueprint.jsonnet - if len(result) != 1 { - t.Fatalf("Expected 1 template file, got: %d", len(result)) - } - - if _, exists := result["blueprint.jsonnet"]; !exists { - t.Error("Expected blueprint.jsonnet to exist in result") - } - - if len(result["blueprint.jsonnet"]) == 0 { - t.Error("Expected blueprint.jsonnet to have content") - } - }) -} - func TestBlueprintHandler_GetLocalTemplateData(t *testing.T) { setup := func(t *testing.T) (BlueprintHandler, *Mocks) { t.Helper() @@ -2057,7 +1535,7 @@ substitutions: }) } -func TestBlueprintHandler_LoadData(t *testing.T) { +func TestBlueprintHandler_loadData(t *testing.T) { setup := func(t *testing.T) (*BaseBlueprintHandler, *Mocks) { t.Helper() mocks := setupMocks(t) @@ -2101,7 +1579,7 @@ func TestBlueprintHandler_LoadData(t *testing.T) { } // When loading the data - err := handler.LoadData(blueprintData) + err := handler.loadData(blueprintData) // Then no error should be returned if err != nil { @@ -2109,7 +1587,7 @@ func TestBlueprintHandler_LoadData(t *testing.T) { } // And the metadata should be correctly loaded - metadata := handler.GetMetadata() + metadata := handler.getMetadata() if metadata.Name != "test-blueprint" { t.Errorf("Expected name to be test-blueprint, got %s", metadata.Name) } @@ -2117,7 +1595,7 @@ func TestBlueprintHandler_LoadData(t *testing.T) { t.Errorf("Expected description to be 'A test blueprint from data', got %s", metadata.Description) } // And the sources should be loaded - sources := handler.GetSources() + sources := handler.getSources() if len(sources) != 1 { t.Errorf("Expected 1 source, got %d", len(sources)) } @@ -2153,11 +1631,11 @@ func TestBlueprintHandler_LoadData(t *testing.T) { } // When loading the data - err := handler.LoadData(blueprintData) + err := handler.loadData(blueprintData) // Then an error should be returned if err == nil { - t.Errorf("Expected LoadData to fail due to marshalling error, but it succeeded") + t.Errorf("Expected loadData to fail due to marshalling error, but it succeeded") } if !strings.Contains(err.Error(), "error marshalling blueprint data to yaml") { t.Errorf("Expected error message to contain 'error marshalling blueprint data to yaml', got %v", err) @@ -2179,11 +1657,11 @@ func TestBlueprintHandler_LoadData(t *testing.T) { } // When loading the data - err := handler.LoadData(blueprintData) + err := handler.loadData(blueprintData) // Then an error should be returned if err == nil { - t.Errorf("Expected LoadData to fail due to unmarshalling error, but it succeeded") + t.Errorf("Expected loadData to fail due to unmarshalling error, but it succeeded") } }) @@ -2208,7 +1686,7 @@ func TestBlueprintHandler_LoadData(t *testing.T) { } // When loading the data with OCI info - err := handler.LoadData(blueprintData, ociInfo) + err := handler.loadData(blueprintData, ociInfo) // Then no error should be returned if err != nil { @@ -2216,7 +1694,7 @@ func TestBlueprintHandler_LoadData(t *testing.T) { } // And the metadata should be correctly loaded - metadata := handler.GetMetadata() + metadata := handler.getMetadata() if metadata.Name != "oci-blueprint" { t.Errorf("Expected name to be oci-blueprint, got %s", metadata.Name) } @@ -2225,18 +1703,18 @@ func TestBlueprintHandler_LoadData(t *testing.T) { } }) - t.Run("LoadDataIgnoredWhenConfigAlreadyLoaded", func(t *testing.T) { + t.Run("loadDataIgnoredWhenConfigAlreadyLoaded", func(t *testing.T) { // Given a blueprint handler that has already loaded config handler, _ := setup(t) // Load config first (simulates loading from YAML) - err := handler.LoadConfig() + err := handler.loadConfig() if err != nil { t.Fatalf("Failed to load config: %v", err) } // Get the original metadata - originalMetadata := handler.GetMetadata() + originalMetadata := handler.getMetadata() // And different blueprint data that would overwrite the config differentData := map[string]any{ @@ -2249,15 +1727,15 @@ func TestBlueprintHandler_LoadData(t *testing.T) { } // When loading the different data - err = handler.LoadData(differentData) + err = handler.loadData(differentData) // Then no error should be returned if err != nil { t.Errorf("Expected no error, got %v", err) } - // But the metadata should remain unchanged (LoadData should be ignored) - currentMetadata := handler.GetMetadata() + // But the metadata should remain unchanged (loadData should be ignored) + currentMetadata := handler.getMetadata() if currentMetadata.Name != originalMetadata.Name { t.Errorf("Expected metadata to remain unchanged, but name changed from %s to %s", originalMetadata.Name, currentMetadata.Name) } @@ -2605,89 +2083,6 @@ func TestBlueprintHandler_Write(t *testing.T) { }) } -func TestBaseBlueprintHandler_SetRenderedKustomizeData(t *testing.T) { - setup := func(t *testing.T) *BaseBlueprintHandler { - t.Helper() - injector := di.NewInjector() - handler := NewBlueprintHandler(injector) - return handler - } - - t.Run("SetData", func(t *testing.T) { - // Given a handler with no existing data - handler := setup(t) - data := map[string]any{ - "key1": "value1", - "key2": 42, - } - // When setting rendered kustomize data - handler.SetRenderedKustomizeData(data) - // Then data should be stored - if !reflect.DeepEqual(handler.kustomizeData, data) { - t.Errorf("Expected kustomizeData = %v, got = %v", data, handler.kustomizeData) - } - }) - - t.Run("OverwriteData", func(t *testing.T) { - // Given a handler with existing data - handler := setup(t) - handler.kustomizeData = map[string]any{ - "existing": "data", - } - newData := map[string]any{ - "new": "data", - } - // When setting new rendered kustomize data - handler.SetRenderedKustomizeData(newData) - // Then new data should overwrite existing data - if !reflect.DeepEqual(handler.kustomizeData, newData) { - t.Errorf("Expected kustomizeData = %v, got = %v", newData, handler.kustomizeData) - } - }) - - t.Run("EmptyData", func(t *testing.T) { - // Given a handler with existing data - handler := setup(t) - handler.kustomizeData = map[string]any{ - "existing": "data", - } - emptyData := map[string]any{} - // When setting empty rendered kustomize data - handler.SetRenderedKustomizeData(emptyData) - // Then empty data should be stored - if !reflect.DeepEqual(handler.kustomizeData, emptyData) { - t.Errorf("Expected kustomizeData = %v, got = %v", emptyData, handler.kustomizeData) - } - }) - - t.Run("ComplexData", func(t *testing.T) { - // Given a handler with no existing data - handler := setup(t) - complexData := map[string]any{ - "nested": map[string]any{ - "level1": map[string]any{ - "level2": []any{ - "string1", - 123, - map[string]any{"key": "value"}, - }, - }, - }, - "array": []any{ - "item1", - 456, - map[string]any{"nested": "data"}, - }, - } - // When setting complex rendered kustomize data - handler.SetRenderedKustomizeData(complexData) - // Then complex data should be stored - if !reflect.DeepEqual(handler.kustomizeData, complexData) { - t.Errorf("Expected kustomizeData = %v, got = %v", complexData, handler.kustomizeData) - } - }) -} - func TestBlueprintHandler_LoadBlueprint(t *testing.T) { t.Run("LoadsBlueprintSuccessfullyWithLocalTemplates", func(t *testing.T) { // Given a blueprint handler with local templates @@ -2741,7 +2136,7 @@ kustomizations: []` } // And blueprint should be loaded - metadata := handler.GetMetadata() + metadata := handler.getMetadata() if metadata.Name != "test-blueprint" { t.Errorf("Expected blueprint name 'test-blueprint', got %s", metadata.Name) } diff --git a/pkg/composer/blueprint/mock_blueprint_handler.go b/pkg/composer/blueprint/mock_blueprint_handler.go index 6caa5d6ea..3479746f4 100644 --- a/pkg/composer/blueprint/mock_blueprint_handler.go +++ b/pkg/composer/blueprint/mock_blueprint_handler.go @@ -3,27 +3,17 @@ package blueprint import ( blueprintv1alpha1 "github.com/windsorcli/cli/api/v1alpha1" "github.com/windsorcli/cli/pkg/di" - "github.com/windsorcli/cli/pkg/composer/artifact" ) // MockBlueprintHandler is a mock implementation of BlueprintHandler interface for testing type MockBlueprintHandler struct { - InitializeFunc func() error - LoadBlueprintFunc func() error - LoadConfigFunc func() error - LoadDataFunc func(data map[string]any, ociInfo ...*artifact.OCIArtifactInfo) error - WriteFunc func(overwrite ...bool) error - GetMetadataFunc func() blueprintv1alpha1.Metadata - GetSourcesFunc func() []blueprintv1alpha1.Source - GetTerraformComponentsFunc func() []blueprintv1alpha1.TerraformComponent - GetKustomizationsFunc func() []blueprintv1alpha1.Kustomization - - WaitForKustomizationsFunc func(message string, names ...string) error - GetDefaultTemplateDataFunc func(contextName string) (map[string][]byte, error) - GetLocalTemplateDataFunc func() (map[string][]byte, error) - InstallFunc func() error - GetRepositoryFunc func() blueprintv1alpha1.Repository - + InitializeFunc func() error + LoadBlueprintFunc func() error + WriteFunc func(overwrite ...bool) error + GetTerraformComponentsFunc func() []blueprintv1alpha1.TerraformComponent + WaitForKustomizationsFunc func(message string, names ...string) error + GetLocalTemplateDataFunc func() (map[string][]byte, error) + InstallFunc func() error DownFunc func() error SetRenderedKustomizeDataFunc func(data map[string]any) GenerateFunc func() *blueprintv1alpha1.Blueprint @@ -58,22 +48,6 @@ func (m *MockBlueprintHandler) LoadBlueprint() error { return nil } -// LoadConfig calls the mock LoadConfigFunc if set, otherwise returns nil -func (m *MockBlueprintHandler) LoadConfig() error { - if m.LoadConfigFunc != nil { - return m.LoadConfigFunc() - } - return nil -} - -// LoadData calls the mock LoadDataFunc if set, otherwise returns nil -func (m *MockBlueprintHandler) LoadData(data map[string]any, ociInfo ...*artifact.OCIArtifactInfo) error { - if m.LoadDataFunc != nil { - return m.LoadDataFunc(data, ociInfo...) - } - return nil -} - // Write calls the mock WriteFunc if set, otherwise returns nil func (m *MockBlueprintHandler) Write(overwrite ...bool) error { if m.WriteFunc != nil { @@ -82,24 +56,6 @@ func (m *MockBlueprintHandler) Write(overwrite ...bool) error { return nil } -// GetMetadata calls the mock GetMetadataFunc if set, otherwise returns a reasonable default -// MetadataV1Alpha1 -func (m *MockBlueprintHandler) GetMetadata() blueprintv1alpha1.Metadata { - if m.GetMetadataFunc != nil { - return m.GetMetadataFunc() - } - return blueprintv1alpha1.Metadata{} -} - -// GetSources calls the mock GetSourcesFunc if set, otherwise returns a reasonable default -// slice of SourceV1Alpha1 -func (m *MockBlueprintHandler) GetSources() []blueprintv1alpha1.Source { - if m.GetSourcesFunc != nil { - return m.GetSourcesFunc() - } - return []blueprintv1alpha1.Source{} -} - // GetTerraformComponents calls the mock GetTerraformComponentsFunc if set, otherwise returns a // reasonable default slice of TerraformComponentV1Alpha1 func (m *MockBlueprintHandler) GetTerraformComponents() []blueprintv1alpha1.TerraformComponent { @@ -109,15 +65,6 @@ func (m *MockBlueprintHandler) GetTerraformComponents() []blueprintv1alpha1.Terr return []blueprintv1alpha1.TerraformComponent{} } -// GetKustomizations calls the mock GetKustomizationsFunc if set, otherwise returns a reasonable -// default slice of kustomizev1.Kustomization -func (m *MockBlueprintHandler) GetKustomizations() []blueprintv1alpha1.Kustomization { - if m.GetKustomizationsFunc != nil { - return m.GetKustomizationsFunc() - } - return []blueprintv1alpha1.Kustomization{} -} - // Install calls the mock InstallFunc if set, otherwise returns nil func (m *MockBlueprintHandler) Install() error { if m.InstallFunc != nil { @@ -126,14 +73,6 @@ func (m *MockBlueprintHandler) Install() error { return nil } -// GetRepository calls the mock GetRepositoryFunc if set, otherwise returns empty Repository -func (m *MockBlueprintHandler) GetRepository() blueprintv1alpha1.Repository { - if m.GetRepositoryFunc != nil { - return m.GetRepositoryFunc() - } - return blueprintv1alpha1.Repository{} -} - // Down mocks the Down method. func (m *MockBlueprintHandler) Down() error { if m.DownFunc != nil { @@ -157,14 +96,6 @@ func (m *MockBlueprintHandler) WaitForKustomizations(message string, names ...st return nil } -// GetDefaultTemplateData calls the mock GetDefaultTemplateDataFunc if set, otherwise returns empty map -func (m *MockBlueprintHandler) GetDefaultTemplateData(contextName string) (map[string][]byte, error) { - if m.GetDefaultTemplateDataFunc != nil { - return m.GetDefaultTemplateDataFunc(contextName) - } - return map[string][]byte{}, nil -} - // GetLocalTemplateData calls the mock GetLocalTemplateDataFunc if set, otherwise returns empty map func (m *MockBlueprintHandler) GetLocalTemplateData() (map[string][]byte, error) { if m.GetLocalTemplateDataFunc != nil { diff --git a/pkg/composer/blueprint/mock_blueprint_handler_test.go b/pkg/composer/blueprint/mock_blueprint_handler_test.go index 3ef371321..e3918096d 100644 --- a/pkg/composer/blueprint/mock_blueprint_handler_test.go +++ b/pkg/composer/blueprint/mock_blueprint_handler_test.go @@ -7,7 +7,6 @@ import ( blueprintv1alpha1 "github.com/windsorcli/cli/api/v1alpha1" "github.com/windsorcli/cli/pkg/di" - "github.com/windsorcli/cli/pkg/composer/artifact" ) // ============================================================================= @@ -48,112 +47,6 @@ func TestMockBlueprintHandler_Initialize(t *testing.T) { }) } -func TestMockBlueprintHandler_LoadConfig(t *testing.T) { - setup := func(t *testing.T) *MockBlueprintHandler { - t.Helper() - injector := di.NewInjector() - handler := NewMockBlueprintHandler(injector) - return handler - } - - mockLoadErr := fmt.Errorf("mock load config error") - - t.Run("WithError", func(t *testing.T) { - // Given a mock handler with load config function - handler := setup(t) - handler.LoadConfigFunc = func() error { - return mockLoadErr - } - // When loading config - err := handler.LoadConfig() - // Then expected error should be returned - if err != mockLoadErr { - t.Errorf("Expected error = %v, got = %v", mockLoadErr, err) - } - }) - - t.Run("WithNoFuncSet", func(t *testing.T) { - // Given a mock handler without load config function - handler := setup(t) - // When loading config - err := handler.LoadConfig() - // Then no error should be returned - if err != nil { - t.Errorf("Expected error = %v, got = %v", nil, err) - } - }) -} - -func TestMockBlueprintHandler_GetMetadata(t *testing.T) { - setup := func(t *testing.T) *MockBlueprintHandler { - t.Helper() - injector := di.NewInjector() - handler := NewMockBlueprintHandler(injector) - return handler - } - - t.Run("WithFuncSet", func(t *testing.T) { - // Given a mock handler with get metadata function - handler := setup(t) - expectedMetadata := blueprintv1alpha1.Metadata{} - handler.GetMetadataFunc = func() blueprintv1alpha1.Metadata { - return expectedMetadata - } - // When getting metadata - metadata := handler.GetMetadata() - // Then expected metadata should be returned - if !reflect.DeepEqual(metadata, expectedMetadata) { - t.Errorf("Expected metadata = %v, got = %v", expectedMetadata, metadata) - } - }) - - t.Run("WithNoFuncSet", func(t *testing.T) { - // Given a mock handler without get metadata function - handler := setup(t) - // When getting metadata - metadata := handler.GetMetadata() - // Then empty metadata should be returned - if !reflect.DeepEqual(metadata, blueprintv1alpha1.Metadata{}) { - t.Errorf("Expected metadata = %v, got = %v", blueprintv1alpha1.Metadata{}, metadata) - } - }) -} - -func TestMockBlueprintHandler_GetSources(t *testing.T) { - setup := func(t *testing.T) *MockBlueprintHandler { - t.Helper() - injector := di.NewInjector() - handler := NewMockBlueprintHandler(injector) - return handler - } - - t.Run("WithFuncSet", func(t *testing.T) { - // Given a mock handler with get sources function - handler := setup(t) - expectedSources := []blueprintv1alpha1.Source{} - handler.GetSourcesFunc = func() []blueprintv1alpha1.Source { - return expectedSources - } - // When getting sources - sources := handler.GetSources() - // Then expected sources should be returned - if !reflect.DeepEqual(sources, expectedSources) { - t.Errorf("Expected sources = %v, got = %v", expectedSources, sources) - } - }) - - t.Run("WithNoFuncSet", func(t *testing.T) { - // Given a mock handler without get sources function - handler := setup(t) - // When getting sources - sources := handler.GetSources() - // Then empty sources should be returned - if !reflect.DeepEqual(sources, []blueprintv1alpha1.Source{}) { - t.Errorf("Expected sources = %v, got = %v", []blueprintv1alpha1.Source{}, sources) - } - }) -} - func TestMockBlueprintHandler_GetTerraformComponents(t *testing.T) { setup := func(t *testing.T) *MockBlueprintHandler { t.Helper() @@ -189,41 +82,6 @@ func TestMockBlueprintHandler_GetTerraformComponents(t *testing.T) { }) } -func TestMockBlueprintHandler_GetKustomizations(t *testing.T) { - setup := func(t *testing.T) *MockBlueprintHandler { - t.Helper() - injector := di.NewInjector() - handler := NewMockBlueprintHandler(injector) - return handler - } - - t.Run("WithFuncSet", func(t *testing.T) { - // Given a mock handler with get kustomizations function - handler := setup(t) - expectedKustomizations := []blueprintv1alpha1.Kustomization{} - handler.GetKustomizationsFunc = func() []blueprintv1alpha1.Kustomization { - return expectedKustomizations - } - // When getting kustomizations - kustomizations := handler.GetKustomizations() - // Then expected kustomizations should be returned - if !reflect.DeepEqual(kustomizations, expectedKustomizations) { - t.Errorf("Expected kustomizations = %v, got = %v", expectedKustomizations, kustomizations) - } - }) - - t.Run("WithNoFuncSet", func(t *testing.T) { - // Given a mock handler without get kustomizations function - handler := setup(t) - // When getting kustomizations - kustomizations := handler.GetKustomizations() - // Then empty kustomizations should be returned - if !reflect.DeepEqual(kustomizations, []blueprintv1alpha1.Kustomization{}) { - t.Errorf("Expected kustomizations = %v, got = %v", []blueprintv1alpha1.Kustomization{}, kustomizations) - } - }) -} - func TestMockBlueprintHandler_Install(t *testing.T) { setup := func(t *testing.T) *MockBlueprintHandler { t.Helper() @@ -260,44 +118,6 @@ func TestMockBlueprintHandler_Install(t *testing.T) { }) } -func TestMockBlueprintHandler_GetRepository(t *testing.T) { - setup := func(t *testing.T) *MockBlueprintHandler { - t.Helper() - injector := di.NewInjector() - handler := NewMockBlueprintHandler(injector) - return handler - } - - t.Run("DefaultBehavior", func(t *testing.T) { - // Given a mock handler without get repository function - handler := setup(t) - // When getting repository - repo := handler.GetRepository() - // Then empty repository should be returned - if repo != (blueprintv1alpha1.Repository{}) { - t.Errorf("Expected empty Repository, got %+v", repo) - } - }) - - t.Run("WithMockFunction", func(t *testing.T) { - // Given a mock handler with get repository function - handler := setup(t) - expected := blueprintv1alpha1.Repository{ - Url: "test-url", - Ref: blueprintv1alpha1.Reference{Branch: "main"}, - } - handler.GetRepositoryFunc = func() blueprintv1alpha1.Repository { - return expected - } - // When getting repository - repo := handler.GetRepository() - // Then expected repository should be returned - if repo != expected { - t.Errorf("Expected %+v, got %+v", expected, repo) - } - }) -} - func TestMockBlueprintHandler_WaitForKustomizations(t *testing.T) { setup := func(t *testing.T) *MockBlueprintHandler { t.Helper() @@ -340,71 +160,6 @@ func TestMockBlueprintHandler_WaitForKustomizations(t *testing.T) { }) } -func TestMockBlueprintHandler_GetDefaultTemplateData(t *testing.T) { - setup := func(t *testing.T) *MockBlueprintHandler { - t.Helper() - return &MockBlueprintHandler{} - } - - t.Run("WithFuncSet", func(t *testing.T) { - // Given a mock handler with GetDefaultTemplateData function - handler := setup(t) - expectedData := map[string][]byte{ - "template1": []byte("template content 1"), - "template2": []byte("template content 2"), - } - handler.GetDefaultTemplateDataFunc = func(contextName string) (map[string][]byte, error) { - return expectedData, nil - } - // When getting default template data - data, err := handler.GetDefaultTemplateData("test-context") - // Then expected data should be returned - if err != nil { - t.Errorf("Expected error = %v, got = %v", nil, err) - } - if len(data) != len(expectedData) { - t.Errorf("Expected data length = %v, got = %v", len(expectedData), len(data)) - } - for key, expectedValue := range expectedData { - if value, exists := data[key]; !exists || string(value) != string(expectedValue) { - t.Errorf("Expected data[%s] = %s, got = %s", key, string(expectedValue), string(value)) - } - } - }) - - t.Run("WithNoFuncSet", func(t *testing.T) { - // Given a mock handler with no GetDefaultTemplateData function - handler := setup(t) - // When getting default template data - data, err := handler.GetDefaultTemplateData("test-context") - // Then empty map and no error should be returned - if err != nil { - t.Errorf("Expected error = %v, got = %v", nil, err) - } - if data == nil || len(data) != 0 { - t.Errorf("Expected empty map, got = %v", data) - } - }) - - t.Run("WithError", func(t *testing.T) { - // Given a mock handler with GetDefaultTemplateData function that returns error - handler := setup(t) - mockErr := fmt.Errorf("mock error") - handler.GetDefaultTemplateDataFunc = func(contextName string) (map[string][]byte, error) { - return nil, mockErr - } - // When getting default template data - data, err := handler.GetDefaultTemplateData("test-context") - // Then expected error should be returned - if err != mockErr { - t.Errorf("Expected error = %v, got = %v", mockErr, err) - } - if data != nil { - t.Errorf("Expected data = %v, got = %v", nil, data) - } - }) -} - func TestMockBlueprintHandler_GetLocalTemplateData(t *testing.T) { setup := func(t *testing.T) *MockBlueprintHandler { t.Helper() @@ -675,38 +430,6 @@ func TestMockBlueprintHandler_SetRenderedKustomizeData(t *testing.T) { }) } -func TestMockBlueprintHandler_LoadData(t *testing.T) { - t.Run("WithFuncSet", func(t *testing.T) { - // Given a mock blueprint handler with LoadDataFunc set - handler := NewMockBlueprintHandler(di.NewInjector()) - expectedError := fmt.Errorf("mock load data error") - handler.LoadDataFunc = func(data map[string]any, ociInfo ...*artifact.OCIArtifactInfo) error { - return expectedError - } - - // When LoadData is called - err := handler.LoadData(map[string]any{}) - - // Then it should return the expected error - if err != expectedError { - t.Errorf("expected error %v, got %v", expectedError, err) - } - }) - - t.Run("WithNoFuncSet", func(t *testing.T) { - // Given a mock blueprint handler without LoadDataFunc set - handler := NewMockBlueprintHandler(di.NewInjector()) - - // When LoadData is called - err := handler.LoadData(map[string]any{}) - - // Then it should return nil - if err != nil { - t.Errorf("expected nil, got %v", err) - } - }) -} - func TestMockBlueprintHandler_Generate(t *testing.T) { setup := func(t *testing.T) *MockBlueprintHandler { t.Helper() diff --git a/pkg/composer/blueprint/templates/default.jsonnet b/pkg/composer/blueprint/templates/default.jsonnet deleted file mode 100644 index 78470493b..000000000 --- a/pkg/composer/blueprint/templates/default.jsonnet +++ /dev/null @@ -1,26 +0,0 @@ -local context = std.extVar("context"); -local ociUrl = std.extVar("ociUrl"); - -{ - kind: "Blueprint", - apiVersion: "blueprints.windsorcli.dev/v1alpha1", - metadata: { - name: context.name, - description: "This blueprint outlines resources in the " + context.name + " context", - }, - repository: { - url: "", - ref: { - branch: "main", - }, - secretName: "flux-system", - }, - sources: [ - { - name: "core", - url: ociUrl, - }, - ], - terraform: [], - kustomize: [], -} diff --git a/pkg/runtime/tools/tools_manager.go b/pkg/runtime/tools/tools_manager.go index aea94e79d..e437fb3ed 100644 --- a/pkg/runtime/tools/tools_manager.go +++ b/pkg/runtime/tools/tools_manager.go @@ -3,7 +3,6 @@ package tools import ( "fmt" "os" - "path/filepath" "regexp" "strconv" "strings" @@ -11,10 +10,10 @@ import ( "github.com/briandowns/spinner" "github.com/windsorcli/cli/pkg/constants" + "github.com/windsorcli/cli/pkg/di" "github.com/windsorcli/cli/pkg/runtime/config" "github.com/windsorcli/cli/pkg/runtime/shell" sh "github.com/windsorcli/cli/pkg/runtime/shell" - "github.com/windsorcli/cli/pkg/di" ) // The ToolsManager is a core component that manages development tools and dependencies @@ -141,29 +140,6 @@ func (t *BaseToolsManager) Check() error { return nil } -// CheckExistingToolsManager identifies the active tools manager, prioritizing aqua. -func CheckExistingToolsManager(projectRoot string) (string, error) { - aquaPath := filepath.Join(projectRoot, "aqua.yaml") - if _, err := osStat(aquaPath); err == nil { - return "aqua", nil - } - - asdfPath := filepath.Join(projectRoot, ".tool-versions") - if _, err := osStat(asdfPath); err == nil { - return "asdf", nil - } - - if _, err := execLookPath("aqua"); err == nil { - return "aqua", nil - } - - if _, err := execLookPath("asdf"); err == nil { - return "asdf", nil - } - - return "", nil -} - // ============================================================================= // Private Methods // ============================================================================= diff --git a/pkg/runtime/tools/tools_manager_test.go b/pkg/runtime/tools/tools_manager_test.go index f38b069e6..65767729f 100644 --- a/pkg/runtime/tools/tools_manager_test.go +++ b/pkg/runtime/tools/tools_manager_test.go @@ -7,9 +7,9 @@ import ( "strings" "testing" - "github.com/windsorcli/cli/pkg/runtime/config" "github.com/windsorcli/cli/pkg/constants" "github.com/windsorcli/cli/pkg/di" + "github.com/windsorcli/cli/pkg/runtime/config" sh "github.com/windsorcli/cli/pkg/runtime/shell" ) @@ -1072,183 +1072,6 @@ func TestToolsManager_checkAwsCli(t *testing.T) { }) } -// ============================================================================= -// Test Public Helpers -// ============================================================================= - -// Tests for existing tools manager detection -func TestCheckExistingToolsManager(t *testing.T) { - setup := func(t *testing.T) *Mocks { - t.Helper() - return setupMocks(t) - } - - t.Run("NoToolsManager", func(t *testing.T) { - // Given no tools manager is installed or configured - setup(t) - projectRoot := "/path/to/project" - osStat = func(name string) (os.FileInfo, error) { - return nil, os.ErrNotExist - } - execLookPath = func(name string) (string, error) { - return "", exec.ErrNotFound - } - // When checking for existing tools manager - managerName, err := CheckExistingToolsManager(projectRoot) - // Then no error should be returned and manager name should be empty - if err != nil { - t.Errorf("Expected CheckExistingToolsManager to succeed, but got error: %v", err) - } - if managerName != "" { - t.Errorf("Expected manager name to be empty, but got: %v", managerName) - } - }) - - t.Run("DetectsAqua", func(t *testing.T) { - // Given a project with aqua configuration - setup(t) - projectRoot := "/path/to/project/with/aqua" - osStat = func(name string) (os.FileInfo, error) { - if strings.Contains(name, "aqua.yaml") { - return nil, nil - } - return nil, os.ErrNotExist - } - // When checking for existing tools manager - managerName, err := CheckExistingToolsManager(projectRoot) - // Then aqua should be detected as the tools manager - if err != nil { - t.Errorf("Expected CheckExistingToolsManager to succeed, but got error: %v", err) - } - if managerName != "aqua" { - t.Errorf("Expected manager name to be 'aqua', but got: %v", managerName) - } - }) - - t.Run("DetectsAsdf", func(t *testing.T) { - // Given a project with asdf configuration - setup(t) - projectRoot := "/path/to/project/with/asdf" - osStat = func(name string) (os.FileInfo, error) { - if strings.Contains(name, ".tool-versions") { - return nil, nil - } - return nil, os.ErrNotExist - } - // When checking for existing tools manager - managerName, err := CheckExistingToolsManager(projectRoot) - // Then asdf should be detected as the tools manager - if err != nil { - t.Errorf("Expected CheckExistingToolsManager to succeed, but got error: %v", err) - } - if managerName != "asdf" { - t.Errorf("Expected manager name to be 'asdf', but got: %v", managerName) - } - }) - - t.Run("DetectsAquaInPath", func(t *testing.T) { - // Given aqua is available in system PATH - setup(t) - projectRoot := "/path/to/project" - osStat = func(name string) (os.FileInfo, error) { - return nil, os.ErrNotExist - } - execLookPath = func(name string) (string, error) { - if name == "aqua" { - return "/usr/bin/aqua", nil - } - return "", exec.ErrNotFound - } - // When checking for existing tools manager - managerName, err := CheckExistingToolsManager(projectRoot) - // Then aqua should be detected as the tools manager - if err != nil { - t.Errorf("Expected CheckExistingToolsManager to succeed, but got error: %v", err) - } - if managerName != "aqua" { - t.Errorf("Expected manager name to be 'aqua', but got: %v", managerName) - } - }) - - t.Run("DetectsAsdfInPath", func(t *testing.T) { - // Given asdf is available in system PATH - setup(t) - projectRoot := "/path/to/project" - osStat = func(name string) (os.FileInfo, error) { - return nil, os.ErrNotExist - } - execLookPath = func(name string) (string, error) { - if name == "asdf" { - return "/usr/bin/asdf", nil - } - if name == "aqua" { - return "", exec.ErrNotFound - } - return "", exec.ErrNotFound - } - // When checking for existing tools manager - managerName, err := CheckExistingToolsManager(projectRoot) - // Then asdf should be detected as the tools manager - if err != nil { - t.Errorf("Expected CheckExistingToolsManager to succeed, but got error: %v", err) - } - if managerName != "asdf" { - t.Errorf("Expected manager name to be 'asdf', but got: %v", managerName) - } - }) - - t.Run("PrioritizesAquaOverAsdf", func(t *testing.T) { - // Given both aqua.yaml and .tool-versions exist in project - setup(t) - projectRoot := "/path/to/project" - osStat = func(name string) (os.FileInfo, error) { - if strings.Contains(name, "aqua.yaml") { - return nil, nil - } - if strings.Contains(name, ".tool-versions") { - return nil, nil - } - return nil, os.ErrNotExist - } - // When checking for existing tools manager - managerName, err := CheckExistingToolsManager(projectRoot) - // Then aqua should be selected over asdf - if err != nil { - t.Errorf("Expected CheckExistingToolsManager to succeed, but got error: %v", err) - } - if managerName != "aqua" { - t.Errorf("Expected manager name to be 'aqua', but got: %v", managerName) - } - }) - - t.Run("PrioritizesAquaInPathOverAsdfInPath", func(t *testing.T) { - // Given both aqua and asdf are available in system PATH - setup(t) - projectRoot := "/path/to/project" - osStat = func(name string) (os.FileInfo, error) { - return nil, os.ErrNotExist - } - execLookPath = func(name string) (string, error) { - if name == "aqua" { - return "/usr/bin/aqua", nil - } - if name == "asdf" { - return "/usr/bin/asdf", nil - } - return "", exec.ErrNotFound - } - // When checking for existing tools manager - managerName, err := CheckExistingToolsManager(projectRoot) - // Then aqua should be selected over asdf - if err != nil { - t.Errorf("Expected CheckExistingToolsManager to succeed, but got error: %v", err) - } - if managerName != "aqua" { - t.Errorf("Expected manager name to be 'aqua', but got: %v", managerName) - } - }) -} - // ============================================================================= // Test Helpers // ============================================================================= diff --git a/pkg/workstation/virt/colima_virt.go b/pkg/workstation/virt/colima_virt.go index 55b10339f..e8d774398 100644 --- a/pkg/workstation/virt/colima_virt.go +++ b/pkg/workstation/virt/colima_virt.go @@ -77,11 +77,11 @@ func (v *ColimaVirt) Down() error { return v.executeColimaCommand("delete") } -// GetVMInfo returns the information about the Colima VM +// getVMInfo returns the information about the Colima VM // Retrieves the VM details from the Colima CLI and parses the JSON output // Converts memory and disk values from bytes to gigabytes for easier consumption // Returns a VMInfo struct with the parsed information or an error if retrieval fails -func (v *ColimaVirt) GetVMInfo() (VMInfo, error) { +func (v *ColimaVirt) getVMInfo() (VMInfo, error) { contextName := v.configHandler.GetContext() command := "colima" @@ -215,22 +215,6 @@ func (v *ColimaVirt) WriteConfig() error { return nil } -// PrintInfo prints the information about the Colima VM -// Retrieves the VM information and formats it in a tabular display -// Shows the VM name, architecture, CPU count, memory, disk size, and IP address -// Returns an error if the VM information cannot be retrieved -func (v *ColimaVirt) PrintInfo() error { - info, err := v.GetVMInfo() - if err != nil { - return fmt.Errorf("error retrieving VM info: %w", err) - } - fmt.Printf("%-15s %-10s %-10s %-10s %-10s %-15s\n", "VM NAME", "ARCH", "CPUS", "MEMORY", "DISK", "ADDRESS") - fmt.Printf("%-15s %-10s %-10d %-10s %-10s %-15s\n", info.Name, info.Arch, info.CPUs, fmt.Sprintf("%dGiB", info.Memory), fmt.Sprintf("%dGiB", info.Disk), info.Address) - fmt.Println() - - return nil -} - // ============================================================================= // Private Methods // ============================================================================= @@ -313,7 +297,7 @@ func (v *ColimaVirt) startColima() (VMInfo, error) { var info VMInfo var lastErr error for i := range make([]int, testRetryAttempts) { - info, err = v.GetVMInfo() + info, err = v.getVMInfo() if err != nil { lastErr = fmt.Errorf("Error retrieving Colima info: %w", err) time.Sleep(time.Duration(RETRY_WAIT*(i+1)) * time.Second) diff --git a/pkg/workstation/virt/colima_virt_test.go b/pkg/workstation/virt/colima_virt_test.go index 2edc3db77..f969e2a09 100644 --- a/pkg/workstation/virt/colima_virt_test.go +++ b/pkg/workstation/virt/colima_virt_test.go @@ -491,121 +491,6 @@ func TestColimaVirt_WriteConfig(t *testing.T) { }) } -func TestColimaVirt_GetVMInfo(t *testing.T) { - setup := func(t *testing.T) (*ColimaVirt, *Mocks) { - t.Helper() - mocks := setupColimaMocks(t) - colimaVirt := NewColimaVirt(mocks.Injector) - colimaVirt.setShims(mocks.Shims) - if err := colimaVirt.Initialize(); err != nil { - t.Fatalf("Failed to initialize ColimaVirt: %v", err) - } - return colimaVirt, mocks - } - - t.Run("Success", func(t *testing.T) { - // Given a ColimaVirt with mock components - colimaVirt, mocks := setup(t) - - // And a mock shell that returns VM info - mocks.Shell.ExecSilentFunc = func(command string, args ...string) (string, error) { - if command == "colima" && len(args) >= 4 && args[0] == "ls" && args[1] == "--profile" && args[3] == "--json" { - return `{ - "address": "192.168.1.2", - "arch": "x86_64", - "cpus": 2, - "disk": 64424509440, - "memory": 4294967296, - "name": "windsor-mock-context", - "runtime": "docker", - "status": "Running" - }`, nil - } - return "", fmt.Errorf("unexpected command: %s %v", command, args) - } - - // When calling GetVMInfo - info, err := colimaVirt.GetVMInfo() - - // Then no error should be returned - if err != nil { - t.Errorf("Expected no error, got %v", err) - } - - // And the info should be correctly populated - if info.Address != "192.168.1.2" { - t.Errorf("Expected address '192.168.1.2', got '%s'", info.Address) - } - if info.Arch != "x86_64" { - t.Errorf("Expected arch 'x86_64', got '%s'", info.Arch) - } - if info.CPUs != 2 { - t.Errorf("Expected CPUs 2, got %d", info.CPUs) - } - if info.Disk != 60 { - t.Errorf("Expected disk 60, got %d", info.Disk) - } - if info.Memory != 4 { - t.Errorf("Expected memory 4, got %d", info.Memory) - } - if info.Name != "windsor-mock-context" { - t.Errorf("Expected name 'windsor-mock-context', got '%s'", info.Name) - } - }) - - t.Run("ErrorExecSilent", func(t *testing.T) { - // Given a ColimaVirt with mock components - colimaVirt, mocks := setup(t) - - // Override shell.ExecSilent to return an error - mocks.Shell.ExecSilentFunc = func(command string, args ...string) (string, error) { - t.Logf("ExecSilent called with command: %s, args: %v", command, args) - return "", fmt.Errorf("mock exec silent error") - } - - // When calling GetVMInfo - t.Log("Calling GetVMInfo") - _, err := colimaVirt.GetVMInfo() - t.Logf("GetVMInfo returned error: %v", err) - - // Then an error should be returned - if err == nil { - t.Fatal("Expected error, got nil") - } - if !strings.Contains(err.Error(), "mock exec silent error") { - t.Errorf("Expected error containing 'mock exec silent error', got %v", err) - } - }) - - t.Run("ErrorJsonUnmarshal", func(t *testing.T) { - // Given a ColimaVirt with mock components - colimaVirt, mocks := setup(t) - - // Save original function to restore it in our mock - originalExecSilent := mocks.Shell.ExecSilentFunc - - // Override just the relevant method to return invalid JSON - mocks.Shell.ExecSilentFunc = func(command string, args ...string) (string, error) { - if command == "colima" && len(args) >= 4 && args[0] == "ls" && args[1] == "--profile" && args[3] == "--json" { - return "invalid json", nil - } - // For any other command, use the original implementation - return originalExecSilent(command, args...) - } - - // When calling GetVMInfo - _, err := colimaVirt.GetVMInfo() - - // Then an error should be returned - if err == nil { - t.Fatal("Expected error, got nil") - } - if !strings.Contains(err.Error(), "invalid character") { - t.Errorf("Expected error containing 'invalid character', got %v", err) - } - }) -} - func TestColimaVirt_Up(t *testing.T) { setup := func(t *testing.T) (*ColimaVirt, *Mocks) { t.Helper() @@ -802,60 +687,6 @@ func TestColimaVirt_Down(t *testing.T) { }) } -// TestColimaVirt_PrintInfo tests the PrintInfo method of the ColimaVirt component. -func TestColimaVirt_PrintInfo(t *testing.T) { - setup := func(t *testing.T) (*ColimaVirt, *Mocks) { - t.Helper() - mocks := setupColimaMocks(t) - colimaVirt := NewColimaVirt(mocks.Injector) - colimaVirt.shims = mocks.Shims - if err := colimaVirt.Initialize(); err != nil { - t.Fatalf("Failed to initialize ColimaVirt: %v", err) - } - return colimaVirt, mocks - } - - t.Run("Success", func(t *testing.T) { - // Given a colima virt instance with valid mocks - colimaVirt, _ := setup(t) - - // When calling PrintInfo - err := colimaVirt.PrintInfo() - - // Then no error should occur - if err != nil { - t.Errorf("expected no error, got %v", err) - } - }) - - t.Run("ErrorGettingVMInfo", func(t *testing.T) { - // Given a colima virt instance with valid mocks - colimaVirt, mocks := setup(t) - - // And mock GetVMInfo returns an error - mocks.Shell.ExecSilentFunc = func(command string, args ...string) (string, error) { - if command == "colima" && len(args) > 0 && args[0] == "ls" { - return "", fmt.Errorf("mock error getting VM info") - } - return "", fmt.Errorf("unexpected command: %s %v", command, args) - } - - // When calling PrintInfo - err := colimaVirt.PrintInfo() - - // Then an error should occur - if err == nil { - t.Error("expected error, got none") - } - - // And the error should contain the expected message - expectedErrorSubstring := "error retrieving VM info" - if !strings.Contains(err.Error(), expectedErrorSubstring) { - t.Errorf("expected error message to contain %q, got %q", expectedErrorSubstring, err.Error()) - } - }) -} - // TestColimaVirt_getArch tests the getArch method of the ColimaVirt component. func TestColimaVirt_getArch(t *testing.T) { setup := func(t *testing.T) (*ColimaVirt, *Mocks) { diff --git a/pkg/workstation/virt/docker_virt.go b/pkg/workstation/virt/docker_virt.go index e2733e7bb..46b2ae816 100644 --- a/pkg/workstation/virt/docker_virt.go +++ b/pkg/workstation/virt/docker_virt.go @@ -210,11 +210,11 @@ func (v *DockerVirt) WriteConfig() error { return nil } -// GetContainerInfo retrieves detailed information about Docker containers managed by +// getContainerInfo retrieves detailed information about Docker containers managed by // Windsor, including their names, IP addresses, and labels. It filters containers // by Windsor-managed labels and context, and optionally by service name if provided. // For each container, it retrieves network settings to determine IP addresses. -func (v *DockerVirt) GetContainerInfo(name ...string) ([]ContainerInfo, error) { +func (v *DockerVirt) getContainerInfo(name ...string) ([]ContainerInfo, error) { contextName := v.configHandler.GetContext() command := "docker" @@ -281,31 +281,6 @@ func (v *DockerVirt) GetContainerInfo(name ...string) ([]ContainerInfo, error) { return containerInfos, nil } -// PrintInfo displays a formatted table of running Docker containers with their names, -// IP addresses, and roles. It retrieves container information using GetContainerInfo -// and presents it in a tabular format for easy reading. If no containers are running, -// it displays an appropriate message. -func (v *DockerVirt) PrintInfo() error { - containerInfos, err := v.GetContainerInfo() - if err != nil { - return fmt.Errorf("error retrieving container info: %w", err) - } - - if len(containerInfos) == 0 { - fmt.Println("No Docker containers are currently running.") - return nil - } - - fmt.Printf("%-30s\t%-15s\t%-10s\n", "CONTAINER NAME", "ADDRESS", "ROLE") - for _, info := range containerInfos { - role := info.Labels["role"] - fmt.Printf("%-30s\t%-15s\t%-10s\n", info.Name, info.Address, role) - } - fmt.Println() - - return nil -} - // Ensure DockerVirt implements ContainerRuntime var _ ContainerRuntime = (*DockerVirt)(nil) diff --git a/pkg/workstation/virt/docker_virt_test.go b/pkg/workstation/virt/docker_virt_test.go index affad6123..947d99549 100644 --- a/pkg/workstation/virt/docker_virt_test.go +++ b/pkg/workstation/virt/docker_virt_test.go @@ -7,7 +7,6 @@ package virt import ( "fmt" "os" - "slices" "sort" "strings" "testing" @@ -751,310 +750,6 @@ func TestDockerVirt_Down(t *testing.T) { }) } -// TestDockerVirt_PrintInfo tests the PrintInfo method of the DockerVirt component. -func TestDockerVirt_PrintInfo(t *testing.T) { - setup := func(t *testing.T) (*DockerVirt, *Mocks) { - t.Helper() - mocks := setupDockerMocks(t) - dockerVirt := NewDockerVirt(mocks.Injector) - dockerVirt.shims = mocks.Shims - if err := dockerVirt.Initialize(); err != nil { - t.Fatalf("Failed to initialize DockerVirt: %v", err) - } - return dockerVirt, mocks - } - - t.Run("Success", func(t *testing.T) { - // Given a docker virt instance with valid mocks - dockerVirt, _ := setup(t) - - // When calling PrintInfo - err := dockerVirt.PrintInfo() - - // Then no error should occur - if err != nil { - t.Errorf("expected no error, got %v", err) - } - }) - - t.Run("NoContainersRunning", func(t *testing.T) { - // Given a docker virt instance with valid mocks - dockerVirt, mocks := setup(t) - - // And no containers are running - mocks.Shell.ExecSilentFunc = func(command string, args ...string) (string, error) { - if command == "docker" && len(args) > 0 && args[0] == "ps" { - return "", nil - } - return "", fmt.Errorf("unexpected command: %s %v", command, args) - } - - // When calling PrintInfo - err := dockerVirt.PrintInfo() - - // Then no error should occur - if err != nil { - t.Errorf("expected no error, got %v", err) - } - }) - - t.Run("ErrorGettingContainerInfo", func(t *testing.T) { - // Given a docker virt instance with valid mocks - dockerVirt, mocks := setup(t) - - // And an error occurs when getting container info - mocks.Shell.ExecSilentFunc = func(command string, args ...string) (string, error) { - if command == "docker" && len(args) > 0 && args[0] == "ps" { - return "", fmt.Errorf("mock error getting container info") - } - return "", fmt.Errorf("unexpected command: %s %v", command, args) - } - - // When calling PrintInfo - err := dockerVirt.PrintInfo() - - // Then an error should occur - if err == nil { - t.Errorf("expected error, got none") - } - - // And the error should contain the expected message - expectedErrorSubstring := "error retrieving container info" - if !strings.Contains(err.Error(), expectedErrorSubstring) { - t.Errorf("expected error message to contain %q, got %q", expectedErrorSubstring, err.Error()) - } - }) - - t.Run("ErrorInspectLabels", func(t *testing.T) { - // Given a docker virt instance with failing inspect - dockerVirt, mocks := setup(t) - mocks.Shell.ExecSilentFunc = func(command string, args ...string) (string, error) { - if command == "docker" && len(args) > 0 { - if args[0] == "ps" { - return "container1", nil - } - if args[0] == "inspect" && args[3] == "{{json .Config.Labels}}" { - return "", fmt.Errorf("inspect failed") - } - } - return "", fmt.Errorf("unexpected command: %s %v", command, args) - } - - // When getting container info - _, err := dockerVirt.GetContainerInfo() - - // Then an error should occur - if err == nil { - t.Errorf("expected error, got none") - } - }) - - t.Run("ErrorInspectNetworks", func(t *testing.T) { - // Given a docker virt instance with failing network inspect - dockerVirt, mocks := setup(t) - mocks.Shell.ExecSilentFunc = func(command string, args ...string) (string, error) { - if command == "docker" && len(args) > 0 { - if args[0] == "ps" { - return "container1", nil - } - if args[0] == "inspect" && args[3] == "{{json .Config.Labels}}" { - return `{"managed_by":"windsor","context":"mock-context","com.docker.compose.service":"service1","role":"test"}`, nil - } - if args[0] == "inspect" && args[3] == "{{json .NetworkSettings.Networks}}" { - return "", fmt.Errorf("network inspect failed") - } - } - return "", fmt.Errorf("unexpected command: %s %v", command, args) - } - - // When getting container info - _, err := dockerVirt.GetContainerInfo() - - // Then an error should occur - if err == nil { - t.Errorf("expected error, got none") - } - if !strings.Contains(err.Error(), "error inspecting container networks") { - t.Errorf("expected error about network inspection, got %v", err) - } - }) - - t.Run("ErrorUnmarshalLabels", func(t *testing.T) { - // Given a docker virt instance with invalid JSON labels - dockerVirt, mocks := setup(t) - mocks.Shell.ExecSilentFunc = func(command string, args ...string) (string, error) { - if command == "docker" && len(args) > 0 { - if args[0] == "ps" { - return "container1", nil - } - if args[0] == "inspect" && args[3] == "{{json .Config.Labels}}" { - return "invalid json", nil - } - } - return "", fmt.Errorf("unexpected command: %s %v", command, args) - } - - // When getting container info - _, err := dockerVirt.GetContainerInfo() - - // Then an error should occur - if err == nil { - t.Errorf("expected error, got none") - } - if !strings.Contains(err.Error(), "error unmarshaling container labels") { - t.Errorf("expected error about unmarshaling labels, got %v", err) - } - }) - - t.Run("ErrorUnmarshalNetworks", func(t *testing.T) { - // Given a docker virt instance with invalid JSON networks - dockerVirt, mocks := setup(t) - mocks.Shell.ExecSilentFunc = func(command string, args ...string) (string, error) { - if command == "docker" && len(args) > 0 { - if args[0] == "ps" { - return "container1", nil - } - if args[0] == "inspect" && args[3] == "{{json .Config.Labels}}" { - return `{"managed_by":"windsor","context":"mock-context","com.docker.compose.service":"service1","role":"test"}`, nil - } - if args[0] == "inspect" && args[3] == "{{json .NetworkSettings.Networks}}" { - return "invalid json", nil - } - } - return "", fmt.Errorf("unexpected command: %s %v", command, args) - } - - // When getting container info - _, err := dockerVirt.GetContainerInfo() - - // Then an error should occur - if err == nil { - t.Errorf("expected error, got none") - } - if !strings.Contains(err.Error(), "error unmarshaling container networks") { - t.Errorf("expected error about unmarshaling networks, got %v", err) - } - }) - - t.Run("FilterByServiceName", func(t *testing.T) { - // Given a docker virt instance with valid mocks - dockerVirt, mocks := setup(t) - - // And multiple containers with different service names - mocks.Shell.ExecSilentFunc = func(command string, args ...string) (string, error) { - if command == "docker" && len(args) > 0 { - if args[0] == "ps" { - return "container1\ncontainer2\ncontainer3", nil - } - if args[0] == "inspect" && args[3] == "{{json .Config.Labels}}" { - switch args[1] { - case "container1": - return `{"managed_by":"windsor","context":"mock-context","com.docker.compose.service":"service1","role":"test"}`, nil - case "container2": - return `{"managed_by":"windsor","context":"mock-context","com.docker.compose.service":"service2","role":"test"}`, nil - case "container3": - return `{"managed_by":"windsor","context":"mock-context","com.docker.compose.service":"service3","role":"test"}`, nil - } - } - if args[0] == "inspect" && args[3] == "{{json .NetworkSettings.Networks}}" { - switch args[1] { - case "container1": - return fmt.Sprintf(`{"windsor-%s":{"IPAddress":"192.168.1.2"}}`, mocks.ConfigHandler.GetContext()), nil - case "container2": - return fmt.Sprintf(`{"windsor-%s":{"IPAddress":"192.168.1.3"}}`, mocks.ConfigHandler.GetContext()), nil - case "container3": - return fmt.Sprintf(`{"windsor-%s":{"IPAddress":"192.168.1.4"}}`, mocks.ConfigHandler.GetContext()), nil - } - } - } - return "", fmt.Errorf("unexpected command: %s %v", command, args) - } - - // When getting container info for specific services - info, err := dockerVirt.GetContainerInfo([]string{"service1", "service3"}...) - - // Then no error should occur - if err != nil { - t.Errorf("expected no error, got %v", err) - } - - // And only containers for specified services should be returned - if len(info) != 2 { - t.Errorf("expected 2 containers, got %d", len(info)) - } - - // And the containers should be for the specified services - serviceNames := []string{} - for _, container := range info { - serviceNames = append(serviceNames, container.Name) - } - if !slices.Contains(serviceNames, "service1") { - t.Error("expected container for service1 to be included") - } - if !slices.Contains(serviceNames, "service3") { - t.Error("expected container for service3 to be included") - } - if slices.Contains(serviceNames, "service2") { - t.Error("expected container for service2 to be excluded") - } - }) -} - -// TestDockerVirt_GetContainerInfo tests the GetContainerInfo method of the DockerVirt component. -func TestDockerVirt_GetContainerInfo(t *testing.T) { - setup := func(t *testing.T) (*DockerVirt, *Mocks) { - t.Helper() - mocks := setupDockerMocks(t) - dockerVirt := NewDockerVirt(mocks.Injector) - dockerVirt.shims = mocks.Shims - if err := dockerVirt.Initialize(); err != nil { - t.Fatalf("Failed to initialize DockerVirt: %v", err) - } - return dockerVirt, mocks - } - - t.Run("Success", func(t *testing.T) { - // Given a docker virt instance with valid mocks - dockerVirt, _ := setup(t) - - // When getting container info - info, err := dockerVirt.GetContainerInfo() - - // Then no error should occur - if err != nil { - t.Errorf("expected no error, got %v", err) - } - - // And containers should be returned - if len(info) != 2 { - t.Errorf("expected 2 containers, got %d", len(info)) - } - }) - - t.Run("NoContainersFound", func(t *testing.T) { - // Given a docker virt instance with no containers - dockerVirt, mocks := setup(t) - mocks.Shell.ExecSilentFunc = func(command string, args ...string) (string, error) { - if command == "docker" && len(args) > 0 && args[0] == "ps" { - return "", nil - } - return "", fmt.Errorf("unexpected command: %s %v", command, args) - } - - // When getting container info - info, err := dockerVirt.GetContainerInfo() - - // Then no error should occur - if err != nil { - t.Errorf("expected no error, got %v", err) - } - // And no containers should be returned - if len(info) != 0 { - t.Errorf("expected no containers, got %d", len(info)) - } - }) -} - // TestDockerVirt_WriteConfig tests the WriteConfig method of the DockerVirt component. func TestDockerVirt_WriteConfig(t *testing.T) { setup := func(t *testing.T) (*DockerVirt, *Mocks) { diff --git a/pkg/workstation/virt/mock_virt.go b/pkg/workstation/virt/mock_virt.go index 582a6d743..5482d688b 100644 --- a/pkg/workstation/virt/mock_virt.go +++ b/pkg/workstation/virt/mock_virt.go @@ -11,13 +11,10 @@ package virt // MockVirt is a struct that simulates a virt environment for testing purposes. type MockVirt struct { - InitializeFunc func() error - UpFunc func(verbose ...bool) error - DownFunc func() error - PrintInfoFunc func() error - WriteConfigFunc func() error - GetVMInfoFunc func() (VMInfo, error) - GetContainerInfoFunc func(name ...string) ([]ContainerInfo, error) + InitializeFunc func() error + UpFunc func(verbose ...bool) error + DownFunc func() error + WriteConfigFunc func() error } // ============================================================================= @@ -60,15 +57,6 @@ func (m *MockVirt) Down() error { return nil } -// PrintInfo prints information about the mock virt. -// If a custom PrintInfoFunc is provided, it will use that function instead. -func (m *MockVirt) PrintInfo() error { - if m.PrintInfoFunc != nil { - return m.PrintInfoFunc() - } - return nil -} - // WriteConfig writes the configuration of the mock virt. // If a custom WriteConfigFunc is provided, it will use that function instead. func (m *MockVirt) WriteConfig() error { @@ -78,24 +66,6 @@ func (m *MockVirt) WriteConfig() error { return nil } -// GetVMInfo retrieves information about the mock VM. -// If a custom GetVMInfoFunc is provided, it will use that function instead. -func (m *MockVirt) GetVMInfo() (VMInfo, error) { - if m.GetVMInfoFunc != nil { - return m.GetVMInfoFunc() - } - return VMInfo{}, nil -} - -// GetContainerInfo retrieves information about the mock containers. -// If a custom GetContainerInfoFunc is provided, it will use that function instead. -func (m *MockVirt) GetContainerInfo(name ...string) ([]ContainerInfo, error) { - if m.GetContainerInfoFunc != nil { - return m.GetContainerInfoFunc(name...) - } - return []ContainerInfo{}, nil -} - // ============================================================================= // Interface Compliance // ============================================================================= diff --git a/pkg/workstation/virt/mock_virt_test.go b/pkg/workstation/virt/mock_virt_test.go index 2f137d61b..93da5b734 100644 --- a/pkg/workstation/virt/mock_virt_test.go +++ b/pkg/workstation/virt/mock_virt_test.go @@ -8,10 +8,10 @@ package virt import ( "testing" - "github.com/windsorcli/cli/pkg/runtime/config" "github.com/windsorcli/cli/pkg/di" - "github.com/windsorcli/cli/pkg/workstation/services" + "github.com/windsorcli/cli/pkg/runtime/config" "github.com/windsorcli/cli/pkg/runtime/shell" + "github.com/windsorcli/cli/pkg/workstation/services" ) // ============================================================================= @@ -145,38 +145,6 @@ func TestMockVirt_Down(t *testing.T) { }) } -// TestMockVirt_PrintInfo tests the PrintInfo method of MockVirt. -func TestMockVirt_PrintInfo(t *testing.T) { - t.Run("PrintInfoFuncImplemented", func(t *testing.T) { - // Given a MockVirt with a custom PrintInfoFunc - mockVirt := NewMockVirt() - mockVirt.PrintInfoFunc = func() error { - return nil - } - - // When calling PrintInfo - err := mockVirt.PrintInfo() - - // Then no error should be returned - if err != nil { - t.Fatalf("Expected no error, got %v", err) - } - }) - - t.Run("PrintInfoFuncNotImplemented", func(t *testing.T) { - // Given a MockVirt without a custom PrintInfoFunc - mockVirt := NewMockVirt() - - // When calling PrintInfo - err := mockVirt.PrintInfo() - - // Then no error should be returned - if err != nil { - t.Fatalf("Expected no error, got %v", err) - } - }) -} - // TestMockVirt_WriteConfig tests the WriteConfig method of MockVirt. func TestMockVirt_WriteConfig(t *testing.T) { t.Run("WriteConfigFuncImplemented", func(t *testing.T) { @@ -208,83 +176,3 @@ func TestMockVirt_WriteConfig(t *testing.T) { } }) } - -// TestMockVirt_GetVMInfo tests the GetVMInfo method of MockVirt. -func TestMockVirt_GetVMInfo(t *testing.T) { - t.Run("GetVMInfoFuncImplemented", func(t *testing.T) { - // Given a MockVirt with a custom GetVMInfoFunc - mockVirt := NewMockVirt() - mockVirt.GetVMInfoFunc = func() (VMInfo, error) { - return VMInfo{Address: "192.168.1.1"}, nil - } - - // When calling GetVMInfo - info, err := mockVirt.GetVMInfo() - - // Then no error should be returned - if err != nil { - t.Fatalf("Expected no error, got %v", err) - } - // And the info should be as expected - if info.Address != "192.168.1.1" { - t.Errorf("Expected address '192.168.1.1', got %v", info.Address) - } - }) - - t.Run("GetVMInfoFuncNotImplemented", func(t *testing.T) { - // Given a MockVirt without a custom GetVMInfoFunc - mockVirt := NewMockVirt() - - // When calling GetVMInfo - info, err := mockVirt.GetVMInfo() - - // Then no error should be returned - if err != nil { - t.Fatalf("Expected no error, got %v", err) - } - // And the info should be empty - if info != (VMInfo{}) { - t.Errorf("Expected empty VMInfo, got %v", info) - } - }) -} - -// TestMockVirt_GetContainerInfo tests the GetContainerInfo method of MockVirt. -func TestMockVirt_GetContainerInfo(t *testing.T) { - t.Run("GetContainerInfoFuncImplemented", func(t *testing.T) { - // Given a MockVirt with a custom GetContainerInfoFunc - mockVirt := NewMockVirt() - mockVirt.GetContainerInfoFunc = func(name ...string) ([]ContainerInfo, error) { - return []ContainerInfo{{Name: "container1"}}, nil - } - - // When calling GetContainerInfo - info, err := mockVirt.GetContainerInfo() - - // Then no error should be returned - if err != nil { - t.Fatalf("Expected no error, got %v", err) - } - // And the info should be as expected - if len(info) != 1 || info[0].Name != "container1" { - t.Errorf("Expected container info with name 'container1', got %v", info) - } - }) - - t.Run("GetContainerInfoFuncNotImplemented", func(t *testing.T) { - // Given a MockVirt without a custom GetContainerInfoFunc - mockVirt := NewMockVirt() - - // When calling GetContainerInfo - info, err := mockVirt.GetContainerInfo() - - // Then no error should be returned - if err != nil { - t.Fatalf("Expected no error, got %v", err) - } - // And the info should be an empty list - if len(info) != 0 { - t.Errorf("Expected info to be an empty list, got %v", info) - } - }) -} diff --git a/pkg/workstation/virt/virt.go b/pkg/workstation/virt/virt.go index bfe8a7380..ae2c98303 100644 --- a/pkg/workstation/virt/virt.go +++ b/pkg/workstation/virt/virt.go @@ -10,8 +10,8 @@ import ( "os" - "github.com/windsorcli/cli/pkg/runtime/config" "github.com/windsorcli/cli/pkg/di" + "github.com/windsorcli/cli/pkg/runtime/config" "github.com/windsorcli/cli/pkg/runtime/shell" ) @@ -61,20 +61,17 @@ type Virt interface { Initialize() error Up() error Down() error - PrintInfo() error WriteConfig() error } // VirtualMachine defines methods for VirtualMachine operations type VirtualMachine interface { Virt - GetVMInfo() (VMInfo, error) } // ContainerRuntime defines methods for container operations type ContainerRuntime interface { Virt - GetContainerInfo(name ...string) ([]ContainerInfo, error) } // =============================================================================