- 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.
176 lines
4.2 KiB
Go
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)
|
|
}
|
|
}
|