351 lines
8.9 KiB
Go
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)
|
|
}
|
|
}
|