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) } }