smartconfig/readme_examples_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

176 lines
4.2 KiB
Go

package smartconfig
import (
"fmt"
"os"
"strings"
"testing"
)
func TestREADMEExamples(t *testing.T) {
// Create test YAML files that match README examples
// Database config file
databaseYAML := `production:
primary:
host: prod-primary.db.example.com
port: 5432
replica:
host: prod-replica.db.example.com
port: 5432
staging:
primary:
host: staging.db.example.com
port: 5432
`
err := os.WriteFile("./test/readme_database.yml", []byte(databaseYAML), 0644)
if err != nil {
t.Fatalf("Failed to create database test file: %v", err)
}
defer func() { _ = os.Remove("./test/readme_database.yml") }()
// Features config file
featuresYAML := `features:
analytics:
enabled: true
provider: google
rate_limiting:
enabled: false
`
err = os.WriteFile("./test/readme_features.yaml", []byte(featuresYAML), 0644)
if err != nil {
t.Fatalf("Failed to create features test file: %v", err)
}
defer func() { _ = os.Remove("./test/readme_features.yaml") }()
// Test the exact examples from README
tests := []struct {
name string
yaml string
expected map[string]interface{}
}{
{
name: "YAML resolver examples from README",
yaml: `
db_config: ${YAML:./test/readme_database.yml:production.primary}
replica_host: ${YAML:./test/readme_database.yml:production.replica.host}
analytics: ${YAML:./test/readme_features.yaml:features.analytics.enabled}
`,
expected: map[string]interface{}{
"db_config": "map[host:prod-primary.db.example.com port:5432]",
"replica_host": "prod-replica.db.example.com",
"analytics": "true",
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
config, err := NewFromReader(strings.NewReader(tt.yaml))
if err != nil {
t.Fatalf("Failed to load config: %v", err)
}
for key, expectedValue := range tt.expected {
val, exists := config.Get(key)
if !exists {
t.Errorf("Key %s not found in config", key)
continue
}
// Convert to string for comparison
strVal := ""
switch v := val.(type) {
case string:
strVal = v
case bool:
strVal = fmt.Sprintf("%t", v)
default:
strVal = fmt.Sprintf("%v", v)
}
if strVal != expectedValue {
t.Errorf("Key %s: expected %q, got %q", key, expectedValue, strVal)
}
}
})
}
}
func TestCompleteExampleFromREADME(t *testing.T) {
// This tests the complete example from the README
err := os.WriteFile("./test/services.json", []byte(`{
"production": {
"api": {
"endpoint": "https://api.example.com"
}
}
}`), 0644)
if err != nil {
t.Fatalf("Failed to create services.json: %v", err)
}
defer func() { _ = os.Remove("./test/services.json") }()
err = os.WriteFile("./test/features.json", []byte(`{
"production": {
"new_ui": false,
"rate_limiting": true
},
"staging": {
"new_ui": true
}
}`), 0644)
if err != nil {
t.Fatalf("Failed to create features.json: %v", err)
}
defer func() { _ = os.Remove("./test/features.json") }()
// Set environment variables for the test
_ = os.Setenv("ENVIRONMENT", "production")
defer func() { _ = os.Unsetenv("ENVIRONMENT") }()
configYAML := `
# External service configuration from JSON config file
services: ${JSON:./test/services.json:production}
# Feature flags from JSON file
features: ${JSON:./test/features.json:${ENV:ENVIRONMENT}}
`
config, err := NewFromReader(strings.NewReader(configYAML))
if err != nil {
t.Fatalf("Failed to load config: %v", err)
}
// Check services were loaded
services, exists := config.Get("services")
if !exists {
t.Fatal("services key not found")
}
// Check it's a map
if _, ok := services.(map[string]interface{}); !ok {
t.Errorf("Expected services to be a map, got %T", services)
}
// Check features were loaded with correct environment
features, exists := config.Get("features")
if !exists {
t.Fatal("features key not found")
}
// Check features content
if featuresMap, ok := features.(map[string]interface{}); ok {
if newUI, exists := featuresMap["new_ui"]; exists {
if newUI != false {
t.Errorf("Expected new_ui to be false, got %v", newUI)
}
} else {
t.Error("new_ui not found in features")
}
} else {
t.Errorf("Expected features to be a map, got %T", features)
}
}