package smartconfig import ( "strings" "testing" ) func TestGjsonPathSupport(t *testing.T) { yamlContent := ` server: host: localhost port: 8080 ssl: enabled: true cert: /etc/ssl/cert.pem database: primary: host: db1.example.com port: 5432 credentials: username: admin password: secret123 replicas: - host: db2.example.com port: 5433 - host: db3.example.com port: 5434 features: new_ui: true rate_limiting: false max_connections: 100 timeout_seconds: 30.5 max_file_size: 10MB metadata: version: 1.2.3 build: 12345 ` config, err := NewFromReader(strings.NewReader(yamlContent)) if err != nil { t.Fatalf("Failed to load config: %v", err) } t.Run("GetString with gjson paths", func(t *testing.T) { tests := []struct { path string expected string }{ {"server.host", "localhost"}, {"server.ssl.cert", "/etc/ssl/cert.pem"}, {"database.primary.host", "db1.example.com"}, {"database.primary.credentials.username", "admin"}, {"database.replicas.0.host", "db2.example.com"}, {"database.replicas.1.host", "db3.example.com"}, {"metadata.version", "1.2.3"}, } for _, tt := range tests { t.Run(tt.path, func(t *testing.T) { result, err := config.GetString(tt.path) if err != nil { t.Errorf("GetString(%q) failed: %v", tt.path, err) return } if result != tt.expected { t.Errorf("GetString(%q) = %q, want %q", tt.path, result, tt.expected) } }) } }) t.Run("GetInt with gjson paths", func(t *testing.T) { tests := []struct { path string expected int }{ {"server.port", 8080}, {"database.primary.port", 5432}, {"database.replicas.0.port", 5433}, {"database.replicas.1.port", 5434}, {"features.max_connections", 100}, {"metadata.build", 12345}, } for _, tt := range tests { t.Run(tt.path, func(t *testing.T) { result, err := config.GetInt(tt.path) if err != nil { t.Errorf("GetInt(%q) failed: %v", tt.path, err) return } if result != tt.expected { t.Errorf("GetInt(%q) = %d, want %d", tt.path, result, tt.expected) } }) } }) t.Run("GetBool with gjson paths", func(t *testing.T) { tests := []struct { path string expected bool }{ {"server.ssl.enabled", true}, {"features.new_ui", true}, {"features.rate_limiting", false}, } for _, tt := range tests { t.Run(tt.path, func(t *testing.T) { result, err := config.GetBool(tt.path) if err != nil { t.Errorf("GetBool(%q) failed: %v", tt.path, err) return } if result != tt.expected { t.Errorf("GetBool(%q) = %v, want %v", tt.path, result, tt.expected) } }) } }) t.Run("GetFloat with gjson paths", func(t *testing.T) { tests := []struct { path string expected float64 }{ {"features.timeout_seconds", 30.5}, } for _, tt := range tests { t.Run(tt.path, func(t *testing.T) { result, err := config.GetFloat(tt.path) if err != nil { t.Errorf("GetFloat(%q) failed: %v", tt.path, err) return } if result != tt.expected { t.Errorf("GetFloat(%q) = %f, want %f", tt.path, result, tt.expected) } }) } }) t.Run("GetBytes with gjson paths", func(t *testing.T) { tests := []struct { path string expected uint64 }{ {"features.max_file_size", 10000000}, // 10MB (decimal, as parsed by humanize) } for _, tt := range tests { t.Run(tt.path, func(t *testing.T) { result, err := config.GetBytes(tt.path) if err != nil { t.Errorf("GetBytes(%q) failed: %v", tt.path, err) return } if result != tt.expected { t.Errorf("GetBytes(%q) = %d, want %d", tt.path, result, tt.expected) } }) } }) t.Run("Get with gjson paths", func(t *testing.T) { // Test array access value, exists := config.Get("database.replicas") if !exists { t.Error("Get(database.replicas) should exist") return } replicas, ok := value.([]interface{}) if !ok { t.Errorf("Get(database.replicas) should return []interface{}, got %T", value) return } if len(replicas) != 2 { t.Errorf("Expected 2 replicas, got %d", len(replicas)) } // Test nested object access value, exists = config.Get("database.primary.credentials") if !exists { t.Error("Get(database.primary.credentials) should exist") return } creds, ok := value.(map[string]interface{}) if !ok { t.Errorf("Get(database.primary.credentials) should return map[string]interface{}, got %T", value) return } if creds["username"] != "admin" { t.Errorf("Expected username=admin, got %v", creds["username"]) } }) t.Run("Non-existent paths", func(t *testing.T) { _, err := config.GetString("server.nonexistent") if err == nil { t.Error("GetString(server.nonexistent) should return error") } _, err = config.GetInt("database.nonexistent.port") if err == nil { t.Error("GetInt(database.nonexistent.port) should return error") } _, exists := config.Get("features.nonexistent") if exists { t.Error("Get(features.nonexistent) should not exist") } }) t.Run("Backward compatibility", func(t *testing.T) { // Test that top-level keys still work yamlContent := ` app_name: TestApp port: 9090 debug: true timeout: 45.5 max_size: 50MB ` config, err := NewFromReader(strings.NewReader(yamlContent)) if err != nil { t.Fatalf("Failed to load config: %v", err) } // Test direct top-level access name, err := config.GetString("app_name") if err != nil || name != "TestApp" { t.Errorf("GetString(app_name) = %q, %v; want TestApp, nil", name, err) } port, err := config.GetInt("port") if err != nil || port != 9090 { t.Errorf("GetInt(port) = %d, %v; want 9090, nil", port, err) } debug, err := config.GetBool("debug") if err != nil || !debug { t.Errorf("GetBool(debug) = %v, %v; want true, nil", debug, err) } timeout, err := config.GetFloat("timeout") if err != nil || timeout != 45.5 { t.Errorf("GetFloat(timeout) = %f, %v; want 45.5, nil", timeout, err) } maxSize, err := config.GetBytes("max_size") expectedSize := uint64(50000000) // 50MB (decimal, as parsed by humanize) if err != nil || maxSize != expectedSize { t.Errorf("GetBytes(max_size) = %d, %v; want %d, nil", maxSize, err, expectedSize) } }) } func TestGjsonAdvancedPaths(t *testing.T) { yamlContent := ` users: - name: Alice age: 30 roles: - admin - developer - name: Bob age: 25 roles: - developer - name: Charlie age: 35 roles: - manager - admin settings: features: ui: theme: dark language: en api: rate_limit: 1000 timeout: 30 ` config, err := NewFromReader(strings.NewReader(yamlContent)) if err != nil { t.Fatalf("Failed to load config: %v", err) } t.Run("Array element access", func(t *testing.T) { // Access specific user name, err := config.GetString("users.1.name") if err != nil || name != "Bob" { t.Errorf("GetString(users.1.name) = %q, %v; want Bob, nil", name, err) } age, err := config.GetInt("users.2.age") if err != nil || age != 35 { t.Errorf("GetInt(users.2.age) = %d, %v; want 35, nil", age, err) } // Access role in array role, err := config.GetString("users.0.roles.0") if err != nil || role != "admin" { t.Errorf("GetString(users.0.roles.0) = %q, %v; want admin, nil", role, err) } }) t.Run("Deep nested access", func(t *testing.T) { theme, err := config.GetString("settings.features.ui.theme") if err != nil || theme != "dark" { t.Errorf("GetString(settings.features.ui.theme) = %q, %v; want dark, nil", theme, err) } rateLimit, err := config.GetInt("settings.features.api.rate_limit") if err != nil || rateLimit != 1000 { t.Errorf("GetInt(settings.features.api.rate_limit) = %d, %v; want 1000, nil", rateLimit, err) } }) }