From eada69c58a9c6918a00aa496a5c313c2ff0e52cb Mon Sep 17 00:00:00 2001 From: Oliver O'Mahony Date: Thu, 12 Dec 2024 14:33:33 +0000 Subject: [PATCH] added tests --- internal/config/config.go | 70 ++++++-- internal/config/config_test.go | 311 ++++++++++++++++++++++++++++++++- internal/config/defaults.go | 2 +- internal/config/types.go | 2 +- 4 files changed, 365 insertions(+), 20 deletions(-) diff --git a/internal/config/config.go b/internal/config/config.go index a220b3aa4..e35a60b78 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -7,6 +7,7 @@ package config import ( "context" + "encoding/json" "errors" "fmt" "log/slog" @@ -422,31 +423,66 @@ func resolveLabels() map[string]interface{} { result := make(map[string]interface{}) for key, value := range input { - // Try to parse as an integer - if intValue, err := strconv.Atoi(value); err == nil { - result[key] = intValue - continue - } + trimmedValue := strings.TrimSpace(value) - // Try to parse as a float - if floatValue, err := strconv.ParseFloat(value, 64); err == nil { - result[key] = floatValue - continue - } + switch { + case trimmedValue == "" || trimmedValue == "nil": // Handle empty values as nil + result[key] = nil - // Try to parse as a boolean - if boolValue, err := strconv.ParseBool(value); err == nil { - result[key] = boolValue - continue - } + case parseInt(trimmedValue) != nil: // Integer + result[key] = parseInt(trimmedValue) + + case parseFloat(trimmedValue) != nil: // Float + result[key] = parseFloat(trimmedValue) - // If no conversion was possible, keep it as a string - result[key] = value + case parseBool(trimmedValue) != nil: // Boolean + result[key] = parseBool(trimmedValue) + + case parseJSON(trimmedValue) != nil: // JSON object/array + result[key] = parseJSON(trimmedValue) + + default: // String + result[key] = trimmedValue + } } return result } +// Parsing helper functions return the parsed value or nil if parsing fails +func parseInt(value string) interface{} { + if intValue, err := strconv.Atoi(value); err == nil { + return intValue + } + + return nil +} + +func parseFloat(value string) interface{} { + if floatValue, err := strconv.ParseFloat(value, 64); err == nil { + return floatValue + } + + return nil +} + +func parseBool(value string) interface{} { + if boolValue, err := strconv.ParseBool(value); err == nil { + return boolValue + } + + return nil +} + +func parseJSON(value string) interface{} { + var jsonValue interface{} + if err := json.Unmarshal([]byte(value), &jsonValue); err == nil { + return jsonValue + } + + return nil +} + func resolveDataPlaneConfig() *DataPlaneConfig { return &DataPlaneConfig{ Nginx: &NginxDataPlaneConfig{ diff --git a/internal/config/config_test.go b/internal/config/config_test.go index 5c3badd21..92d21dcf6 100644 --- a/internal/config/config_test.go +++ b/internal/config/config_test.go @@ -7,6 +7,7 @@ package config import ( "os" "path" + "strings" "testing" "time" @@ -107,7 +108,7 @@ func TestRegisterFlags(t *testing.T) { assert.Equal(t, "warn", viperInstance.GetString(LogLevelKey)) assert.Equal(t, "/var/log/test/agent.log", viperInstance.GetString(LogPathKey)) assert.Equal(t, 10*time.Second, viperInstance.GetDuration(ClientTimeoutKey)) - assert.Equal(t, map[string]string{}, viperInstance.GetStringMapString(LabelsRootKey)) + assert.Equal(t, make(map[string]string), viperInstance.GetStringMapString(LabelsRootKey)) } func TestSeekFileInPaths(t *testing.T) { @@ -288,6 +289,313 @@ func TestClient(t *testing.T) { assert.Equal(t, expected, result) } +func TestResolveLabels(t *testing.T) { + // Helper to set up the viper instance + setupViper := func(input map[string]string) { + viperInstance = viper.New() // Create a new viper instance for isolation + viperInstance.Set(LabelsRootKey, input) + } + + tests := []struct { + input map[string]string + expected map[string]interface{} + name string + }{ + { + name: "Test 1: Integer values", + input: map[string]string{ + "key1": "123", + "key2": "456", + }, + expected: map[string]interface{}{ + "key1": 123, + "key2": 456, + }, + }, + { + name: "Test 2: Float values", + input: map[string]string{ + "key1": "123.45", + "key2": "678.90", + }, + expected: map[string]interface{}{ + "key1": 123.45, + "key2": 678.9, + }, + }, + { + name: "Test 3: Boolean values", + input: map[string]string{ + "key1": "true", + "key2": "false", + }, + expected: map[string]interface{}{ + "key1": true, + "key2": false, + }, + }, + { + name: "Test 4: Mixed types", + input: map[string]string{ + "key1": "true", + "key2": "123", + "key3": "45.67", + "key4": "hello", + }, + expected: map[string]interface{}{ + "key1": true, + "key2": 123, + "key3": 45.67, + "key4": "hello", + }, + }, + { + name: "Test 5: String values", + input: map[string]string{ + "key1": "hello", + "key2": "world", + }, + expected: map[string]interface{}{ + "key1": "hello", + "key2": "world", + }, + }, + { + name: "Test 6: Empty input", + input: make(map[string]string), + expected: make(map[string]interface{}), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Setup viper with test input + setupViper(tt.input) + + // Call the function + actual := resolveLabels() + + // Assert the results + assert.Equal(t, tt.expected, actual) + }) + } +} + +func TestResolveLabelsWithYAML(t *testing.T) { + tests := []struct { + expected map[string]interface{} + name string + yamlInput string + }{ + { + name: "Test 1: Integer and Float Values", + yamlInput: ` +labels: + key1: "123" + key2: "45.67" +`, + expected: map[string]interface{}{ + "key1": 123, + "key2": 45.67, + }, + }, + { + name: "Test 2: Boolean Values", + yamlInput: ` +labels: + key1: "true" + key2: "false" +`, + expected: map[string]interface{}{ + "key1": true, + "key2": false, + }, + }, + { + name: "Test 3: Nil and Empty Values", + yamlInput: ` +labels: + key1: "nil" + key2: "" +`, + expected: map[string]interface{}{ + "key1": nil, + "key2": nil, + }, + }, + { + name: "Test 4: Array Values", + yamlInput: ` +labels: + key1: "[1, 2, 3]" +`, + expected: map[string]interface{}{ + "key1": []interface{}{float64(1), float64(2), float64(3)}, + }, + }, + { + name: "Test 5: Nested JSON Object", + yamlInput: ` +labels: + key1: '{"a": 1, "b": 2}' +`, + expected: map[string]interface{}{ + "key1": map[string]interface{}{ + "a": float64(1), + "b": float64(2), + }, + }, + }, + { + name: "Test 6: Plain Strings", + yamlInput: ` +labels: + key1: "hello" + key2: "world" +`, + expected: map[string]interface{}{ + "key1": "hello", + "key2": "world", + }, + }, + { + name: "Test 7: Specific Strings Example", + yamlInput: ` +labels: + config-sync-group: "group1" +`, + expected: map[string]interface{}{ + "config-sync-group": "group1", + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Set up viper with YAML input + viperInstance = viper.New() // Create a new viper instance for isolation + viperInstance.SetConfigType("yaml") + + err := viperInstance.ReadConfig(strings.NewReader(tt.yamlInput)) + require.NoError(t, err, "Error reading YAML input") + + // Call the function + actual := resolveLabels() + + // Assert the results + assert.Equal(t, tt.expected, actual) + }) + } +} + +func TestParseInt(t *testing.T) { + tests := []struct { + expected interface{} + name string + input string + }{ + {name: "Test 1: Valid Integer", input: "123", expected: 123}, + {name: "Test 2: Negative Integer", input: "-456", expected: -456}, + {name: "Test 3: Zero", input: "0", expected: 0}, + {name: "Test 4: Invalid Integer", input: "abc", expected: nil}, + {name: "Test 5: Empty String", input: "", expected: nil}, + {name: "Test 6: Float String", input: "45.67", expected: nil}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := parseInt(tt.input) + assert.Equal(t, tt.expected, result) + }) + } +} + +func TestParseFloat(t *testing.T) { + tests := []struct { + expected interface{} + name string + input string + }{ + {name: "Test 1: Valid Float", input: "45.67", expected: 45.67}, + {name: "Test 2: Negative Float", input: "-123.45", expected: -123.45}, + {name: "Test 3: Valid Integer as Float", input: "123", expected: 123.0}, + {name: "Test 4: Invalid Float", input: "abc", expected: nil}, + {name: "Test 5: Empty String", input: "", expected: nil}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := parseFloat(tt.input) + assert.Equal(t, tt.expected, result) + }) + } +} + +func TestParseBool(t *testing.T) { + tests := []struct { + expected interface{} + name string + input string + }{ + {name: "Test 1: True (lowercase)", input: "true", expected: true}, + {name: "Test 2: False (lowercase)", input: "false", expected: false}, + {name: "Test 3: True (uppercase)", input: "TRUE", expected: true}, + {name: "Test 4: False (uppercase)", input: "FALSE", expected: false}, + {name: "Test 5: Numeric True", input: "1", expected: true}, + {name: "Test 6: Numeric False", input: "0", expected: false}, + {name: "Test 7: Invalid Boolean", input: "abc", expected: nil}, + {name: "Test 8: Empty String", input: "", expected: nil}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := parseBool(tt.input) + assert.Equal(t, tt.expected, result) + }) + } +} + +func TestParseJSON(t *testing.T) { + tests := []struct { + expected interface{} + name string + input string + }{ + { + name: "Test 1: Valid JSON Object", + input: `{"a": 1, "b": "text"}`, + expected: map[string]interface{}{ + "a": float64(1), + "b": "text", + }, + }, + { + name: "Test 2: Valid JSON Array", + input: `[1, 2, 3]`, + expected: []interface{}{float64(1), float64(2), float64(3)}, + }, + { + name: "Test 3: Nested JSON", + input: `{"a": {"b": [1, 2, 3]}}`, + expected: map[string]interface{}{ + "a": map[string]interface{}{"b": []interface{}{float64(1), float64(2), float64(3)}}, + }, + }, + {name: "Test 4: Invalid JSON", input: `{"a": 1,`, expected: nil}, + {name: "Test 5: Empty String", input: "", expected: nil}, + {name: "Test 6: Plain String", input: `"hello"`, expected: "hello"}, + {name: "Test 7: Number as JSON", input: "123", expected: float64(123)}, + {name: "Test 8: Boolean as JSON", input: "true", expected: true}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := parseJSON(tt.input) + assert.Equal(t, tt.expected, result) + }) + } +} + func getAgentConfig() *Config { return &Config{ UUID: "", @@ -400,5 +708,6 @@ func getAgentConfig() *Config { ServerName: "server-name", }, }, + Labels: make(map[string]any), } } diff --git a/internal/config/defaults.go b/internal/config/defaults.go index 80aa6f963..e3651a940 100644 --- a/internal/config/defaults.go +++ b/internal/config/defaults.go @@ -93,5 +93,5 @@ func DefaultAllowedDirectories() []string { } func DefaultLabels() map[string]string { - return map[string]string{} + return make(map[string]string) } diff --git a/internal/config/types.go b/internal/config/types.go index 780308de4..ddcc6802f 100644 --- a/internal/config/types.go +++ b/internal/config/types.go @@ -40,12 +40,12 @@ type ( File *File `yaml:"-" mapstructure:"file"` Common *CommonSettings `yaml:"-"` Watchers *Watchers `yaml:"-"` + Labels map[string]any `yaml:"-" mapstructure:"labels"` Version string `yaml:"-"` Path string `yaml:"-"` UUID string `yaml:"-"` AllowedDirectories []string `yaml:"-" mapstructure:"allowed_directories"` Features []string `yaml:"-"` - Labels map[string]any `yaml:"-" mapstructure:"labels"` } Log struct {