Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions pkg/blueprint/blueprint_handler_helper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ func (m *mockConfigHandler) SetContext(context string) error
func (m *mockConfigHandler) GetConfigRoot() (string, error) { return "/tmp", nil }
func (m *mockConfigHandler) Clean() error { return nil }
func (m *mockConfigHandler) IsLoaded() bool { return true }
func (m *mockConfigHandler) IsContextConfigLoaded() bool { return true }
func (m *mockConfigHandler) SetSecretsProvider(provider secrets.SecretsProvider) {}
func (m *mockConfigHandler) GenerateContextID() error { return nil }
func (m *mockConfigHandler) YamlMarshalWithDefinedPaths(v any) ([]byte, error) { return nil, nil }
Expand Down
1 change: 1 addition & 0 deletions pkg/config/config_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ type ConfigHandler interface {
GetConfigRoot() (string, error)
Clean() error
IsLoaded() bool
IsContextConfigLoaded() bool
SetSecretsProvider(provider secrets.SecretsProvider)
GenerateContextID() error
YamlMarshalWithDefinedPaths(v any) ([]byte, error)
Expand Down
214 changes: 214 additions & 0 deletions pkg/config/config_handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"testing"

"github.com/windsorcli/cli/api/v1alpha1"
"github.com/windsorcli/cli/api/v1alpha1/cluster"
"github.com/windsorcli/cli/pkg/di"
"github.com/windsorcli/cli/pkg/secrets"
"github.com/windsorcli/cli/pkg/shell"
Expand Down Expand Up @@ -190,6 +191,219 @@ func TestConfigHandler_IsLoaded(t *testing.T) {
})
}

