Files
dodo/pkg/config/parser/env_test.go

1130 lines
33 KiB
Go

package parser
import (
"net/url"
"testing"
"time"
"github.com/aykhans/dodo/pkg/types"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestNewConfigENVParser(t *testing.T) {
t.Run("NewConfigENVParser with empty prefix", func(t *testing.T) {
parser := NewConfigENVParser("")
require.NotNil(t, parser)
assert.Empty(t, parser.envPrefix)
})
t.Run("NewConfigENVParser with prefix", func(t *testing.T) {
parser := NewConfigENVParser("DODO")
require.NotNil(t, parser)
assert.Equal(t, "DODO", parser.envPrefix)
})
}
func TestConfigENVParser_getFullEnvName(t *testing.T) {
t.Run("getFullEnvName with empty prefix", func(t *testing.T) {
parser := ConfigENVParser{envPrefix: ""}
assert.Equal(t, "URL", parser.getFullEnvName("URL"))
assert.Equal(t, "METHOD", parser.getFullEnvName("METHOD"))
})
t.Run("getFullEnvName with prefix", func(t *testing.T) {
parser := ConfigENVParser{envPrefix: "DODO"}
assert.Equal(t, "DODO_URL", parser.getFullEnvName("URL"))
assert.Equal(t, "DODO_METHOD", parser.getFullEnvName("METHOD"))
})
t.Run("getFullEnvName with complex prefix", func(t *testing.T) {
parser := ConfigENVParser{envPrefix: "MY_APP"}
assert.Equal(t, "MY_APP_CONFIG_FILE", parser.getFullEnvName("CONFIG_FILE"))
})
}
func TestConfigENVParser_getEnv(t *testing.T) {
t.Run("getEnv with empty prefix", func(t *testing.T) {
t.Setenv("TEST_ENV_VAR", "test_value")
parser := ConfigENVParser{envPrefix: ""}
assert.Equal(t, "test_value", parser.getEnv("TEST_ENV_VAR"))
})
t.Run("getEnv with prefix", func(t *testing.T) {
t.Setenv("PREFIX_TEST_VAR", "prefixed_value")
parser := ConfigENVParser{envPrefix: "PREFIX"}
assert.Equal(t, "prefixed_value", parser.getEnv("TEST_VAR"))
})
t.Run("getEnv for non-existent variable", func(t *testing.T) {
parser := ConfigENVParser{envPrefix: "PREFIX"}
assert.Empty(t, parser.getEnv("NON_EXISTENT"))
})
}
func TestConfigENVParser_Parse_ConfigFile(t *testing.T) {
t.Run("Parse with valid YAML config file", func(t *testing.T) {
t.Setenv("CONFIG_FILE", "/path/to/config.yaml")
parser := NewConfigENVParser("")
config, err := parser.Parse()
require.NoError(t, err)
require.NotNil(t, config)
assert.Len(t, config.Files, 1)
assert.Equal(t, "/path/to/config.yaml", config.Files[0].String())
assert.Equal(t, types.ConfigFileTypeYAML, config.Files[0].Type())
})
t.Run("Parse with config file without extension", func(t *testing.T) {
t.Setenv("CONFIG_FILE", "/path/to/config")
parser := NewConfigENVParser("")
config, err := parser.Parse()
assert.Nil(t, config)
var fieldErr types.FieldParseErrors
require.ErrorAs(t, err, &fieldErr)
assert.Len(t, fieldErr.Errors, 1)
assert.Equal(t, "CONFIG_FILE", fieldErr.Errors[0].Field)
assert.Equal(t, "/path/to/config", fieldErr.Errors[0].Value)
assert.Contains(t, fieldErr.Errors[0].Err.Error(), "file extension not found")
})
t.Run("Parse with unsupported config file type", func(t *testing.T) {
t.Setenv("CONFIG_FILE", "/path/to/config.json")
parser := NewConfigENVParser("")
config, err := parser.Parse()
assert.Nil(t, config)
var fieldErr types.FieldParseErrors
require.ErrorAs(t, err, &fieldErr)
assert.Len(t, fieldErr.Errors, 1)
assert.Equal(t, "CONFIG_FILE", fieldErr.Errors[0].Field)
assert.Equal(t, "/path/to/config.json", fieldErr.Errors[0].Value)
assert.Contains(t, fieldErr.Errors[0].Err.Error(), "file type")
assert.Contains(t, fieldErr.Errors[0].Err.Error(), "not supported")
})
t.Run("Parse with remote config file URL", func(t *testing.T) {
t.Setenv("CONFIG_FILE", "https://example.com/config.yaml")
parser := NewConfigENVParser("")
config, err := parser.Parse()
require.NoError(t, err)
require.NotNil(t, config)
assert.Len(t, config.Files, 1)
assert.Equal(t, "https://example.com/config.yaml", config.Files[0].String())
assert.Equal(t, types.ConfigFileTypeYAML, config.Files[0].Type())
assert.Equal(t, types.ConfigFileLocationRemote, config.Files[0].LocationType())
})
t.Run("Parse with config file using prefix", func(t *testing.T) {
t.Setenv("DODO_CONFIG_FILE", "/path/to/config.yml")
parser := NewConfigENVParser("DODO")
config, err := parser.Parse()
require.NoError(t, err)
require.NotNil(t, config)
assert.Len(t, config.Files, 1)
})
}
func TestConfigENVParser_Parse_BooleanFields(t *testing.T) {
t.Run("Parse with YES=true", func(t *testing.T) {
t.Setenv("YES", "true")
parser := NewConfigENVParser("")
config, err := parser.Parse()
require.NoError(t, err)
require.NotNil(t, config)
require.NotNil(t, config.Yes)
assert.True(t, *config.Yes)
})
t.Run("Parse with YES=false", func(t *testing.T) {
t.Setenv("YES", "false")
parser := NewConfigENVParser("")
config, err := parser.Parse()
require.NoError(t, err)
require.NotNil(t, config)
require.NotNil(t, config.Yes)
assert.False(t, *config.Yes)
})
t.Run("Parse with invalid YES value", func(t *testing.T) {
t.Setenv("YES", "maybe")
parser := NewConfigENVParser("")
config, err := parser.Parse()
assert.Nil(t, config)
var fieldErr types.FieldParseErrors
require.ErrorAs(t, err, &fieldErr)
assert.Len(t, fieldErr.Errors, 1)
assert.Equal(t, "YES", fieldErr.Errors[0].Field)
assert.Equal(t, "maybe", fieldErr.Errors[0].Value)
assert.Contains(t, fieldErr.Errors[0].Err.Error(), "invalid value for boolean")
})
t.Run("Parse with SKIP_VERIFY=true", func(t *testing.T) {
t.Setenv("SKIP_VERIFY", "true")
parser := NewConfigENVParser("")
config, err := parser.Parse()
require.NoError(t, err)
require.NotNil(t, config)
require.NotNil(t, config.SkipVerify)
assert.True(t, *config.SkipVerify)
})
t.Run("Parse with invalid SKIP_VERIFY value", func(t *testing.T) {
t.Setenv("SKIP_VERIFY", "invalid")
parser := NewConfigENVParser("")
config, err := parser.Parse()
assert.Nil(t, config)
var fieldErr types.FieldParseErrors
require.ErrorAs(t, err, &fieldErr)
assert.Len(t, fieldErr.Errors, 1)
assert.Equal(t, "SKIP_VERIFY", fieldErr.Errors[0].Field)
assert.Equal(t, "invalid", fieldErr.Errors[0].Value)
assert.Contains(t, fieldErr.Errors[0].Err.Error(), "invalid value for boolean")
})
t.Run("Parse with numeric boolean values", func(t *testing.T) {
t.Setenv("YES", "1")
t.Setenv("SKIP_VERIFY", "0")
parser := NewConfigENVParser("")
config, err := parser.Parse()
require.NoError(t, err)
require.NotNil(t, config)
require.NotNil(t, config.Yes)
assert.True(t, *config.Yes, "1 should be parsed as true")
require.NotNil(t, config.SkipVerify)
assert.False(t, *config.SkipVerify, "0 should be parsed as false")
})
t.Run("Parse boolean fields with prefix", func(t *testing.T) {
t.Setenv("APP_YES", "true")
t.Setenv("APP_SKIP_VERIFY", "false")
parser := NewConfigENVParser("APP")
config, err := parser.Parse()
require.NoError(t, err)
require.NotNil(t, config)
require.NotNil(t, config.Yes)
assert.True(t, *config.Yes)
require.NotNil(t, config.SkipVerify)
assert.False(t, *config.SkipVerify)
})
}
func TestConfigENVParser_Parse_StringFields(t *testing.T) {
t.Run("Parse with METHOD", func(t *testing.T) {
t.Setenv("METHOD", "POST")
parser := NewConfigENVParser("")
config, err := parser.Parse()
require.NoError(t, err)
require.NotNil(t, config)
require.NotNil(t, config.Method)
assert.Equal(t, "POST", *config.Method)
})
t.Run("Parse with empty METHOD", func(t *testing.T) {
t.Setenv("METHOD", "")
parser := NewConfigENVParser("")
config, err := parser.Parse()
require.NoError(t, err)
require.NotNil(t, config)
assert.Nil(t, config.Method)
})
t.Run("Parse METHOD with prefix", func(t *testing.T) {
t.Setenv("TEST_METHOD", "DELETE")
parser := NewConfigENVParser("TEST")
config, err := parser.Parse()
require.NoError(t, err)
require.NotNil(t, config)
require.NotNil(t, config.Method)
assert.Equal(t, "DELETE", *config.Method)
})
}
func TestConfigENVParser_Parse_URL(t *testing.T) {
t.Run("Parse with valid URL", func(t *testing.T) {
t.Setenv("URL", "https://api.example.com/v1/endpoint")
parser := NewConfigENVParser("")
config, err := parser.Parse()
require.NoError(t, err)
require.NotNil(t, config)
require.NotNil(t, config.URL)
assert.Equal(t, "https://api.example.com/v1/endpoint", config.URL.String())
})
t.Run("Parse with invalid URL", func(t *testing.T) {
t.Setenv("URL", "://invalid-url")
parser := NewConfigENVParser("")
config, err := parser.Parse()
assert.Nil(t, config)
var fieldErr types.FieldParseErrors
require.ErrorAs(t, err, &fieldErr)
assert.Len(t, fieldErr.Errors, 1)
assert.Equal(t, "URL", fieldErr.Errors[0].Field)
assert.Equal(t, "://invalid-url", fieldErr.Errors[0].Value)
})
t.Run("Parse URL with query parameters", func(t *testing.T) {
t.Setenv("URL", "https://api.example.com?param1=value1&param2=value2")
parser := NewConfigENVParser("")
config, err := parser.Parse()
require.NoError(t, err)
require.NotNil(t, config)
require.NotNil(t, config.URL)
parsedURL, _ := url.Parse("https://api.example.com?param1=value1&param2=value2")
assert.Equal(t, parsedURL, config.URL)
})
t.Run("Parse URL with prefix", func(t *testing.T) {
t.Setenv("MY_APP_URL", "https://example.com")
parser := NewConfigENVParser("MY_APP")
config, err := parser.Parse()
require.NoError(t, err)
require.NotNil(t, config)
require.NotNil(t, config.URL)
assert.Equal(t, "https://example.com", config.URL.String())
})
}
func TestConfigENVParser_Parse_UintFields(t *testing.T) {
t.Run("Parse with DODOS", func(t *testing.T) {
t.Setenv("DODOS", "10")
parser := NewConfigENVParser("")
config, err := parser.Parse()
require.NoError(t, err)
require.NotNil(t, config)
require.NotNil(t, config.DodosCount)
assert.Equal(t, uint(10), *config.DodosCount)
})
t.Run("Parse with invalid DODOS value", func(t *testing.T) {
t.Setenv("DODOS", "-5")
parser := NewConfigENVParser("")
config, err := parser.Parse()
assert.Nil(t, config)
var fieldErr types.FieldParseErrors
require.ErrorAs(t, err, &fieldErr)
assert.Len(t, fieldErr.Errors, 1)
assert.Equal(t, "DODOS", fieldErr.Errors[0].Field)
assert.Equal(t, "-5", fieldErr.Errors[0].Value)
assert.Contains(t, fieldErr.Errors[0].Err.Error(), "invalid value for unsigned integer")
})
t.Run("Parse with REQUESTS", func(t *testing.T) {
t.Setenv("REQUESTS", "1000")
parser := NewConfigENVParser("")
config, err := parser.Parse()
require.NoError(t, err)
require.NotNil(t, config)
require.NotNil(t, config.RequestCount)
assert.Equal(t, uint(1000), *config.RequestCount)
})
t.Run("Parse with invalid REQUESTS value", func(t *testing.T) {
t.Setenv("REQUESTS", "not_a_number")
parser := NewConfigENVParser("")
config, err := parser.Parse()
assert.Nil(t, config)
var fieldErr types.FieldParseErrors
require.ErrorAs(t, err, &fieldErr)
assert.Len(t, fieldErr.Errors, 1)
assert.Equal(t, "REQUESTS", fieldErr.Errors[0].Field)
assert.Equal(t, "not_a_number", fieldErr.Errors[0].Value)
assert.Contains(t, fieldErr.Errors[0].Err.Error(), "invalid value for unsigned integer")
})
t.Run("Parse with zero values", func(t *testing.T) {
t.Setenv("DODOS", "0")
t.Setenv("REQUESTS", "0")
parser := NewConfigENVParser("")
config, err := parser.Parse()
require.NoError(t, err)
require.NotNil(t, config)
require.NotNil(t, config.DodosCount)
assert.Equal(t, uint(0), *config.DodosCount)
require.NotNil(t, config.RequestCount)
assert.Equal(t, uint(0), *config.RequestCount)
})
t.Run("Parse uint fields with prefix", func(t *testing.T) {
t.Setenv("TEST_DODOS", "5")
t.Setenv("TEST_REQUESTS", "500")
parser := NewConfigENVParser("TEST")
config, err := parser.Parse()
require.NoError(t, err)
require.NotNil(t, config)
require.NotNil(t, config.DodosCount)
assert.Equal(t, uint(5), *config.DodosCount)
require.NotNil(t, config.RequestCount)
assert.Equal(t, uint(500), *config.RequestCount)
})
}
func TestConfigENVParser_Parse_DurationFields(t *testing.T) {
t.Run("Parse with DURATION", func(t *testing.T) {
t.Setenv("DURATION", "5m")
parser := NewConfigENVParser("")
config, err := parser.Parse()
require.NoError(t, err)
require.NotNil(t, config)
require.NotNil(t, config.Duration)
assert.Equal(t, 5*time.Minute, *config.Duration)
})
t.Run("Parse with complex DURATION", func(t *testing.T) {
t.Setenv("DURATION", "1h30m45s")
parser := NewConfigENVParser("")
config, err := parser.Parse()
require.NoError(t, err)
require.NotNil(t, config)
require.NotNil(t, config.Duration)
expected := time.Hour + 30*time.Minute + 45*time.Second
assert.Equal(t, expected, *config.Duration)
})
t.Run("Parse with invalid DURATION", func(t *testing.T) {
t.Setenv("DURATION", "invalid")
parser := NewConfigENVParser("")
config, err := parser.Parse()
assert.Nil(t, config)
var fieldErr types.FieldParseErrors
require.ErrorAs(t, err, &fieldErr)
assert.Len(t, fieldErr.Errors, 1)
assert.Equal(t, "DURATION", fieldErr.Errors[0].Field)
assert.Equal(t, "invalid", fieldErr.Errors[0].Value)
assert.Contains(t, fieldErr.Errors[0].Err.Error(), "invalid value duration")
})
t.Run("Parse with TIMEOUT", func(t *testing.T) {
t.Setenv("TIMEOUT", "30s")
parser := NewConfigENVParser("")
config, err := parser.Parse()
require.NoError(t, err)
require.NotNil(t, config)
require.NotNil(t, config.Timeout)
assert.Equal(t, 30*time.Second, *config.Timeout)
})
t.Run("Parse with invalid TIMEOUT", func(t *testing.T) {
t.Setenv("TIMEOUT", "30")
parser := NewConfigENVParser("")
config, err := parser.Parse()
assert.Nil(t, config)
var fieldErr types.FieldParseErrors
require.ErrorAs(t, err, &fieldErr)
assert.Len(t, fieldErr.Errors, 1)
assert.Equal(t, "TIMEOUT", fieldErr.Errors[0].Field)
assert.Equal(t, "30", fieldErr.Errors[0].Value)
assert.Contains(t, fieldErr.Errors[0].Err.Error(), "invalid value duration")
})
t.Run("Parse with zero durations", func(t *testing.T) {
t.Setenv("DURATION", "0s")
t.Setenv("TIMEOUT", "0s")
parser := NewConfigENVParser("")
config, err := parser.Parse()
require.NoError(t, err)
require.NotNil(t, config)
require.NotNil(t, config.Duration)
assert.Equal(t, time.Duration(0), *config.Duration)
require.NotNil(t, config.Timeout)
assert.Equal(t, time.Duration(0), *config.Timeout)
})
t.Run("Parse duration fields with prefix", func(t *testing.T) {
t.Setenv("APP_DURATION", "10m")
t.Setenv("APP_TIMEOUT", "5s")
parser := NewConfigENVParser("APP")
config, err := parser.Parse()
require.NoError(t, err)
require.NotNil(t, config)
require.NotNil(t, config.Duration)
assert.Equal(t, 10*time.Minute, *config.Duration)
require.NotNil(t, config.Timeout)
assert.Equal(t, 5*time.Second, *config.Timeout)
})
}
func TestConfigENVParser_Parse_CollectionFields(t *testing.T) {
t.Run("Parse with PARAM", func(t *testing.T) {
t.Setenv("PARAM", "key1=value1")
parser := NewConfigENVParser("")
config, err := parser.Parse()
require.NoError(t, err)
require.NotNil(t, config)
assert.Len(t, config.Params, 1)
assert.Equal(t, "key1", config.Params[0].Key)
assert.Equal(t, []string{"value1"}, config.Params[0].Value)
})
t.Run("Parse with HEADER", func(t *testing.T) {
t.Setenv("HEADER", "Content-Type: application/json")
parser := NewConfigENVParser("")
config, err := parser.Parse()
require.NoError(t, err)
require.NotNil(t, config)
assert.Len(t, config.Headers, 1)
assert.Equal(t, "Content-Type", config.Headers[0].Key)
assert.Equal(t, []string{"application/json"}, config.Headers[0].Value)
})
t.Run("Parse with COOKIE", func(t *testing.T) {
t.Setenv("COOKIE", "session=abc123")
parser := NewConfigENVParser("")
config, err := parser.Parse()
require.NoError(t, err)
require.NotNil(t, config)
assert.Len(t, config.Cookies, 1)
assert.Equal(t, "session", config.Cookies[0].Key)
assert.Equal(t, []string{"abc123"}, config.Cookies[0].Value)
})
t.Run("Parse with BODY", func(t *testing.T) {
t.Setenv("BODY", `{"data": "test"}`)
parser := NewConfigENVParser("")
config, err := parser.Parse()
require.NoError(t, err)
require.NotNil(t, config)
assert.Len(t, config.Bodies, 1)
assert.Equal(t, types.Body(`{"data": "test"}`), config.Bodies[0]) //nolint:testifylint
})
t.Run("Parse collection fields with prefix", func(t *testing.T) {
t.Setenv("APP_PARAM", "api_key=secret")
t.Setenv("APP_HEADER", "Authorization: Bearer token")
t.Setenv("APP_COOKIE", "user=john")
t.Setenv("APP_BODY", "request body")
parser := NewConfigENVParser("APP")
config, err := parser.Parse()
require.NoError(t, err)
require.NotNil(t, config)
assert.Len(t, config.Params, 1)
assert.Equal(t, "api_key", config.Params[0].Key)
assert.Len(t, config.Headers, 1)
assert.Equal(t, "Authorization", config.Headers[0].Key)
assert.Len(t, config.Cookies, 1)
assert.Equal(t, "user", config.Cookies[0].Key)
assert.Len(t, config.Bodies, 1)
assert.Equal(t, types.Body("request body"), config.Bodies[0])
})
}
func TestConfigENVParser_Parse_Proxy(t *testing.T) {
t.Run("Parse with valid PROXY", func(t *testing.T) {
t.Setenv("PROXY", "http://proxy.example.com:8080")
parser := NewConfigENVParser("")
config, err := parser.Parse()
require.NoError(t, err)
require.NotNil(t, config)
assert.Len(t, config.Proxies, 1)
assert.Equal(t, "http://proxy.example.com:8080", config.Proxies[0].String())
})
t.Run("Parse with invalid PROXY", func(t *testing.T) {
t.Setenv("PROXY", "://invalid-proxy")
parser := NewConfigENVParser("")
config, err := parser.Parse()
assert.Nil(t, config)
var fieldErr types.FieldParseErrors
require.ErrorAs(t, err, &fieldErr)
assert.Len(t, fieldErr.Errors, 1)
assert.Equal(t, "PROXY", fieldErr.Errors[0].Field)
assert.Equal(t, "://invalid-proxy", fieldErr.Errors[0].Value)
})
t.Run("Parse PROXY with SOCKS5", func(t *testing.T) {
t.Setenv("PROXY", "socks5://127.0.0.1:1080")
parser := NewConfigENVParser("")
config, err := parser.Parse()
require.NoError(t, err)
require.NotNil(t, config)
assert.Len(t, config.Proxies, 1)
assert.Equal(t, "socks5://127.0.0.1:1080", config.Proxies[0].String())
})
t.Run("Parse PROXY with prefix", func(t *testing.T) {
t.Setenv("TEST_PROXY", "http://proxy.test.com:3128")
parser := NewConfigENVParser("TEST")
config, err := parser.Parse()
require.NoError(t, err)
require.NotNil(t, config)
assert.Len(t, config.Proxies, 1)
assert.Equal(t, "http://proxy.test.com:3128", config.Proxies[0].String())
})
}
func TestConfigENVParser_Parse_AllFields(t *testing.T) {
t.Run("Parse with all environment variables set", func(t *testing.T) {
// Set all environment variables
t.Setenv("CONFIG_FILE", "/path/to/config.yaml")
t.Setenv("YES", "true")
t.Setenv("SKIP_VERIFY", "true")
t.Setenv("METHOD", "POST")
t.Setenv("URL", "https://api.example.com/test")
t.Setenv("DODOS", "10")
t.Setenv("REQUESTS", "1000")
t.Setenv("DURATION", "5m")
t.Setenv("TIMEOUT", "30s")
t.Setenv("PARAM", "key=value")
t.Setenv("HEADER", "Content-Type: application/json")
t.Setenv("COOKIE", "session=token")
t.Setenv("BODY", `{"test": "data"}`)
t.Setenv("PROXY", "http://proxy.example.com:8080")
parser := NewConfigENVParser("")
config, err := parser.Parse()
require.NoError(t, err)
require.NotNil(t, config)
// Verify all fields
assert.Len(t, config.Files, 1)
assert.Equal(t, "/path/to/config.yaml", config.Files[0].String())
require.NotNil(t, config.Yes)
assert.True(t, *config.Yes)
require.NotNil(t, config.SkipVerify)
assert.True(t, *config.SkipVerify)
require.NotNil(t, config.Method)
assert.Equal(t, "POST", *config.Method)
require.NotNil(t, config.URL)
assert.Equal(t, "https://api.example.com/test", config.URL.String())
require.NotNil(t, config.DodosCount)
assert.Equal(t, uint(10), *config.DodosCount)
require.NotNil(t, config.RequestCount)
assert.Equal(t, uint(1000), *config.RequestCount)
require.NotNil(t, config.Duration)
assert.Equal(t, 5*time.Minute, *config.Duration)
require.NotNil(t, config.Timeout)
assert.Equal(t, 30*time.Second, *config.Timeout)
assert.Len(t, config.Params, 1)
assert.Equal(t, "key", config.Params[0].Key)
assert.Len(t, config.Headers, 1)
assert.Equal(t, "Content-Type", config.Headers[0].Key)
assert.Len(t, config.Cookies, 1)
assert.Equal(t, "session", config.Cookies[0].Key)
assert.Len(t, config.Bodies, 1)
assert.Equal(t, types.Body(`{"test": "data"}`), config.Bodies[0]) //nolint:testifylint
assert.Len(t, config.Proxies, 1)
assert.Equal(t, "http://proxy.example.com:8080", config.Proxies[0].String())
})
t.Run("Parse with all fields using prefix", func(t *testing.T) {
// Set all environment variables with prefix
t.Setenv("MY_APP_CONFIG_FILE", "/app/config.yml")
t.Setenv("MY_APP_YES", "false")
t.Setenv("MY_APP_SKIP_VERIFY", "false")
t.Setenv("MY_APP_METHOD", "GET")
t.Setenv("MY_APP_URL", "https://example.com")
t.Setenv("MY_APP_DODOS", "5")
t.Setenv("MY_APP_REQUESTS", "500")
t.Setenv("MY_APP_DURATION", "10m")
t.Setenv("MY_APP_TIMEOUT", "10s")
t.Setenv("MY_APP_PARAM", "param1=value1")
t.Setenv("MY_APP_HEADER", "Accept: application/json")
t.Setenv("MY_APP_COOKIE", "auth=token123")
t.Setenv("MY_APP_BODY", "test body")
t.Setenv("MY_APP_PROXY", "http://127.0.0.1:8080")
parser := NewConfigENVParser("MY_APP")
config, err := parser.Parse()
require.NoError(t, err)
require.NotNil(t, config)
// Verify all fields
assert.Len(t, config.Files, 1)
assert.Equal(t, "/app/config.yml", config.Files[0].String())
require.NotNil(t, config.Yes)
assert.False(t, *config.Yes)
require.NotNil(t, config.SkipVerify)
assert.False(t, *config.SkipVerify)
require.NotNil(t, config.Method)
assert.Equal(t, "GET", *config.Method)
require.NotNil(t, config.URL)
assert.Equal(t, "https://example.com", config.URL.String())
require.NotNil(t, config.DodosCount)
assert.Equal(t, uint(5), *config.DodosCount)
require.NotNil(t, config.RequestCount)
assert.Equal(t, uint(500), *config.RequestCount)
require.NotNil(t, config.Duration)
assert.Equal(t, 10*time.Minute, *config.Duration)
require.NotNil(t, config.Timeout)
assert.Equal(t, 10*time.Second, *config.Timeout)
assert.Len(t, config.Params, 1)
assert.Len(t, config.Headers, 1)
assert.Len(t, config.Cookies, 1)
assert.Len(t, config.Bodies, 1)
assert.Len(t, config.Proxies, 1)
})
}
func TestConfigENVParser_Parse_MultipleErrors(t *testing.T) {
t.Run("Parse with multiple field errors", func(t *testing.T) {
// Set multiple invalid values
t.Setenv("CONFIG_FILE", "/path/to/config") // Missing extension
t.Setenv("YES", "invalid_bool")
t.Setenv("SKIP_VERIFY", "not_a_bool")
t.Setenv("URL", "://invalid-url")
t.Setenv("DODOS", "-10")
t.Setenv("REQUESTS", "not_a_number")
t.Setenv("DURATION", "invalid_duration")
t.Setenv("TIMEOUT", "30") // Missing time unit
t.Setenv("PROXY", "://invalid-proxy")
parser := NewConfigENVParser("")
config, err := parser.Parse()
assert.Nil(t, config)
var fieldErr types.FieldParseErrors
require.ErrorAs(t, err, &fieldErr)
// Should have 9 errors
assert.Len(t, fieldErr.Errors, 9)
// Check that all expected fields have errors
errorFields := make(map[string]bool)
for _, e := range fieldErr.Errors {
errorFields[e.Field] = true
}
assert.True(t, errorFields["CONFIG_FILE"])
assert.True(t, errorFields["YES"])
assert.True(t, errorFields["SKIP_VERIFY"])
assert.True(t, errorFields["URL"])
assert.True(t, errorFields["DODOS"])
assert.True(t, errorFields["REQUESTS"])
assert.True(t, errorFields["DURATION"])
assert.True(t, errorFields["TIMEOUT"])
assert.True(t, errorFields["PROXY"])
})
}
func TestConfigENVParser_Parse_EmptyValues(t *testing.T) {
t.Run("Parse with empty string environment variables", func(t *testing.T) {
// Set empty values
t.Setenv("CONFIG_FILE", "")
t.Setenv("YES", "")
t.Setenv("SKIP_VERIFY", "")
t.Setenv("METHOD", "")
t.Setenv("URL", "")
t.Setenv("DODOS", "")
t.Setenv("REQUESTS", "")
t.Setenv("DURATION", "")
t.Setenv("TIMEOUT", "")
t.Setenv("PARAM", "")
t.Setenv("HEADER", "")
t.Setenv("COOKIE", "")
t.Setenv("BODY", "")
t.Setenv("PROXY", "")
parser := NewConfigENVParser("")
config, err := parser.Parse()
// Empty values should not cause errors, fields should just be nil/empty
require.NoError(t, err)
require.NotNil(t, config)
assert.Empty(t, config.Files)
assert.Nil(t, config.Yes)
assert.Nil(t, config.SkipVerify)
assert.Nil(t, config.Method)
assert.Nil(t, config.URL)
assert.Nil(t, config.DodosCount)
assert.Nil(t, config.RequestCount)
assert.Nil(t, config.Duration)
assert.Nil(t, config.Timeout)
assert.Empty(t, config.Params)
assert.Empty(t, config.Headers)
assert.Empty(t, config.Cookies)
assert.Empty(t, config.Bodies)
assert.Empty(t, config.Proxies)
})
}
func TestConfigENVParser_Parse_NoEnvironmentVariables(t *testing.T) {
t.Run("Parse with no environment variables set", func(t *testing.T) {
parser := NewConfigENVParser("")
config, err := parser.Parse()
require.NoError(t, err)
require.NotNil(t, config)
// All fields should be nil or empty
assert.Empty(t, config.Files)
assert.Nil(t, config.Yes)
assert.Nil(t, config.SkipVerify)
assert.Nil(t, config.Method)
assert.Nil(t, config.URL)
assert.Nil(t, config.DodosCount)
assert.Nil(t, config.RequestCount)
assert.Nil(t, config.Duration)
assert.Nil(t, config.Timeout)
assert.Empty(t, config.Params)
assert.Empty(t, config.Headers)
assert.Empty(t, config.Cookies)
assert.Empty(t, config.Bodies)
assert.Empty(t, config.Proxies)
})
t.Run("Parse with prefix but no matching environment variables", func(t *testing.T) {
parser := NewConfigENVParser("NONEXISTENT_PREFIX")
config, err := parser.Parse()
require.NoError(t, err)
require.NotNil(t, config)
// All fields should be nil or empty
assert.Empty(t, config.Files)
assert.Nil(t, config.Yes)
assert.Nil(t, config.SkipVerify)
assert.Nil(t, config.Method)
assert.Nil(t, config.URL)
assert.Nil(t, config.DodosCount)
assert.Nil(t, config.RequestCount)
assert.Nil(t, config.Duration)
assert.Nil(t, config.Timeout)
assert.Empty(t, config.Params)
assert.Empty(t, config.Headers)
assert.Empty(t, config.Cookies)
assert.Empty(t, config.Bodies)
assert.Empty(t, config.Proxies)
})
}
func TestConfigENVParser_InterfaceConformance(t *testing.T) {
t.Run("ConfigENVParser implements IParser interface", func(t *testing.T) {
parser := ConfigENVParser{envPrefix: "TEST"}
// This test verifies that ConfigENVParser implements the IParser interface
var _ IParser = parser
// Also test that the pointer type implements the interface
var _ IParser = &parser
})
}
func TestConfigENVParser_EdgeCases(t *testing.T) {
t.Run("Parse with very long prefix", func(t *testing.T) {
longPrefix := "THIS_IS_A_VERY_LONG_PREFIX_FOR_TESTING_PURPOSES"
envName := longPrefix + "_METHOD"
t.Setenv(envName, "GET")
parser := NewConfigENVParser(longPrefix)
config, err := parser.Parse()
require.NoError(t, err)
require.NotNil(t, config)
require.NotNil(t, config.Method)
assert.Equal(t, "GET", *config.Method)
})
t.Run("Parse with special characters in values", func(t *testing.T) {
t.Setenv("URL", "https://example.com/path?key=value&special=%20%21%40%23")
t.Setenv("HEADER", "X-Special-Header: value with spaces and special!@#$%^&*()")
t.Setenv("BODY", `{"special": "characters!@#$%^&*()_+-=[]{}|;':\",./<>?"}`)
parser := NewConfigENVParser("")
config, err := parser.Parse()
require.NoError(t, err)
require.NotNil(t, config)
require.NotNil(t, config.URL)
assert.Contains(t, config.URL.String(), "special=%20%21%40%23")
assert.Len(t, config.Headers, 1)
assert.Contains(t, config.Headers[0].Value[0], "special!@#$%^&*()")
assert.Len(t, config.Bodies, 1)
assert.Contains(t, string(config.Bodies[0]), "special")
})
t.Run("Parse with maximum uint values", func(t *testing.T) {
t.Setenv("DODOS", "4294967295") // Max uint32
t.Setenv("REQUESTS", "4294967295")
parser := NewConfigENVParser("")
config, err := parser.Parse()
require.NoError(t, err)
require.NotNil(t, config)
require.NotNil(t, config.DodosCount)
assert.Equal(t, uint(4294967295), *config.DodosCount)
require.NotNil(t, config.RequestCount)
assert.Equal(t, uint(4294967295), *config.RequestCount)
})
t.Run("Parse with very large duration values", func(t *testing.T) {
t.Setenv("DURATION", "9999h59m59s")
t.Setenv("TIMEOUT", "999999s")
parser := NewConfigENVParser("")
config, err := parser.Parse()
require.NoError(t, err)
require.NotNil(t, config)
require.NotNil(t, config.Duration)
require.NotNil(t, config.Timeout)
// Just verify they were parsed without checking exact values
assert.Greater(t, *config.Duration, time.Hour)
assert.Greater(t, *config.Timeout, time.Second)
})
}
func TestConfigENVParser_RealWorldScenarios(t *testing.T) {
t.Run("Parse typical production environment configuration", func(t *testing.T) {
// Simulate a production environment configuration
t.Setenv("PROD_CONFIG_FILE", "https://config.example.com/prod.yaml")
t.Setenv("PROD_YES", "false")
t.Setenv("PROD_SKIP_VERIFY", "false")
t.Setenv("PROD_METHOD", "POST")
t.Setenv("PROD_URL", "https://api.production.com/v2/endpoint")
t.Setenv("PROD_DODOS", "100")
t.Setenv("PROD_REQUESTS", "10000")
t.Setenv("PROD_DURATION", "1h")
t.Setenv("PROD_TIMEOUT", "60s")
t.Setenv("PROD_HEADER", "Authorization: Bearer production-token-12345")
t.Setenv("PROD_PROXY", "http://corporate-proxy.example.com:8080")
parser := NewConfigENVParser("PROD")
config, err := parser.Parse()
require.NoError(t, err)
require.NotNil(t, config)
// Verify production settings
assert.Len(t, config.Files, 1)
assert.Equal(t, types.ConfigFileLocationRemote, config.Files[0].LocationType())
require.NotNil(t, config.Yes)
assert.False(t, *config.Yes, "Production should not auto-confirm")
require.NotNil(t, config.SkipVerify)
assert.False(t, *config.SkipVerify, "Production should verify certificates")
require.NotNil(t, config.DodosCount)
assert.Equal(t, uint(100), *config.DodosCount)
require.NotNil(t, config.RequestCount)
assert.Equal(t, uint(10000), *config.RequestCount)
require.NotNil(t, config.Duration)
assert.Equal(t, time.Hour, *config.Duration)
require.NotNil(t, config.Timeout)
assert.Equal(t, 60*time.Second, *config.Timeout)
})
t.Run("Parse development environment configuration", func(t *testing.T) {
// Simulate a development environment configuration
t.Setenv("DEV_CONFIG_FILE", "/local/config/dev.yaml")
t.Setenv("DEV_YES", "true")
t.Setenv("DEV_SKIP_VERIFY", "true")
t.Setenv("DEV_METHOD", "GET")
t.Setenv("DEV_URL", "http://localhost:8080/test")
t.Setenv("DEV_DODOS", "1")
t.Setenv("DEV_REQUESTS", "10")
t.Setenv("DEV_DURATION", "30s")
t.Setenv("DEV_TIMEOUT", "5s")
t.Setenv("DEV_HEADER", "X-Debug: true")
parser := NewConfigENVParser("DEV")
config, err := parser.Parse()
require.NoError(t, err)
require.NotNil(t, config)
// Verify development settings
assert.Len(t, config.Files, 1)
assert.Equal(t, types.ConfigFileLocationLocal, config.Files[0].LocationType())
require.NotNil(t, config.Yes)
assert.True(t, *config.Yes, "Development can auto-confirm")
require.NotNil(t, config.SkipVerify)
assert.True(t, *config.SkipVerify, "Development can skip certificate verification")
require.NotNil(t, config.DodosCount)
assert.Equal(t, uint(1), *config.DodosCount, "Development uses fewer workers")
require.NotNil(t, config.RequestCount)
assert.Equal(t, uint(10), *config.RequestCount, "Development uses fewer requests")
require.NotNil(t, config.Duration)
assert.Equal(t, 30*time.Second, *config.Duration)
require.NotNil(t, config.Timeout)
assert.Equal(t, 5*time.Second, *config.Timeout)
assert.Len(t, config.Headers, 1)
assert.Equal(t, "X-Debug", config.Headers[0].Key)
})
}
func TestConfigENVParser_PartialConfiguration(t *testing.T) {
t.Run("Parse with only critical fields set", func(t *testing.T) {
t.Setenv("URL", "https://api.example.com")
t.Setenv("METHOD", "POST")
parser := NewConfigENVParser("")
config, err := parser.Parse()
require.NoError(t, err)
require.NotNil(t, config)
// Only URL and METHOD should be set
require.NotNil(t, config.URL)
assert.Equal(t, "https://api.example.com", config.URL.String())
require.NotNil(t, config.Method)
assert.Equal(t, "POST", *config.Method)
// Everything else should be nil or empty
assert.Empty(t, config.Files)
assert.Nil(t, config.Yes)
assert.Nil(t, config.SkipVerify)
assert.Nil(t, config.DodosCount)
assert.Nil(t, config.RequestCount)
assert.Nil(t, config.Duration)
assert.Nil(t, config.Timeout)
assert.Empty(t, config.Params)
assert.Empty(t, config.Headers)
assert.Empty(t, config.Cookies)
assert.Empty(t, config.Bodies)
assert.Empty(t, config.Proxies)
})
t.Run("Parse with only optional fields set", func(t *testing.T) {
t.Setenv("HEADER", "User-Agent: CustomAgent/1.0")
t.Setenv("COOKIE", "preferences=dark-mode")
t.Setenv("PARAM", "debug=true")
parser := NewConfigENVParser("")
config, err := parser.Parse()
require.NoError(t, err)
require.NotNil(t, config)
// Only collection fields should have values
assert.Len(t, config.Headers, 1)
assert.Equal(t, "User-Agent", config.Headers[0].Key)
assert.Len(t, config.Cookies, 1)
assert.Equal(t, "preferences", config.Cookies[0].Key)
assert.Len(t, config.Params, 1)
assert.Equal(t, "debug", config.Params[0].Key)
// Core fields should be nil
assert.Nil(t, config.URL)
assert.Nil(t, config.Method)
assert.Nil(t, config.DodosCount)
assert.Nil(t, config.RequestCount)
})
}