diff --git a/pkg/config/config_handler.go b/pkg/config/config_handler.go index bd082bb67..73b96aec6 100644 --- a/pkg/config/config_handler.go +++ b/pkg/config/config_handler.go @@ -24,6 +24,9 @@ type ConfigHandler interface { // LoadConfig loads the configuration from the specified path LoadConfig(path string) error + // LoadConfigString loads the configuration from the provided string content + LoadConfigString(content string) error + // GetString retrieves a string value for the specified key from the configuration GetString(key string, defaultValue ...string) string @@ -78,7 +81,6 @@ type ConfigHandler interface { // BaseConfigHandler is a base implementation of the ConfigHandler interface type BaseConfigHandler struct { - ConfigHandler injector di.Injector shell shell.Shell config v1alpha1.Config diff --git a/pkg/config/mock_config_handler.go b/pkg/config/mock_config_handler.go index 86257f399..d9416060f 100644 --- a/pkg/config/mock_config_handler.go +++ b/pkg/config/mock_config_handler.go @@ -10,6 +10,7 @@ type MockConfigHandler struct { InitializeFunc func() error SetSecretsProviderFunc func(provider secrets.SecretsProvider) LoadConfigFunc func(path string) error + LoadConfigStringFunc func(content string) error IsLoadedFunc func() bool GetStringFunc func(key string, defaultValue ...string) string GetIntFunc func(key string, defaultValue ...int) int @@ -56,6 +57,14 @@ func (m *MockConfigHandler) LoadConfig(path string) error { return nil } +// LoadConfigString calls the mock LoadConfigStringFunc if set, otherwise returns nil +func (m *MockConfigHandler) LoadConfigString(content string) error { + if m.LoadConfigStringFunc != nil { + return m.LoadConfigStringFunc(content) + } + return nil +} + // IsLoaded calls the mock IsLoadedFunc if set, otherwise returns false func (m *MockConfigHandler) IsLoaded() bool { if m.IsLoadedFunc != nil { diff --git a/pkg/config/mock_config_handler_test.go b/pkg/config/mock_config_handler_test.go index f41a78343..e6a57e15e 100644 --- a/pkg/config/mock_config_handler_test.go +++ b/pkg/config/mock_config_handler_test.go @@ -6,6 +6,7 @@ import ( "reflect" "testing" + "github.com/goccy/go-yaml" "github.com/windsorcli/cli/api/v1alpha1" "github.com/windsorcli/cli/pkg/di" "github.com/windsorcli/cli/pkg/secrets" @@ -29,6 +30,9 @@ func setupSafeMocks(injector ...di.Injector) *MockObjects { // Mock necessary dependencies mockShell := &shell.MockShell{} + mockShell.GetProjectRootFunc = func() (string, error) { + return "/tmp", nil + } inj.Register("shell", mockShell) // Mock secrets provider @@ -40,10 +44,8 @@ func setupSafeMocks(injector ...di.Injector) *MockObjects { return nil, nil } - // Mock yamlUnmarshal to return an error - yamlUnmarshal = func(data []byte, v interface{}) error { - return nil - } + // Use real YAML unmarshaling + yamlUnmarshal = yaml.Unmarshal // Mock osReadFile to simulate reading a file osReadFile = func(filename string) ([]byte, error) { @@ -726,3 +728,33 @@ func TestMockConfigHandler_IsLoaded(t *testing.T) { } }) } + +func TestMockConfigHandler_LoadConfigString(t *testing.T) { + t.Run("WithFuncSet", func(t *testing.T) { + // Given a mock config handler with LoadConfigStringFunc set + handler := NewMockConfigHandler() + mockErr := fmt.Errorf("mock load config string error") + handler.LoadConfigStringFunc = func(content string) error { return mockErr } + + // When LoadConfigString is called + err := handler.LoadConfigString("some content") + + // Then the error should match the expected mock error + if err != mockErr { + t.Errorf("Expected error = %v, got = %v", mockErr, err) + } + }) + + t.Run("WithNoFuncSet", func(t *testing.T) { + // Given a mock config handler without LoadConfigStringFunc set + handler := NewMockConfigHandler() + + // When LoadConfigString is called + err := handler.LoadConfigString("some content") + + // Then no error should be returned + if err != nil { + t.Errorf("Expected error = %v, got = %v", nil, err) + } + }) +} diff --git a/pkg/config/yaml_config_handler.go b/pkg/config/yaml_config_handler.go index 47770cd70..8bfdb2d91 100644 --- a/pkg/config/yaml_config_handler.go +++ b/pkg/config/yaml_config_handler.go @@ -14,7 +14,6 @@ import ( // YamlConfigHandler implements the ConfigHandler interface using goccy/go-yaml type YamlConfigHandler struct { BaseConfigHandler - config v1alpha1.Config path string defaultContextConfig v1alpha1.Context } @@ -28,6 +27,28 @@ func NewYamlConfigHandler(injector di.Injector) *YamlConfigHandler { } } +// LoadConfigString loads the configuration from the provided string content. +func (y *YamlConfigHandler) LoadConfigString(content string) error { + if content == "" { + return nil + } + + if err := yamlUnmarshal([]byte(content), &y.BaseConfigHandler.config); err != nil { + return fmt.Errorf("error unmarshalling yaml: %w", err) + } + + // Check and set the config version + if y.BaseConfigHandler.config.Version == "" { + y.BaseConfigHandler.config.Version = "v1alpha1" + } else if y.BaseConfigHandler.config.Version != "v1alpha1" { + return fmt.Errorf("unsupported config version: %s", y.BaseConfigHandler.config.Version) + } + + y.BaseConfigHandler.loaded = true + + return nil +} + // LoadConfig loads the configuration from the specified path. If the file does not exist, it does nothing. func (y *YamlConfigHandler) LoadConfig(path string) error { y.path = path @@ -40,20 +61,7 @@ func (y *YamlConfigHandler) LoadConfig(path string) error { return fmt.Errorf("error reading config file: %w", err) } - if err := yamlUnmarshal(data, &y.config); err != nil { - return fmt.Errorf("error unmarshalling yaml: %w", err) - } - - // Check and set the config version - if y.config.Version == "" { - y.config.Version = "v1alpha1" - } else if y.config.Version != "v1alpha1" { - return fmt.Errorf("unsupported config version: %s", y.config.Version) - } - - y.loaded = true - - return nil + return y.LoadConfigString(string(data)) } // SaveConfig saves the current configuration to the specified path. If the path is empty, it uses the previously loaded path. diff --git a/pkg/config/yaml_config_handler_test.go b/pkg/config/yaml_config_handler_test.go index c2034b659..2783d418d 100644 --- a/pkg/config/yaml_config_handler_test.go +++ b/pkg/config/yaml_config_handler_test.go @@ -1672,3 +1672,75 @@ func TestAssignValue(t *testing.T) { } }) } + +func TestYamlConfigHandler_LoadConfigString(t *testing.T) { + t.Run("Success", func(t *testing.T) { + mocks := setupSafeMocks() + handler := NewYamlConfigHandler(mocks.Injector) + handler.Initialize() + handler.SetContext("test") + + yamlContent := ` +version: v1alpha1 +contexts: + test: + environment: + TEST_VAR: test_value` + + err := handler.LoadConfigString(yamlContent) + if err != nil { + t.Fatalf("LoadConfigString() unexpected error: %v", err) + } + + value := handler.GetString("environment.TEST_VAR") + if value != "test_value" { + t.Errorf("Expected TEST_VAR = 'test_value', got '%s'", value) + } + }) + + t.Run("EmptyContent", func(t *testing.T) { + mocks := setupSafeMocks() + handler := NewYamlConfigHandler(mocks.Injector) + handler.Initialize() + + err := handler.LoadConfigString("") + if err != nil { + t.Fatalf("LoadConfigString() unexpected error: %v", err) + } + }) + + t.Run("InvalidYAML", func(t *testing.T) { + mocks := setupSafeMocks() + handler := NewYamlConfigHandler(mocks.Injector) + handler.Initialize() + + yamlContent := `invalid: yaml: content: [}` + + err := handler.LoadConfigString(yamlContent) + if err == nil { + t.Fatal("LoadConfigString() expected error for invalid YAML") + } + if !strings.Contains(err.Error(), "error unmarshalling yaml") { + t.Errorf("Expected error about invalid YAML, got: %v", err) + } + }) + + t.Run("UnsupportedVersion", func(t *testing.T) { + mocks := setupSafeMocks() + handler := NewYamlConfigHandler(mocks.Injector) + handler.Initialize() + + yamlContent := ` +version: v2alpha1 +contexts: + test: {}` + + err := handler.LoadConfigString(yamlContent) + if err == nil { + t.Fatal("LoadConfigString() expected error for unsupported version") + } + if !strings.Contains(err.Error(), "unsupported config version") { + t.Errorf("Expected error about unsupported version, got: %v", err) + } + }) +}