package smartconfig import ( "strings" "testing" ) func TestMalformedDataHandling(t *testing.T) { t.Run("Config with circular references", func(t *testing.T) { // YAML doesn't support circular references directly, but we can test // configurations that might cause issues during JSON conversion yamlContent := ` a: &anchor b: *anchor c: test ` config, err := NewFromReader(strings.NewReader(yamlContent)) if err != nil { // It's ok if this fails, we just want to ensure no panic return } // Try to access the circular structure _, _ = config.GetString("a.b.c") _, _ = config.Get("a.b.b.b.b") // Should handle gracefully without stack overflow }) t.Run("Very long paths", func(t *testing.T) { yamlContent := ` root: level1: level2: level3: level4: level5: value: "deep" ` config, err := NewFromReader(strings.NewReader(yamlContent)) if err != nil { t.Fatalf("Failed to load config: %v", err) } // Build a very long path longPath := "root" for i := 0; i < 100; i++ { longPath += ".level1" } // Should handle gracefully _, _ = config.GetString(longPath) // Extremely long path veryLongPath := strings.Repeat("a.", 10000) + "b" _, _ = config.GetString(veryLongPath) }) t.Run("Special YAML values", func(t *testing.T) { yamlContent := ` special: yes_value: yes no_value: no on_value: on off_value: off tilde: ~ empty: quoted_yes: "yes" quoted_no: "no" multiline: | line1 line2 line3 folded: > this is a folded string ` config, err := NewFromReader(strings.NewReader(yamlContent)) if err != nil { t.Fatalf("Failed to load config: %v", err) } // YAML's "yes" and "no" are parsed as strings by the YAML parser // Go's strconv.ParseBool doesn't handle these, so they should fail _, err = config.GetBool("special.yes_value") if err == nil { t.Error("GetBool(yes_value) should fail - 'yes' is not a valid Go boolean") } _, err = config.GetBool("special.no_value") if err == nil { t.Error("GetBool(no_value) should fail - 'no' is not a valid Go boolean") } // But we should be able to get them as strings yesStr, err := config.GetString("special.yes_value") if err != nil || yesStr != "yes" { t.Errorf("GetString(yes_value) = %q, %v; want 'yes', nil", yesStr, err) } // Same for "on" and "off" _, err = config.GetBool("special.on_value") if err == nil { t.Error("GetBool(on_value) should fail - 'on' is not a valid Go boolean") } _, err = config.GetBool("special.off_value") if err == nil { t.Error("GetBool(off_value) should fail - 'off' is not a valid Go boolean") } // Quoted versions should be strings quotedYes, err := config.GetString("special.quoted_yes") if err != nil || quotedYes != "yes" { t.Errorf("GetString(quoted_yes) = %q, %v; want 'yes', nil", quotedYes, err) } // Multiline strings multiline, err := config.GetString("special.multiline") if err != nil || !strings.Contains(multiline, "line1") { t.Errorf("Multiline string handling failed: %q", multiline) } }) t.Run("Invalid type conversions", func(t *testing.T) { yamlContent := ` data: object: key: value array: - item1 - item2 string: "hello" number: 42 boolean: true ` config, err := NewFromReader(strings.NewReader(yamlContent)) if err != nil { t.Fatalf("Failed to load config: %v", err) } // Try to get object as string _, err = config.GetString("data.object") if err == nil { t.Error("GetString on object should fail") } // Try to get array as int _, err = config.GetInt("data.array") if err == nil { t.Error("GetInt on array should fail") } // Try to get string as bytes (should work if valid) _, err = config.GetBytes("data.string") if err == nil { t.Error("GetBytes on non-size string should fail") } }) t.Run("JSON marshaling edge cases", func(t *testing.T) { yamlContent := ` tricky: large_int: 18446744073709551615 # max uint64 float_like_int: 1.0 scientific: 1e10 hex: 0xFF # YAML treats as integer 255 octal: 0o777 # YAML treats as integer 511 binary: 0b1111 # YAML treats as integer 15 date: 2023-12-25 datetime: 2023-12-25T10:30:00Z ` config, err := NewFromReader(strings.NewReader(yamlContent)) if err != nil { // Some YAML parsers might not support all these formats t.Skipf("YAML parser doesn't support test formats: %v", err) } // Large integers might lose precision in JSON _, _ = config.GetString("tricky.large_int") // Float that looks like int floatInt, err := config.GetFloat("tricky.float_like_int") if err != nil { t.Errorf("GetFloat(float_like_int) failed: %v", err) } if floatInt != 1.0 { t.Errorf("float_like_int = %f, want 1.0", floatInt) } // Scientific notation sci, err := config.GetFloat("tricky.scientific") if err != nil { t.Errorf("GetFloat(scientific) failed: %v", err) } if sci != 1e10 { t.Errorf("scientific = %f, want 1e10", sci) } }) t.Run("Concurrent access", func(t *testing.T) { yamlContent := ` concurrent: value1: "test1" value2: "test2" nested: value3: "test3" ` config, err := NewFromReader(strings.NewReader(yamlContent)) if err != nil { t.Fatalf("Failed to load config: %v", err) } // Run multiple goroutines accessing the config done := make(chan bool, 10) for i := 0; i < 10; i++ { go func(id int) { for j := 0; j < 100; j++ { switch id % 3 { case 0: _, _ = config.GetString("concurrent.value1") case 1: _, _ = config.GetString("concurrent.value2") case 2: _, _ = config.GetString("concurrent.nested.value3") } } done <- true }(i) } // Wait for all goroutines for i := 0; i < 10; i++ { <-done } }) } func TestGjsonPathNormalization(t *testing.T) { yamlContent := ` data: value: "test" array: - first - second ` config, err := NewFromReader(strings.NewReader(yamlContent)) if err != nil { t.Fatalf("Failed to load config: %v", err) } t.Run("Different path formats", func(t *testing.T) { // These should all access the same value paths := []string{ "data.value", "data.value", // With spaces (trimmed in real usage) "data\\.value", // Escaped dot (gjson might interpret differently) } for _, path := range paths { t.Run(path, func(t *testing.T) { trimmedPath := strings.TrimSpace(path) _, _ = config.GetString(trimmedPath) }) } }) t.Run("Array access formats", func(t *testing.T) { // Different ways to access array elements val1, err := config.GetString("data.array.0") if err != nil || val1 != "first" { t.Errorf("Array access with .0 failed: %v, %v", val1, err) } // gjson also supports [index] syntax // but our YAML structure uses .index val2, err := config.GetString("data.array.1") if err != nil || val2 != "second" { t.Errorf("Array access with .1 failed: %v, %v", val2, err) } }) }