// TestConfigHandler_IsContextConfigLoaded tests the IsContextConfigLoaded method of the ConfigHandler
func TestConfigHandler_IsContextConfigLoaded(t *testing.T) {
setup := func(t *testing.T) (*YamlConfigHandler, *Mocks) {
mocks := setupMocks(t)
handler := NewYamlConfigHandler(mocks.Injector)
handler.shims = mocks.Shims
return handler, mocks
}

t.Run("ReturnsFalseWhenBaseConfigNotLoaded", func(t *testing.T) {
// Given a YamlConfigHandler with base config not loaded
handler, _ := setup(t)
handler.BaseConfigHandler.loaded = false

// When IsContextConfigLoaded is called
isLoaded := handler.IsContextConfigLoaded()

// Then it should return false
if isLoaded {
t.Errorf("expected IsContextConfigLoaded to return false when base config not loaded, got true")
}
})

t.Run("ReturnsFalseWhenContextNotSet", func(t *testing.T) {
// Given a YamlConfigHandler with base config loaded but no context set
handler, mocks := setup(t)
handler.BaseConfigHandler.loaded = true

// Mock GetContext to return empty string
mocks.Shell.GetProjectRootFunc = func() (string, error) {
return "/mock/project/root", nil
}
mocks.Shims.ReadFile = func(filename string) ([]byte, error) {
return []byte(""), nil // Empty context
}
mocks.Shims.Getenv = func(key string) string {
return "" // No environment variable
}
handler.Initialize()

// When IsContextConfigLoaded is called
isLoaded := handler.IsContextConfigLoaded()

// Then it should return false
if isLoaded {
t.Errorf("expected IsContextConfigLoaded to return false when context not set, got true")
}
})

t.Run("ReturnsFalseWhenContextsMapIsNil", func(t *testing.T) {
// Given a YamlConfigHandler with base config loaded and context set, but no contexts map
handler, mocks := setup(t)
handler.BaseConfigHandler.loaded = true
handler.config = v1alpha1.Config{
Contexts: nil, // No contexts map
}

// Mock GetContext to return a context name
mocks.Shell.GetProjectRootFunc = func() (string, error) {
return "/mock/project/root", nil
}
mocks.Shims.ReadFile = func(filename string) ([]byte, error) {
return []byte("test-context"), nil
}
mocks.Shims.Getenv = func(key string) string {
return ""
}
handler.Initialize()

// When IsContextConfigLoaded is called
isLoaded := handler.IsContextConfigLoaded()

// Then it should return false
if isLoaded {
t.Errorf("expected IsContextConfigLoaded to return false when contexts map is nil, got true")
}
})

t.Run("ReturnsFalseWhenContextDoesNotExist", func(t *testing.T) {
// Given a YamlConfigHandler with base config loaded, context set, but context doesn't exist in map
handler, mocks := setup(t)
handler.BaseConfigHandler.loaded = true
handler.config = v1alpha1.Config{
Contexts: map[string]*v1alpha1.Context{
"other-context": {
// Empty context but valid
},
},
}

// Mock GetContext to return a context name that doesn't exist
mocks.Shell.GetProjectRootFunc = func() (string, error) {
return "/mock/project/root", nil
}
mocks.Shims.ReadFile = func(filename string) ([]byte, error) {
return []byte("test-context"), nil
}
mocks.Shims.Getenv = func(key string) string {
return ""
}
handler.Initialize()

// When IsContextConfigLoaded is called
isLoaded := handler.IsContextConfigLoaded()

// Then it should return false
if isLoaded {
t.Errorf("expected IsContextConfigLoaded to return false when context doesn't exist, got true")
}
})

t.Run("ReturnsFalseWhenContextExistsButIsNil", func(t *testing.T) {
// Given a YamlConfigHandler with base config loaded, context set, but context value is nil
handler, mocks := setup(t)
handler.BaseConfigHandler.loaded = true
handler.config = v1alpha1.Config{
Contexts: map[string]*v1alpha1.Context{
"test-context": nil, // Context exists but is nil
},
}

// Mock GetContext to return the context name
mocks.Shell.GetProjectRootFunc = func() (string, error) {
return "/mock/project/root", nil
}
mocks.Shims.ReadFile = func(filename string) ([]byte, error) {
return []byte("test-context"), nil
}
mocks.Shims.Getenv = func(key string) string {
return ""
}
handler.Initialize()

// When IsContextConfigLoaded is called
isLoaded := handler.IsContextConfigLoaded()

// Then it should return false
if isLoaded {
t.Errorf("expected IsContextConfigLoaded to return false when context exists but is nil, got true")
}
})

t.Run("ReturnsTrueWhenContextExistsAndIsValid", func(t *testing.T) {
// Given a YamlConfigHandler with base config loaded, context set, and valid context config
handler, mocks := setup(t)
handler.BaseConfigHandler.loaded = true
handler.config = v1alpha1.Config{
Contexts: map[string]*v1alpha1.Context{
"test-context": {
Cluster: &cluster.ClusterConfig{
Workers: cluster.NodeGroupConfig{
Volumes: []string{"/var/blah"},
},
},
},
},
}

// Mock GetContext to return the context name
mocks.Shell.GetProjectRootFunc = func() (string, error) {
return "/mock/project/root", nil
}
mocks.Shims.ReadFile = func(filename string) ([]byte, error) {
return []byte("test-context"), nil
}
mocks.Shims.Getenv = func(key string) string {
return ""
}
handler.Initialize()

// When IsContextConfigLoaded is called
isLoaded := handler.IsContextConfigLoaded()

// Then it should return true
if !isLoaded {
t.Errorf("expected IsContextConfigLoaded to return true when context exists and is valid, got false")
}
})

t.Run("ReturnsTrueWhenContextExistsAndHasEmptyConfig", func(t *testing.T) {
// Given a YamlConfigHandler with base config loaded, context set, and context config exists but is empty
handler, mocks := setup(t)
handler.BaseConfigHandler.loaded = true
handler.config = v1alpha1.Config{
Contexts: map[string]*v1alpha1.Context{
"test-context": {
// Empty config but still valid
},
},
}

// Mock GetContext to return the context name
mocks.Shell.GetProjectRootFunc = func() (string, error) {
return "/mock/project/root", nil
}
mocks.Shims.ReadFile = func(filename string) ([]byte, error) {
return []byte("test-context"), nil
}
mocks.Shims.Getenv = func(key string) string {
return ""
}
handler.Initialize()

// When IsContextConfigLoaded is called
isLoaded := handler.IsContextConfigLoaded()

// Then it should return true (empty config is still valid)
if !isLoaded {
t.Errorf("expected IsContextConfigLoaded to return true when context exists with empty config, got false")
}
})
}

