| // Licensed to the Apache Software Foundation (ASF) under one |
| // or more contributor license agreements. See the NOTICE file |
| // distributed with this work for additional information |
| // regarding copyright ownership. The ASF licenses this file |
| // to you under the Apache License, Version 2.0 (the |
| // "License"); you may not use this file except in compliance |
| // with the License. You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, |
| // software distributed under the License is distributed on an |
| // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| // KIND, either express or implied. See the License for the |
| // specific language governing permissions and limitations |
| // under the License. |
| |
| package config |
| |
| import ( |
| "encoding/json" |
| "fmt" |
| "os" |
| "path/filepath" |
| "reflect" |
| "testing" |
| |
| "github.com/stretchr/testify/require" |
| ) |
| |
| func TestSaveConfig(t *testing.T) { |
| // Create a temporary directory for testing |
| tempDir := t.TempDir() |
| |
| configMap, err := NewConfigMapBuilder().WithConfigJson(filepath.Join(tempDir, "config.json")).Build() |
| require.NoError(t, err) |
| |
| err = configMap.Insert("key", "value") |
| require.NoError(t, err) |
| |
| err = configMap.SaveConfig() |
| require.NoError(t, err) |
| |
| // Read the saved file |
| savedJSON, err := os.ReadFile(configMap.configPath) |
| require.NoError(t, err) |
| |
| // Unmarshal the JSON |
| var savedConfig map[string]interface{} |
| err = json.Unmarshal(savedJSON, &savedConfig) |
| require.NoError(t, err) |
| |
| // Verify the saved config matches the original config |
| require.Equal(t, configMap.config, savedConfig) |
| } |
| |
| func TestInsert(t *testing.T) { |
| |
| testCases := []struct { |
| name string |
| startingMap ConfigMap |
| key string |
| value string |
| expected ConfigMap |
| err error |
| }{ |
| { |
| name: "empty map", |
| startingMap: ConfigMap{ |
| config: map[string]interface{}{}, |
| }, |
| key: "KEY", |
| value: "value", |
| expected: ConfigMap{ |
| config: map[string]interface{}{ |
| "key": "value", |
| }, |
| }, |
| err: nil, |
| }, |
| { |
| name: "map with key", |
| startingMap: ConfigMap{ |
| config: map[string]interface{}{ |
| "key": "value", |
| }, |
| }, |
| key: "KEY2", |
| value: "value2", |
| expected: ConfigMap{ |
| config: map[string]interface{}{ |
| "key": "value", |
| "key2": "value2", |
| }, |
| }, |
| err: nil, |
| }, |
| { |
| name: "map with nested key", |
| startingMap: ConfigMap{ |
| config: map[string]interface{}{ |
| "key": map[string]interface{}{ |
| "key": "value", |
| }, |
| }, |
| }, |
| key: "KEY_OTHER", |
| value: "value2", |
| expected: ConfigMap{ |
| config: map[string]interface{}{ |
| "key": map[string]interface{}{ |
| "key": "value", |
| "other": "value2", |
| }, |
| }, |
| }, |
| err: nil, |
| }, |
| { |
| name: "existing key is overwritten", |
| startingMap: ConfigMap{ |
| config: map[string]interface{}{ |
| "key": "value", |
| }, |
| }, |
| key: "KEY", |
| value: "value2", |
| expected: ConfigMap{ |
| config: map[string]interface{}{ |
| "key": "value2", |
| }, |
| }, |
| err: nil, |
| }, |
| } |
| |
| for _, tc := range testCases { |
| t.Run(tc.name, func(t *testing.T) { |
| err := tc.startingMap.Insert(tc.key, tc.value) |
| assertExpectedError(t, err, tc.err) |
| |
| if !reflect.DeepEqual(tc.startingMap, tc.expected) { |
| t.Errorf("expected '%v' but got '%v'", tc.expected, tc.startingMap) |
| } |
| }) |
| } |
| } |
| |
| func TestGet(t *testing.T) { |
| testCases := []struct { |
| name string |
| startingMap ConfigMap |
| key string |
| expected string |
| err error |
| }{ |
| { |
| name: "empty maps", |
| startingMap: ConfigMap{ |
| opsRootConfig: map[string]interface{}{}, |
| config: map[string]interface{}{}, |
| }, |
| key: "KEY", |
| expected: "", |
| err: fmt.Errorf("invalid key: '%s' - key does not exist", "KEY"), |
| }, |
| { |
| name: "config map with key", |
| startingMap: ConfigMap{ |
| opsRootConfig: map[string]interface{}{}, |
| config: map[string]interface{}{ |
| "key": "value", |
| }, |
| }, |
| key: "KEY", |
| expected: "value", |
| err: nil, |
| }, |
| { |
| name: "ops root config with key", |
| startingMap: ConfigMap{ |
| opsRootConfig: map[string]interface{}{ |
| "key": "value", |
| }, |
| config: map[string]interface{}{}, |
| }, |
| key: "KEY", |
| expected: "value", |
| err: nil, |
| }, |
| { |
| name: "map with nested key", |
| startingMap: ConfigMap{ |
| config: map[string]interface{}{ |
| "nested": map[string]interface{}{ |
| "key": "value", |
| }, |
| }, |
| }, |
| key: "NESTED_KEY", |
| expected: "value", |
| err: nil, |
| }, |
| { |
| name: "map with both ops root and config keys", |
| startingMap: ConfigMap{ |
| opsRootConfig: map[string]interface{}{ |
| "key": "value", |
| "key2": "value2", |
| }, |
| config: map[string]interface{}{ |
| "key": "value2", |
| "different": "value3", |
| }, |
| }, |
| key: "KEY", |
| expected: "value2", |
| err: nil, |
| }, |
| } |
| |
| for _, tc := range testCases { |
| t.Run(tc.name, func(t *testing.T) { |
| value, err := tc.startingMap.Get(tc.key) |
| assertExpectedError(t, err, tc.err) |
| |
| if value != tc.expected { |
| t.Errorf("expected '%s' but got '%s'", tc.expected, value) |
| } |
| }) |
| } |
| } |
| |
| func TestFlatten(t *testing.T) { |
| |
| testCases := []struct { |
| name string |
| input ConfigMap |
| expected map[string]string |
| }{ |
| { |
| name: "empty map", |
| input: ConfigMap{ |
| config: map[string]interface{}{}, |
| opsRootConfig: map[string]interface{}{}, |
| }, |
| expected: map[string]string{}, |
| }, |
| { |
| name: "one key map", |
| input: ConfigMap{ |
| opsRootConfig: map[string]interface{}{}, |
| config: map[string]interface{}{ |
| "key": "value", |
| }, |
| }, |
| expected: map[string]string{ |
| "KEY": "value", |
| }, |
| }, |
| { |
| name: "nested map", |
| input: ConfigMap{ |
| config: map[string]interface{}{ |
| "key": map[string]interface{}{ |
| "key": "value", |
| }, |
| }, |
| }, |
| expected: map[string]string{ |
| "KEY_KEY": "value", |
| }, |
| }, |
| { |
| name: "nested map with multiple keys", |
| input: ConfigMap{ |
| opsRootConfig: map[string]interface{}{ |
| "different": "value", |
| "key": "value0", |
| }, |
| config: map[string]interface{}{ |
| "key": map[string]interface{}{ |
| "key": "value1", |
| "other": "value2", |
| }, |
| }, |
| }, |
| expected: map[string]string{ |
| "DIFFERENT": "value", |
| "KEY_KEY": "value1", |
| "KEY_OTHER": "value2", |
| }, |
| }, |
| } |
| |
| for _, tc := range testCases { |
| t.Run(tc.name, func(t *testing.T) { |
| flattened := tc.input.Flatten() |
| if !reflect.DeepEqual(flattened, tc.expected) { |
| t.Errorf("expected '%v' but got '%v'", tc.expected, flattened) |
| } |
| }) |
| } |
| } |
| |
| func TestDelete(t *testing.T) { |
| emptyMap := ConfigMap{ |
| opsRootConfig: map[string]interface{}{}, |
| config: map[string]interface{}{}, |
| } |
| testCases := []struct { |
| name string |
| startingMap ConfigMap |
| key string |
| expected ConfigMap |
| err error |
| }{ |
| { |
| name: "empty map", |
| startingMap: emptyMap, |
| key: "KEY", |
| expected: emptyMap, |
| err: fmt.Errorf("invalid key: '%s' - key does not exist in config.json", "KEY"), |
| }, |
| { |
| name: "map with key", |
| startingMap: ConfigMap{ |
| opsRootConfig: map[string]interface{}{}, |
| config: map[string]interface{}{"key": "value"}, |
| }, |
| key: "KEY", |
| expected: emptyMap, |
| err: nil, |
| }, |
| { |
| name: "map with nested key", |
| startingMap: ConfigMap{ |
| opsRootConfig: map[string]interface{}{}, |
| config: map[string]interface{}{ |
| "nested": map[string]interface{}{ |
| "key": "value", |
| }, |
| }, |
| }, |
| key: "NESTED_KEY", |
| expected: ConfigMap{ |
| opsRootConfig: map[string]interface{}{}, |
| config: map[string]interface{}{}, |
| }, |
| err: nil, |
| }, |
| { |
| name: "opsroot is ignored", |
| startingMap: ConfigMap{ |
| opsRootConfig: map[string]interface{}{ |
| "key": "value", |
| }, |
| config: map[string]interface{}{}, |
| }, |
| key: "KEY", |
| expected: ConfigMap{ |
| opsRootConfig: map[string]interface{}{ |
| "key": "value", |
| }, |
| config: map[string]interface{}{}, |
| }, |
| err: fmt.Errorf("invalid key: '%s' - key does not exist in config.json", "KEY"), |
| }, |
| } |
| |
| for _, tc := range testCases { |
| t.Run(tc.name, func(t *testing.T) { |
| err := tc.startingMap.Delete(tc.key) |
| assertExpectedError(t, err, tc.err) |
| |
| if !reflect.DeepEqual(tc.startingMap, tc.expected) { |
| t.Errorf("expected '%v' but got '%v'", tc.expected, tc.startingMap) |
| } |
| }) |
| } |
| |
| } |
| func Test_parseKey(t *testing.T) { |
| testCases := []struct { |
| name string |
| input string |
| want []string |
| err error |
| }{ |
| { |
| name: "Simple Key", |
| input: "foo", |
| want: []string{"foo"}, |
| err: nil, |
| }, |
| { |
| name: "Complex Key", |
| input: "foo_bar", |
| want: []string{"foo", "bar"}, |
| err: nil, |
| }, |
| { |
| name: "Complex Key 2", |
| input: "foo_bar_baz", |
| want: []string{"foo", "bar", "baz"}, |
| err: nil, |
| }, |
| { |
| name: "Invalid Key", |
| input: "foo_bar_baz_", |
| want: nil, |
| err: fmt.Errorf("invalid key: %s", "foo_bar_baz_"), |
| }, |
| { |
| name: "Invalid Key 2", |
| input: "_foo_bar_baz", |
| want: nil, |
| err: fmt.Errorf("invalid key: %s", "_foo_bar_baz"), |
| }} |
| |
| for _, tc := range testCases { |
| t.Run(tc.name, func(t *testing.T) { |
| got, err := parseKey(tc.input) |
| assertExpectedError(t, err, tc.err) |
| if !reflect.DeepEqual(tc.want, got) { |
| t.Errorf("Expected %v, got %v", tc.want, got) |
| } |
| }) |
| } |
| } |
| |
| func Test_parseValue(t *testing.T) { |
| testCases := []struct { |
| name string |
| input string |
| want interface{} |
| err error |
| }{ |
| { |
| name: "String", |
| input: "foo", |
| want: "foo", |
| err: nil, |
| }, |
| { |
| name: "Complex String", |
| input: "Another foo bar", |
| want: "Another foo bar", |
| err: nil, |
| }, |
| { |
| name: "Number", |
| input: "123.456", |
| want: 123.456, |
| err: nil, |
| }, |
| { |
| name: "Boolean True", |
| input: "true", |
| want: true, |
| err: nil, |
| }, |
| { |
| name: "Boolean False", |
| input: "false", |
| want: false, |
| err: nil, |
| }, |
| { |
| name: "Null", |
| input: "null", |
| want: nil, |
| err: nil, |
| }, |
| { |
| name: "JSON", |
| input: `{"foo": "bar"}`, |
| want: map[string]interface{}{"foo": "bar"}, |
| err: nil, |
| }, |
| } |
| |
| for _, tc := range testCases { |
| t.Run(tc.name, func(t *testing.T) { |
| got, err := parseValue(tc.input) |
| assertExpectedError(t, err, tc.err) |
| if !reflect.DeepEqual(tc.want, got) { |
| t.Errorf("Expected %v, got %v", tc.want, got) |
| } |
| }) |
| } |
| } |
| |
| func assertExpectedError(t *testing.T, err error, expected error) { |
| t.Helper() |
| if expected != nil { |
| if err == nil { |
| t.Fatalf("expected error '%s' but got nil", expected) |
| } |
| if err.Error() != expected.Error() { |
| t.Errorf("expected error '%s' but got '%s'", expected, err) |
| } |
| } |
| |
| if expected == nil && err != nil { |
| t.Errorf("expected no error but got '%s'", err) |
| } |
| } |
| |
| func Test_mergeMaps(t *testing.T) { |
| testCases := []struct { |
| name string |
| m1 map[string]interface{} |
| m2 map[string]interface{} |
| expected map[string]interface{} |
| }{ |
| { |
| name: "m1 empty", |
| m1: map[string]interface{}{}, |
| m2: map[string]interface{}{ |
| "test": "test", |
| }, |
| expected: map[string]interface{}{ |
| "test": "test", |
| }, |
| }, |
| { |
| name: "m2 empty", |
| m1: map[string]interface{}{ |
| "test": "test", |
| }, |
| m2: map[string]interface{}{}, |
| expected: map[string]interface{}{ |
| "test": "test", |
| }, |
| }, |
| { |
| name: "m1 and m2 not empty", |
| m1: map[string]interface{}{ |
| "test": "test", |
| }, |
| m2: map[string]interface{}{ |
| "test2": "test2", |
| }, |
| expected: map[string]interface{}{ |
| "test": "test", |
| "test2": "test2", |
| }, |
| }, |
| { |
| name: "m1 and m2 not empty with same key", |
| m1: map[string]interface{}{ |
| "test": "test", |
| }, |
| m2: map[string]interface{}{ |
| "test": "test2", |
| }, |
| expected: map[string]interface{}{ |
| "test": "test2", |
| }, |
| }, |
| { |
| name: "m1 and m2 not empty with same key and nested map", |
| m1: map[string]interface{}{ |
| "test": map[string]interface{}{ |
| "test": "test", |
| }, |
| }, |
| m2: map[string]interface{}{ |
| "test": map[string]interface{}{ |
| "test2": "test2", |
| }, |
| }, |
| expected: map[string]interface{}{ |
| "test": map[string]interface{}{ |
| "test": "test", |
| "test2": "test2", |
| }, |
| }, |
| }, |
| } |
| |
| for _, tc := range testCases { |
| t.Run(tc.name, func(t *testing.T) { |
| result := mergeMaps(tc.m1, tc.m2) |
| if !reflect.DeepEqual(result, tc.expected) { |
| t.Errorf("expected: %v, got: %v", tc.expected, result) |
| } |
| }) |
| } |
| } |