smartconfig/resolver_yaml_test.go
sneak 6b8c16dd1d Implement proper YAML path navigation and complex type support
- YAML resolver now supports full path navigation (e.g., production.primary.host)
- Both JSON and YAML resolvers return YAML-formatted data for complex types
- This allows proper type preservation when loading objects/arrays from files
- Updated convertToType to parse YAML returned by resolvers
- Added comprehensive tests for YAML path navigation including arrays
- Fixed JSON resolver to support "." path for entire document
- All README examples now work correctly

The key insight was that resolvers should return YAML strings for complex
types, which can then be parsed and merged into the configuration structure,
preserving the original types (maps, arrays) instead of flattening to strings.
2025-07-21 18:57:13 +02:00

247 lines
5.6 KiB
Go

package smartconfig
import (
"os"
"testing"
)
func TestYAMLResolverPathNavigation(t *testing.T) {
resolver := &YAMLResolver{}
tests := []struct {
name string
input string
expected string
wantErr bool
}{
// Basic path navigation
{
name: "top level key",
input: "./test/features.yaml:settings",
expected: "debug: false\nlog_level: info",
wantErr: false,
},
{
name: "nested path with dot notation",
input: "./test/database.yaml:production.primary",
expected: "host: prod-db-primary.example.com\nport: 5432\nusername: prod_user",
wantErr: false,
},
{
name: "deeply nested path",
input: "./test/database.yaml:production.replica.host",
expected: "prod-db-replica.example.com",
wantErr: false,
},
{
name: "boolean value",
input: "./test/features.yaml:features.analytics.enabled",
expected: "true",
wantErr: false,
},
{
name: "string value",
input: "./test/features.yaml:features.analytics.provider",
expected: "google",
wantErr: false,
},
{
name: "numeric value",
input: "./test/features.yaml:features.rate_limiting.requests_per_minute",
expected: "100",
wantErr: false,
},
{
name: "root document with dot",
input: "./test/features.yaml:.",
expected: "features:\n analytics:\n enabled: true\n provider: google\n tracking_id: UA-123456\n new_ui:\n enabled: false\n rollout_percentage: 25\n rate_limiting:\n enabled: true\n requests_per_minute: 100\nsettings:\n debug: false\n log_level: info",
wantErr: false,
},
// Error cases
{
name: "non-existent path",
input: "./test/database.yaml:production.nonexistent.key",
expected: "",
wantErr: true,
},
{
name: "invalid file",
input: "./test/nonexistent.yaml:some.path",
expected: "",
wantErr: true,
},
{
name: "missing path separator",
input: "./test/database.yaml",
expected: "",
wantErr: true,
},
{
name: "empty path",
input: "./test/database.yaml:",
expected: "",
wantErr: true,
},
// Edge cases with special characters
{
name: "path with spaces (if exists)",
input: "./test/features.yaml:settings.log_level",
expected: "info",
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result, err := resolver.Resolve(tt.input)
if tt.wantErr {
if err == nil {
t.Errorf("Expected error but got none, result: %s", result)
}
} else {
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
if result != tt.expected {
t.Errorf("Expected %q, got %q", tt.expected, result)
}
}
})
}
}
func TestYAMLResolverArrayAccess(t *testing.T) {
// Create a test YAML file with arrays
yamlContent := `servers:
- name: server1
host: 192.168.1.1
port: 8080
- name: server2
host: 192.168.1.2
port: 8081
users:
- admin
- user1
- user2
`
// Write test file
err := os.WriteFile("./test/arrays.yaml", []byte(yamlContent), 0644)
if err != nil {
t.Fatalf("Failed to create test file: %v", err)
}
defer func() { _ = os.Remove("./test/arrays.yaml") }()
resolver := &YAMLResolver{}
tests := []struct {
name string
input string
expected string
wantErr bool
}{
{
name: "array index access",
input: "./test/arrays.yaml:servers.0",
expected: "host: 192.168.1.1\nname: server1\nport: 8080",
wantErr: false,
},
{
name: "array element property",
input: "./test/arrays.yaml:servers.1.host",
expected: "192.168.1.2",
wantErr: false,
},
{
name: "simple array element",
input: "./test/arrays.yaml:users.0",
expected: "admin",
wantErr: false,
},
{
name: "out of bounds array access",
input: "./test/arrays.yaml:servers.10",
expected: "",
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result, err := resolver.Resolve(tt.input)
if tt.wantErr {
if err == nil {
t.Errorf("Expected error but got none, result: %s", result)
}
} else {
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
if result != tt.expected {
t.Errorf("Expected %q, got %q", tt.expected, result)
}
}
})
}
}
func TestYAMLResolverComplexPaths(t *testing.T) {
// Test quoted paths and special cases
yamlContent := `special:
"dotted.key": value1
"key with spaces": value2
123: numeric_key
nested:
level1:
level2:
level3:
level4:
deepvalue: "found it!"
`
err := os.WriteFile("./test/complex.yaml", []byte(yamlContent), 0644)
if err != nil {
t.Fatalf("Failed to create test file: %v", err)
}
defer func() { _ = os.Remove("./test/complex.yaml") }()
resolver := &YAMLResolver{}
tests := []struct {
name string
input string
expected string
wantErr bool
}{
{
name: "deeply nested value",
input: "./test/complex.yaml:nested.level1.level2.level3.level4.deepvalue",
expected: "found it!",
wantErr: false,
},
// Note: Quoted keys would require special handling in the path parser
// For now, we'll stick to simple dot notation
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result, err := resolver.Resolve(tt.input)
if tt.wantErr {
if err == nil {
t.Errorf("Expected error but got none, result: %s", result)
}
} else {
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
if result != tt.expected {
t.Errorf("Expected %q, got %q", tt.expected, result)
}
}
})
}
}