package smartconfig import ( "fmt" "strconv" "strings" "testing" ) func TestGjsonPathEdgeCases(t *testing.T) { yamlContent := ` # Edge case test data empty_string: "" null_value: null zero: 0 false_bool: false empty_map: {} empty_array: [] special_keys: "key.with.dots": "dotted key value" "key with spaces": "spaced key value" "key[with]brackets": "bracketed key value" "key{with}braces": "braced key value" "123": "numeric key" "": "empty key" "key\"with\"quotes": "quoted key" "key'with'quotes": "single quoted key" "key\nwith\nnewlines": "newline key" "key\twith\ttabs": "tab key" deeply: nested: structure: with: many: levels: value: "deep value" arrays: nested: - - - value: "triple nested array" mixed: - name: "first" items: - id: 1 - id: 2 - name: "second" items: - id: 3 - id: 4 unicode: "emoji🎉key": "emoji value" "中文键": "chinese value" "مفتاح": "arabic value" "ключ": "russian value" types: very_large_number: 9223372036854775807 # max int64 very_small_number: -9223372036854775808 # min int64 float_edge: 1.7976931348623157e+308 # near max float64 tiny_float: 2.2250738585072014e-308 # near min positive float64 special_values: inf: .inf neg_inf: -.inf nan: .nan inf_string: "Infinity" neg_inf_string: "-Infinity" nan_string: "NaN" ` config, err := NewFromReader(strings.NewReader(yamlContent)) if err != nil { t.Fatalf("Failed to load config: %v", err) } t.Run("Empty and null values", func(t *testing.T) { // Empty string val, err := config.GetString("empty_string") if err != nil || val != "" { t.Errorf("GetString(empty_string) = %q, %v; want empty string, nil", val, err) } // Zero value zero, err := config.GetInt("zero") if err != nil || zero != 0 { t.Errorf("GetInt(zero) = %d, %v; want 0, nil", zero, err) } // False boolean falseBool, err := config.GetBool("false_bool") if err != nil || falseBool != false { t.Errorf("GetBool(false_bool) = %v, %v; want false, nil", falseBool, err) } // Null value should fail for typed getters _, err = config.GetString("null_value") if err == nil { t.Error("GetString(null_value) should return error for null") } }) t.Run("Special character keys", func(t *testing.T) { // Keys with dots should be accessible with escaped syntax // Note: gjson doesn't support accessing keys with dots directly // This is a known limitation // Empty map emptyMap, exists := config.Get("empty_map") if !exists { t.Error("empty_map should exist") } if m, ok := emptyMap.(map[string]interface{}); !ok || len(m) != 0 { t.Errorf("empty_map should be an empty map, got %v", emptyMap) } // Empty array emptyArray, exists := config.Get("empty_array") if !exists { t.Error("empty_array should exist") } if a, ok := emptyArray.([]interface{}); !ok || len(a) != 0 { t.Errorf("empty_array should be an empty array, got %v", emptyArray) } }) t.Run("Very deep nesting", func(t *testing.T) { deepValue, err := config.GetString("deeply.nested.structure.with.many.levels.value") if err != nil || deepValue != "deep value" { t.Errorf("Deep nested access failed: %v, %v", deepValue, err) } }) t.Run("Complex array access", func(t *testing.T) { // Triple nested array val, err := config.GetString("arrays.nested.0.0.0.value") if err != nil || val != "triple nested array" { t.Errorf("Triple nested array access = %q, %v; want 'triple nested array', nil", val, err) } // Mixed structure navigation id1, err := config.GetInt("arrays.mixed.0.items.0.id") if err != nil || id1 != 1 { t.Errorf("Mixed array access = %d, %v; want 1, nil", id1, err) } id4, err := config.GetInt("arrays.mixed.1.items.1.id") if err != nil || id4 != 4 { t.Errorf("Mixed array access = %d, %v; want 4, nil", id4, err) } }) t.Run("Unicode keys", func(t *testing.T) { // Unicode keys should work normally chineseVal, exists := config.Get("unicode.中文键") if !exists || chineseVal != "chinese value" { t.Errorf("Unicode key access failed: %v", chineseVal) } }) t.Run("Type edge cases", func(t *testing.T) { // Very large numbers largeNum, err := config.GetInt("types.very_large_number") if err != nil { t.Errorf("GetInt(very_large_number) failed: %v", err) } if largeNum != 9223372036854775807 { t.Errorf("Large number = %d, want 9223372036854775807", largeNum) } // Very small numbers smallNum, err := config.GetInt("types.very_small_number") if err != nil { t.Errorf("GetInt(very_small_number) failed: %v", err) } if smallNum != -9223372036854775808 { t.Errorf("Small number = %d, want -9223372036854775808", smallNum) } }) t.Run("Special float values", func(t *testing.T) { // These special float values should be converted to strings inf, err := config.GetString("special_values.inf") if err != nil || inf != "Infinity" { t.Errorf("GetString(inf) = %q, %v; want 'Infinity', nil", inf, err) } negInf, err := config.GetString("special_values.neg_inf") if err != nil || negInf != "-Infinity" { t.Errorf("GetString(neg_inf) = %q, %v; want '-Infinity', nil", negInf, err) } nan, err := config.GetString("special_values.nan") if err != nil || nan != "NaN" { t.Errorf("GetString(nan) = %q, %v; want 'NaN', nil", nan, err) } }) t.Run("Invalid paths", func(t *testing.T) { // Various invalid path formats invalidPaths := []string{ "...", // Only dots "key.", // Trailing dot ".key", // Leading dot "key..value", // Double dots "key..", // Double trailing dots "key[", // Unclosed bracket "key]", // Unmatched bracket // "key.[0]", // Actually valid gjson syntax "arrays.mixed.-1.name", // Negative array index "arrays.mixed.999.name", // Out of bounds array index "", // Empty path "nonexistent", // Simple non-existent key "nonexistent.nested.key", // Nested non-existent } for _, path := range invalidPaths { t.Run(path, func(t *testing.T) { _, err := config.GetString(path) if err == nil { t.Errorf("GetString(%q) should return error", path) } _, exists := config.Get(path) if exists { t.Errorf("Get(%q) should return false for exists", path) } }) } }) t.Run("Type conversion edge cases", func(t *testing.T) { // Try to get string as various types strConfig := ` string_number: "123" string_bool: "true" string_float: "45.67" string_bytes: "1GB" not_a_number: "abc" not_a_bool: "yes" partial_number: "123abc" ` c, _ := NewFromReader(strings.NewReader(strConfig)) // String to int num, err := c.GetInt("string_number") if err != nil || num != 123 { t.Errorf("GetInt(string_number) = %d, %v; want 123, nil", num, err) } // String to bool b, err := c.GetBool("string_bool") if err != nil || b != true { t.Errorf("GetBool(string_bool) = %v, %v; want true, nil", b, err) } // Invalid conversions _, err = c.GetInt("not_a_number") if err == nil { t.Error("GetInt(not_a_number) should fail") } _, err = c.GetBool("not_a_bool") if err == nil { t.Error("GetBool(not_a_bool) should fail") } _, err = c.GetInt("partial_number") if err == nil { t.Error("GetInt(partial_number) should fail") } }) t.Run("Gjson special syntax", func(t *testing.T) { // Test that gjson special syntax doesn't cause issues dangerousPaths := []string{ "*", // Wildcard "special_keys.#", // Array length operator "special_keys.@reverse", // Modifier "special_keys|@pretty", // Pipe operator "arrays.mixed.#.name", // Multi-value syntax "arrays.mixed.[0,1]", // Union syntax } for _, path := range dangerousPaths { t.Run(path, func(t *testing.T) { // These should either work correctly or fail gracefully _, _ = config.GetString(path) _, _ = config.Get(path) // We're just ensuring no panic occurs }) } }) } func TestGjsonPathSecurity(t *testing.T) { yamlContent := ` sensitive: password: "secret123" api_key: "sk-1234567890" public: version: "1.0.0" ` config, err := NewFromReader(strings.NewReader(yamlContent)) if err != nil { t.Fatalf("Failed to load config: %v", err) } t.Run("Path traversal attempts", func(t *testing.T) { // Ensure path traversal doesn't work traversalPaths := []string{ "../sensitive/password", "public/../sensitive/password", "./sensitive/password", "public/../../sensitive/password", } for _, path := range traversalPaths { t.Run(path, func(t *testing.T) { // These should not access sensitive data through traversal val, _ := config.GetString(path) if val == "secret123" { t.Errorf("Path traversal %q should not access sensitive data", path) } }) } }) } func TestGjsonPerformanceEdgeCases(t *testing.T) { // Test with large configurations var largeYaml strings.Builder largeYaml.WriteString("large_array:\n") for i := 0; i < 1000; i++ { largeYaml.WriteString(" - id: ") largeYaml.WriteString(strconv.Itoa(i)) largeYaml.WriteString("\n") } largeYaml.WriteString("large_map:\n") for i := 0; i < 100; i++ { largeYaml.WriteString(fmt.Sprintf(" key%d: value%d\n", i, i)) } config, err := NewFromReader(strings.NewReader(largeYaml.String())) if err != nil { t.Fatalf("Failed to load large config: %v", err) } // Access various elements _, err = config.GetString("large_array.999.id") if err != nil { t.Errorf("Failed to access last array element: %v", err) } _, err = config.GetString("large_map.key99") if err != nil { t.Errorf("Failed to access map element: %v", err) } } func TestResolverArgumentValidation(t *testing.T) { yamlContent := ` test: value ` config, err := NewFromReader(strings.NewReader(yamlContent)) if err != nil { t.Fatalf("Failed to load config: %v", err) } t.Run("Path with interpolation syntax", func(t *testing.T) { // Paths that look like interpolation should be handled correctly suspiciousPaths := []string{ "${ENV:PATH}", // Interpolation syntax in path "test${ENV:SUFFIX}", // Partial interpolation "${test}", // Brace syntax "$test", // Dollar sign "test$", // Trailing dollar } for _, path := range suspiciousPaths { t.Run(path, func(t *testing.T) { _, _ = config.GetString(path) // Should not panic or cause issues }) } }) }