smartconfig/main_test.go
2025-07-20 12:12:14 +02:00

351 lines
8.9 KiB
Go

package smartconfig
import (
"os"
"regexp"
"strings"
"testing"
)
func TestEnvResolver(t *testing.T) {
_ = os.Setenv("TEST_VAR", "test-value")
defer func() { _ = os.Unsetenv("TEST_VAR") }()
resolver := &EnvResolver{}
result, err := resolver.Resolve("TEST_VAR")
if err != nil {
t.Fatalf("Failed to resolve env var: %v", err)
}
if result != "test-value" {
t.Errorf("Expected 'test-value', got '%s'", result)
}
}
func TestExecResolver(t *testing.T) {
resolver := &ExecResolver{}
result, err := resolver.Resolve("echo hello")
if err != nil {
t.Fatalf("Failed to execute command: %v", err)
}
if result != "hello" {
t.Errorf("Expected 'hello', got '%s'", result)
}
}
func TestFileResolver(t *testing.T) {
resolver := &FileResolver{}
result, err := resolver.Resolve("./test/machine-id")
if err != nil {
t.Fatalf("Failed to read file: %v", err)
}
if result != "test-machine-123456789" {
t.Errorf("Expected 'test-machine-123456789', got '%s'", result)
}
}
func TestJSONResolver(t *testing.T) {
resolver := &JSONResolver{}
// Test simple path
result, err := resolver.Resolve("./test/hosts.json:.")
if err != nil {
t.Fatalf("Failed to resolve JSON: %v", err)
}
if !strings.Contains(result, "tls_sni_host") {
t.Errorf("Expected JSON output to contain 'tls_sni_host', got '%s'", result)
}
}
func TestYAMLResolver(t *testing.T) {
resolver := &YAMLResolver{}
// Test simple path
result, err := resolver.Resolve("./test/data.yaml:.")
if err != nil {
t.Fatalf("Failed to resolve YAML: %v", err)
}
if !strings.Contains(result, "database") {
t.Errorf("Expected YAML output to contain 'database', got '%s'", result)
}
}
func TestInterpolateBasic(t *testing.T) {
_ = os.Setenv("BASIC", "value")
defer func() { _ = os.Unsetenv("BASIC") }()
config := New()
content := "test: \"${ENV:BASIC}\""
err := config.LoadFromReader(strings.NewReader(content))
if err != nil {
t.Fatalf("Failed to load config: %v", err)
}
value, err := config.GetString("test")
if err != nil {
t.Fatalf("Failed to get test value: %v", err)
}
if value != "value" {
t.Errorf("Expected 'value', got '%s'", value)
}
}
func TestInterpolateMethod(t *testing.T) {
config := New()
_ = os.Setenv("FOO", "bar")
defer func() { _ = os.Unsetenv("FOO") }()
// Test direct interpolation
result, _ := config.interpolate("${ENV:FOO}", 0)
if result != "bar" {
t.Errorf("Expected 'bar', got '%s'", result)
}
}
func TestInterpolateSimple(t *testing.T) {
config := New()
_ = os.Setenv("TEST", "value")
defer func() { _ = os.Unsetenv("TEST") }()
// Verify regex finds the match
re := regexp.MustCompile(`\$\{([^:]+?):(.*?)\}`)
matches := re.FindAllStringSubmatch("${ENV:TEST}", -1)
if len(matches) == 0 {
t.Fatal("Regex didn't find any matches")
}
// Test simple interpolation
result, _ := config.interpolate("${ENV:TEST}", 0)
if result != "value" {
t.Errorf("Simple interpolation failed: expected 'value', got '%s'", result)
}
}
func TestInterpolateStep(t *testing.T) {
config := New()
// Set env vars
_ = os.Setenv("BAR", "value1")
_ = os.Setenv("FOO_value1", "success")
defer func() {
_ = os.Unsetenv("BAR")
_ = os.Unsetenv("FOO_value1")
}()
// Step 1: Inner interpolation should resolve first
step1 := "${ENV:BAR}"
result1, _ := config.interpolate(step1, 0)
if result1 != "value1" {
t.Errorf("Step 1 failed: expected 'value1', got '%s'", result1)
}
// Step 2: With the result, build the outer
step2 := "${ENV:FOO_value1}"
result2, _ := config.interpolate(step2, 0)
if result2 != "success" {
t.Errorf("Step 2 failed: expected 'success', got '%s'", result2)
}
// Step 3: Full nested
full := "${ENV:FOO_${ENV:BAR}}"
result3, _ := config.interpolate(full, 0)
if result3 != "success" {
t.Errorf("Full nested failed: expected 'success', got '%s'", result3)
}
}
func TestRegexPattern(t *testing.T) {
re := regexp.MustCompile(`\$\{([^:]+?):(.*?)\}`)
// Test simple case
matches := re.FindStringSubmatch("${ENV:FOO}")
if len(matches) != 3 || matches[1] != "ENV" || matches[2] != "FOO" {
t.Errorf("Simple pattern failed: %v", matches)
}
// Test nested case - this will fail with current regex
input := "${ENV:FOO_${ENV:BAR}}"
matches = re.FindStringSubmatch(input)
if len(matches) == 3 {
t.Logf("Nested pattern matches: resolver=%s, value=%s", matches[1], matches[2])
// This will show that value is "FOO_${ENV:BAR" which is wrong
}
}
func TestInterpolateNested(t *testing.T) {
config := New()
_ = os.Setenv("INNER", "BAR")
_ = os.Setenv("FOO_BAR", "success")
defer func() {
_ = os.Unsetenv("INNER")
_ = os.Unsetenv("FOO_BAR")
}()
// Test nested interpolation directly
result, _ := config.interpolate("${ENV:FOO_${ENV:INNER}}", 0)
if result != "success" {
t.Errorf("Expected 'success', got '%s'", result)
}
}
func TestSimpleNestedInterpolation(t *testing.T) {
_ = os.Setenv("SUFFIX", "BAR")
_ = os.Setenv("FOO_BAR", "success")
defer func() {
_ = os.Unsetenv("SUFFIX")
_ = os.Unsetenv("FOO_BAR")
}()
config := New()
content := "test: \"${ENV:FOO_${ENV:SUFFIX}}\""
err := config.LoadFromReader(strings.NewReader(content))
if err != nil {
t.Fatalf("Failed to load config: %v", err)
}
value, err := config.GetString("test")
if err != nil {
t.Fatalf("Failed to get test value: %v", err)
}
if value != "success" {
t.Errorf("Expected 'success', got '%s'", value)
}
}
func TestConfigInterpolation(t *testing.T) {
// Set up environment
_ = os.Setenv("TEST_APP_NAME", "myapp")
_ = os.Setenv("TEST_PORT", "8080")
_ = os.Setenv("TEST_ENV_SUFFIX", "VALUE")
_ = os.Setenv("NESTED_VALUE", "nested-result")
defer func() {
_ = os.Unsetenv("TEST_APP_NAME")
_ = os.Unsetenv("TEST_PORT")
_ = os.Unsetenv("TEST_ENV_SUFFIX")
_ = os.Unsetenv("NESTED_VALUE")
}()
config := New()
err := config.LoadFromFile("./test/config.yaml")
if err != nil {
t.Fatalf("Failed to load config: %v", err)
}
// Test basic interpolation
name, err := config.GetString("name")
if err != nil {
t.Fatalf("Failed to get name: %v", err)
}
if name != "myapp" {
t.Errorf("Expected name 'myapp', got '%s'", name)
}
// Test exec interpolation
if serverData, ok := config.data["server"].(map[string]interface{}); ok {
if hostname, ok := serverData["hostname"].(string); ok {
if hostname == "" {
t.Errorf("Expected hostname to be non-empty")
}
} else {
t.Errorf("Expected server.hostname to be a string")
}
} else {
t.Errorf("Expected server to be a map")
}
// Test file interpolation
if machineData, ok := config.data["machine"].(map[string]interface{}); ok {
if id, ok := machineData["id"].(string); ok {
if id != "test-machine-123456789" {
t.Errorf("Expected machine.id 'test-machine-123456789', got '%s'", id)
}
}
}
// Test nested interpolation
if nestedData, ok := config.data["nested"].(map[string]interface{}); ok {
if value, ok := nestedData["value"].(string); ok {
if value != "nested-result" {
t.Errorf("Expected nested.value 'nested-result', got '%s'", value)
}
}
}
// Test environment injection
injectedVar := os.Getenv("INJECTED_VAR")
if injectedVar != "injected-value" {
t.Errorf("Expected INJECTED_VAR to be 'injected-value', got '%s'", injectedVar)
}
computedVar := os.Getenv("COMPUTED_VAR")
if computedVar != "computed" {
t.Errorf("Expected COMPUTED_VAR to be 'computed', got '%s'", computedVar)
}
}
func TestRecursionLimit(t *testing.T) {
config := New()
// Create a deeply nested interpolation
content := "value: ${ENV:LEVEL1_${ENV:LEVEL2_${ENV:LEVEL3_${ENV:LEVEL4}}}}"
_ = os.Setenv("LEVEL4", "END")
_ = os.Setenv("LEVEL3_END", "3")
_ = os.Setenv("LEVEL2_3", "2")
_ = os.Setenv("LEVEL1_2", "final")
defer func() {
_ = os.Unsetenv("LEVEL4")
_ = os.Unsetenv("LEVEL3_END")
_ = os.Unsetenv("LEVEL2_3")
_ = os.Unsetenv("LEVEL1_2")
}()
err := config.LoadFromReader(strings.NewReader(content))
if err != nil {
t.Fatalf("Failed to load config: %v", err)
}
// The interpolation should stop at depth 3
value, _ := config.GetString("value")
// At depth 3, it should have resolved to "final" or stopped
if value == "" {
t.Errorf("Expected some value from recursion limit test")
}
}
func TestInvalidResolverFormat(t *testing.T) {
tests := []struct {
name string
resolver Resolver
input string
}{
{"JSON missing path", &JSONResolver{}, "file.json"},
{"YAML missing path", &YAMLResolver{}, "file.yaml"},
{"Vault missing key", &VaultResolver{}, "secret/data/myapp"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
_, err := tt.resolver.Resolve(tt.input)
if err == nil {
t.Errorf("Expected error for invalid format")
}
})
}
}
func TestUnknownResolver(t *testing.T) {
config := New()
content := "value: ${UNKNOWN:test}"
err := config.LoadFromReader(strings.NewReader(content))
if err != nil {
t.Fatalf("Failed to load config: %v", err)
}
// Unknown resolver should leave the value as-is
value, _ := config.GetString("value")
if value != "${UNKNOWN:test}" {
t.Errorf("Expected '${UNKNOWN:test}', got '%s'", value)
}
}