// TestBaseConfigHandler_GetContext tests the GetContext method of the BaseConfigHandler
func TestBaseConfigHandler_GetContext(t *testing.T) {
setup := func(t *testing.T) (*BaseConfigHandler, *Mocks) {
Expand Down
9 changes: 9 additions & 0 deletions pkg/config/mock_config_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ type MockConfigHandler struct {
GetStringFunc func(key string, defaultValue ...string) string
GetIntFunc func(key string, defaultValue ...int) int
GetBoolFunc func(key string, defaultValue ...bool) bool
IsContextConfigLoadedFunc func() bool
GetStringSliceFunc func(key string, defaultValue ...[]string) []string
GetStringMapFunc func(key string, defaultValue ...map[string]string) map[string]string
SetFunc func(key string, value any) error
Expand Down Expand Up @@ -85,6 +86,14 @@ func (m *MockConfigHandler) IsLoaded() bool {
return false
}

// IsContextConfigLoaded calls the mock IsContextConfigLoadedFunc if set, otherwise returns false
func (m *MockConfigHandler) IsContextConfigLoaded() bool {
if m.IsContextConfigLoadedFunc != nil {
return m.IsContextConfigLoadedFunc()
}
return false
}

// GetString calls the mock GetStringFunc if set, otherwise returns a reasonable default string
func (m *MockConfigHandler) GetString(key string, defaultValue ...string) string {
if m.GetStringFunc != nil {
Expand Down
29 changes: 29 additions & 0 deletions pkg/config/mock_config_handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -642,6 +642,35 @@ func TestMockConfigHandler_IsLoaded(t *testing.T) {
})
}

func TestMockConfigHandler_IsContextConfigLoaded(t *testing.T) {
t.Run("WithFuncSet", func(t *testing.T) {
// Given a new mock config handler with IsContextConfigLoadedFunc set
handler := NewMockConfigHandler()
handler.IsContextConfigLoadedFunc = func() bool { return true }

// When IsContextConfigLoaded is called
loaded := handler.IsContextConfigLoaded()

// Then the returned value should be true
if !loaded {
t.Errorf("Expected IsContextConfigLoaded to return true, got %v", loaded)
}
})

t.Run("WithNoFuncSet", func(t *testing.T) {
// Given a new mock config handler without IsContextConfigLoadedFunc set
handler := NewMockConfigHandler()

// When IsContextConfigLoaded is called
loaded := handler.IsContextConfigLoaded()

// Then the returned value should be false
if loaded {
t.Errorf("Expected IsContextConfigLoaded to return false, got %v", loaded)
}
})
}

func TestMockConfigHandler_LoadConfigString(t *testing.T) {
t.Run("WithFuncSet", func(t *testing.T) {
// Given a mock config handler with LoadConfigStringFunc set
Expand Down
21 changes: 21 additions & 0 deletions pkg/config/yaml_config_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,27 @@ func (y *YamlConfigHandler) LoadContextConfig() error {
return nil
}

// IsContextConfigLoaded determines whether context-specific configuration has been loaded for the current context.
// It returns true if the base configuration is loaded, the current context name is set, and a non-nil context
// configuration exists for the current context in the configuration map. Returns false otherwise.
func (y *YamlConfigHandler) IsContextConfigLoaded() bool {
if !y.BaseConfigHandler.loaded {
return false
}

contextName := y.GetContext()
if contextName == "" {
return false
}

if y.config.Contexts == nil {
return false
}

context, exists := y.config.Contexts[contextName]
return exists && context != nil
}

// SaveConfig writes configuration to root windsor.yaml and the current context's windsor.yaml.
// Root windsor.yaml contains only the version field. The context file contains the context config
// without the contexts wrapper. Files are created only if absent: root windsor.yaml is created if
Expand Down
2 changes: 1 addition & 1 deletion pkg/pipelines/check_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -281,7 +281,7 @@ func TestCheckPipeline_Initialize(t *testing.T) {
if err == nil {
t.Fatal("Expected error, got nil")
}
if err.Error() != "failed to load config: error loading config file: config loading failed" {
if err.Error() != "failed to load base config: error loading config file: config loading failed" {
t.Errorf("Expected config loading error, got: %v", err)
}
})
Expand Down
2 changes: 1 addition & 1 deletion pkg/pipelines/env_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ func TestEnvPipeline_Initialize(t *testing.T) {
if err == nil {
t.Fatal("Expected error, got nil")
}
if err.Error() != "failed to load config: error retrieving project root: project root error" {
if err.Error() != "failed to load base config: error retrieving project root: project root error" {
t.Errorf("Expected load config error, got: %v", err)
}
})
Expand Down
8 changes: 4 additions & 4 deletions pkg/pipelines/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,10 @@ func (p *InitPipeline) Initialize(injector di.Injector, ctx context.Context) err
return fmt.Errorf("Error setting context value: %w", err)
}

if err := p.setDefaultConfiguration(ctx, contextName); err != nil {
return err
if !p.configHandler.IsContextConfigLoaded() {
if err := p.setDefaultConfiguration(ctx, contextName); err != nil {
return err
}
}

if err := p.processPlatformConfiguration(ctx); err != nil {
Expand Down Expand Up @@ -478,8 +480,6 @@ func (p *InitPipeline) prepareTemplateData(ctx context.Context) (map[string][]by
return make(map[string][]byte), nil
}



// processTemplateData renders and processes template data for the InitPipeline.
// Renders all templates using the template renderer, and loads blueprint data from the rendered output if present.
// Returns the rendered template data map or an error if rendering or blueprint loading fails.
Expand Down
Loading
Loading