mirror of
https://github.com/aykhans/dodo.git
synced 2025-04-18 18:39:43 +00:00
515 lines
18 KiB
Go
515 lines
18 KiB
Go
package config
|
|
|
|
import (
|
|
"net/url"
|
|
"os"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/aykhans/dodo/types"
|
|
"github.com/aykhans/dodo/utils"
|
|
"github.com/stretchr/testify/assert"
|
|
)
|
|
|
|
func TestNewConfig(t *testing.T) {
|
|
config := NewConfig()
|
|
assert.NotNil(t, config)
|
|
assert.IsType(t, &Config{}, config)
|
|
}
|
|
|
|
func TestNewRequestConfig(t *testing.T) {
|
|
// Create a sample Config object
|
|
urlObj := types.RequestURL{}
|
|
urlObj.Set("https://example.com")
|
|
|
|
conf := &Config{
|
|
Method: utils.ToPtr("GET"),
|
|
URL: &urlObj,
|
|
Timeout: &types.Timeout{Duration: 5 * time.Second},
|
|
DodosCount: utils.ToPtr(uint(10)),
|
|
RequestCount: utils.ToPtr(uint(100)),
|
|
Duration: &types.Duration{Duration: 1 * time.Minute},
|
|
Yes: utils.ToPtr(true),
|
|
Params: types.Params{{Key: "key1", Value: []string{"value1"}}},
|
|
Headers: types.Headers{{Key: "User-Agent", Value: []string{"TestAgent"}}},
|
|
Cookies: types.Cookies{{Key: "session", Value: []string{"123"}}},
|
|
Body: types.Body{"test body"},
|
|
Proxies: types.Proxies{url.URL{Scheme: "http", Host: "proxy.example.com:8080"}},
|
|
}
|
|
|
|
// Call the function being tested
|
|
rc := NewRequestConfig(conf)
|
|
|
|
// Assert the fields are correctly mapped
|
|
assert.Equal(t, "GET", rc.Method)
|
|
assert.Equal(t, "https://example.com", rc.URL.String())
|
|
assert.Equal(t, 5*time.Second, rc.Timeout)
|
|
assert.Equal(t, uint(10), rc.DodosCount)
|
|
assert.Equal(t, uint(100), rc.RequestCount)
|
|
assert.Equal(t, 1*time.Minute, rc.Duration)
|
|
assert.True(t, rc.Yes)
|
|
assert.Equal(t, types.Params{{Key: "key1", Value: []string{"value1"}}}, rc.Params)
|
|
assert.Equal(t, types.Headers{{Key: "User-Agent", Value: []string{"TestAgent"}}}, rc.Headers)
|
|
assert.Equal(t, types.Cookies{{Key: "session", Value: []string{"123"}}}, rc.Cookies)
|
|
assert.Equal(t, types.Body{"test body"}, rc.Body)
|
|
assert.Equal(t, types.Proxies{url.URL{Scheme: "http", Host: "proxy.example.com:8080"}}, rc.Proxies)
|
|
}
|
|
|
|
func TestGetValidDodosCountForRequests(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
dodosCount uint
|
|
requestCount uint
|
|
expected uint
|
|
}{
|
|
{
|
|
name: "no request count limit",
|
|
dodosCount: 10,
|
|
requestCount: 0,
|
|
expected: 10,
|
|
},
|
|
{
|
|
name: "dodos count less than request count",
|
|
dodosCount: 5,
|
|
requestCount: 100,
|
|
expected: 5,
|
|
},
|
|
{
|
|
name: "dodos count greater than request count",
|
|
dodosCount: 100,
|
|
requestCount: 10,
|
|
expected: 10,
|
|
},
|
|
{
|
|
name: "dodos count equal to request count",
|
|
dodosCount: 50,
|
|
requestCount: 50,
|
|
expected: 50,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
rc := &RequestConfig{
|
|
DodosCount: tt.dodosCount,
|
|
RequestCount: tt.requestCount,
|
|
}
|
|
result := rc.GetValidDodosCountForRequests()
|
|
assert.Equal(t, tt.expected, result)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGetMaxConns(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
dodosCount uint
|
|
requestCount uint
|
|
minConns uint
|
|
expected uint
|
|
}{
|
|
{
|
|
name: "min connections higher than valid dodos count",
|
|
dodosCount: 10,
|
|
requestCount: 0,
|
|
minConns: 20,
|
|
expected: 30, // 20 * 150%
|
|
},
|
|
{
|
|
name: "min connections lower than valid dodos count",
|
|
dodosCount: 30,
|
|
requestCount: 0,
|
|
minConns: 10,
|
|
expected: 45, // 30 * 150%
|
|
},
|
|
{
|
|
name: "request count limits dodos count",
|
|
dodosCount: 100,
|
|
requestCount: 20,
|
|
minConns: 5,
|
|
expected: 30, // 20 * 150%
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
rc := &RequestConfig{
|
|
DodosCount: tt.dodosCount,
|
|
RequestCount: tt.requestCount,
|
|
}
|
|
result := rc.GetMaxConns(tt.minConns)
|
|
assert.Equal(t, tt.expected, result)
|
|
})
|
|
}
|
|
}
|
|
|
|
// Skip the Print test as it's mainly a formatting function
|
|
// that uses external table rendering library
|
|
func TestRequestConfigPrint(t *testing.T) {
|
|
// Create a sample RequestConfig
|
|
rc := &RequestConfig{
|
|
Method: "GET",
|
|
URL: url.URL{Scheme: "https", Host: "example.com"},
|
|
Timeout: 5 * time.Second,
|
|
DodosCount: 10,
|
|
RequestCount: 100,
|
|
Duration: 1 * time.Minute,
|
|
Params: types.Params{{Key: "param1", Value: []string{"value1"}}},
|
|
Headers: types.Headers{{Key: "User-Agent", Value: []string{"TestAgent"}}},
|
|
Cookies: types.Cookies{{Key: "session", Value: []string{"123"}}},
|
|
Body: types.Body{"test body"},
|
|
Proxies: types.Proxies{url.URL{Scheme: "http", Host: "proxy.example.com:8080"}},
|
|
}
|
|
|
|
// We'll just call the function to ensure it doesn't panic
|
|
// Redirect output to /dev/null
|
|
origStdout := os.Stdout
|
|
devNull, _ := os.Open(os.DevNull)
|
|
os.Stdout = devNull
|
|
|
|
// Call the function
|
|
rc.Print()
|
|
|
|
// Restore stdout
|
|
os.Stdout = origStdout
|
|
|
|
// No assertions needed, we're just checking that it doesn't panic
|
|
}
|
|
|
|
func TestConfigValidate(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
config *Config
|
|
expectedErrors int
|
|
expectURLQuery bool
|
|
}{
|
|
{
|
|
name: "valid config",
|
|
config: &Config{
|
|
Method: utils.ToPtr("GET"),
|
|
URL: func() *types.RequestURL { u := &types.RequestURL{}; u.Set("https://example.com"); return u }(),
|
|
Timeout: &types.Timeout{Duration: 5 * time.Second},
|
|
DodosCount: utils.ToPtr(uint(10)),
|
|
RequestCount: utils.ToPtr(uint(100)),
|
|
Duration: &types.Duration{Duration: 1 * time.Minute},
|
|
},
|
|
expectedErrors: 0,
|
|
expectURLQuery: false,
|
|
},
|
|
{
|
|
name: "missing URL",
|
|
config: &Config{
|
|
Method: utils.ToPtr("GET"),
|
|
URL: nil,
|
|
Timeout: &types.Timeout{Duration: 5 * time.Second},
|
|
DodosCount: utils.ToPtr(uint(10)),
|
|
RequestCount: utils.ToPtr(uint(100)),
|
|
Duration: &types.Duration{Duration: 1 * time.Minute},
|
|
},
|
|
expectedErrors: 1,
|
|
expectURLQuery: false,
|
|
},
|
|
{
|
|
name: "missing method",
|
|
config: &Config{
|
|
Method: nil,
|
|
URL: func() *types.RequestURL { u := &types.RequestURL{}; u.Set("https://example.com"); return u }(),
|
|
Timeout: &types.Timeout{Duration: 5 * time.Second},
|
|
DodosCount: utils.ToPtr(uint(10)),
|
|
RequestCount: utils.ToPtr(uint(100)),
|
|
Duration: &types.Duration{Duration: 1 * time.Minute},
|
|
},
|
|
expectedErrors: 1,
|
|
expectURLQuery: false,
|
|
},
|
|
{
|
|
name: "invalid URL scheme",
|
|
config: &Config{
|
|
Method: utils.ToPtr("GET"),
|
|
URL: func() *types.RequestURL { u := &types.RequestURL{}; u.Scheme = "ftp"; u.Host = "example.com"; return u }(),
|
|
Timeout: &types.Timeout{Duration: 5 * time.Second},
|
|
DodosCount: utils.ToPtr(uint(10)),
|
|
RequestCount: utils.ToPtr(uint(100)),
|
|
Duration: &types.Duration{Duration: 1 * time.Minute},
|
|
},
|
|
expectedErrors: 1,
|
|
expectURLQuery: false,
|
|
},
|
|
{
|
|
name: "missing both duration and request count",
|
|
config: &Config{
|
|
Method: utils.ToPtr("GET"),
|
|
URL: func() *types.RequestURL { u := &types.RequestURL{}; u.Set("https://example.com"); return u }(),
|
|
Timeout: &types.Timeout{Duration: 5 * time.Second},
|
|
DodosCount: utils.ToPtr(uint(10)),
|
|
RequestCount: utils.ToPtr(uint(0)),
|
|
Duration: &types.Duration{Duration: 0},
|
|
},
|
|
expectedErrors: 1,
|
|
expectURLQuery: false,
|
|
},
|
|
{
|
|
name: "URL with query parameters",
|
|
config: &Config{
|
|
Method: utils.ToPtr("GET"),
|
|
URL: func() *types.RequestURL {
|
|
u := &types.RequestURL{}
|
|
u.Set("https://example.com?param1=value1¶m2=value2")
|
|
return u
|
|
}(),
|
|
Timeout: &types.Timeout{Duration: 5 * time.Second},
|
|
DodosCount: utils.ToPtr(uint(10)),
|
|
RequestCount: utils.ToPtr(uint(100)),
|
|
Duration: &types.Duration{Duration: 1 * time.Minute},
|
|
},
|
|
expectedErrors: 0,
|
|
expectURLQuery: true,
|
|
},
|
|
{
|
|
name: "invalid proxy scheme",
|
|
config: &Config{
|
|
Method: utils.ToPtr("GET"),
|
|
URL: func() *types.RequestURL { u := &types.RequestURL{}; u.Set("https://example.com"); return u }(),
|
|
Timeout: &types.Timeout{Duration: 5 * time.Second},
|
|
DodosCount: utils.ToPtr(uint(10)),
|
|
RequestCount: utils.ToPtr(uint(100)),
|
|
Duration: &types.Duration{Duration: 1 * time.Minute},
|
|
Proxies: types.Proxies{url.URL{Scheme: "invalid", Host: "proxy.example.com:8080"}},
|
|
},
|
|
expectedErrors: 1,
|
|
expectURLQuery: false,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
errors := tt.config.Validate()
|
|
|
|
// Check number of errors
|
|
assert.Len(t, errors, tt.expectedErrors)
|
|
|
|
// Check if URL query parameters are extracted properly
|
|
if tt.expectURLQuery {
|
|
assert.Empty(t, tt.config.URL.RawQuery)
|
|
assert.NotEmpty(t, tt.config.Params)
|
|
found := false
|
|
for _, param := range tt.config.Params {
|
|
if param.Key == "param1" && len(param.Value) > 0 && param.Value[0] == "value1" {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
assert.True(t, found, "Expected param1=value1 in Params but not found")
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestMergeConfig(t *testing.T) {
|
|
baseURL := func() *types.RequestURL { u := &types.RequestURL{}; u.Set("https://example.com"); return u }()
|
|
newURL := func() *types.RequestURL { u := &types.RequestURL{}; u.Set("https://new-example.com"); return u }()
|
|
|
|
baseConfig := &Config{
|
|
Method: utils.ToPtr("GET"),
|
|
URL: baseURL,
|
|
Timeout: &types.Timeout{Duration: 5 * time.Second},
|
|
DodosCount: utils.ToPtr(uint(10)),
|
|
RequestCount: utils.ToPtr(uint(100)),
|
|
Duration: &types.Duration{Duration: 1 * time.Minute},
|
|
Yes: utils.ToPtr(false),
|
|
Params: types.Params{{Key: "base-param", Value: []string{"base-value"}}},
|
|
Headers: types.Headers{{Key: "base-header", Value: []string{"base-value"}}},
|
|
Cookies: types.Cookies{{Key: "base-cookie", Value: []string{"base-value"}}},
|
|
Body: types.Body{"base-body"},
|
|
Proxies: types.Proxies{url.URL{Scheme: "http", Host: "base-proxy.example.com:8080"}},
|
|
}
|
|
|
|
tests := []struct {
|
|
name string
|
|
newConfig *Config
|
|
assertions func(t *testing.T, result *Config)
|
|
}{
|
|
{
|
|
name: "merge all fields",
|
|
newConfig: &Config{
|
|
Method: utils.ToPtr("POST"),
|
|
URL: newURL,
|
|
Timeout: &types.Timeout{Duration: 10 * time.Second},
|
|
DodosCount: utils.ToPtr(uint(20)),
|
|
RequestCount: utils.ToPtr(uint(200)),
|
|
Duration: &types.Duration{Duration: 2 * time.Minute},
|
|
Yes: utils.ToPtr(true),
|
|
Params: types.Params{{Key: "new-param", Value: []string{"new-value"}}},
|
|
Headers: types.Headers{{Key: "new-header", Value: []string{"new-value"}}},
|
|
Cookies: types.Cookies{{Key: "new-cookie", Value: []string{"new-value"}}},
|
|
Body: types.Body{"new-body"},
|
|
Proxies: types.Proxies{url.URL{Scheme: "http", Host: "new-proxy.example.com:8080"}},
|
|
},
|
|
assertions: func(t *testing.T, result *Config) {
|
|
assert.Equal(t, "POST", *result.Method)
|
|
assert.Equal(t, "https://new-example.com", result.URL.String())
|
|
assert.Equal(t, 10*time.Second, result.Timeout.Duration)
|
|
assert.Equal(t, uint(20), *result.DodosCount)
|
|
assert.Equal(t, uint(200), *result.RequestCount)
|
|
assert.Equal(t, 2*time.Minute, result.Duration.Duration)
|
|
assert.True(t, *result.Yes)
|
|
assert.Equal(t, types.Params{{Key: "new-param", Value: []string{"new-value"}}}, result.Params)
|
|
assert.Equal(t, types.Headers{{Key: "new-header", Value: []string{"new-value"}}}, result.Headers)
|
|
assert.Equal(t, types.Cookies{{Key: "new-cookie", Value: []string{"new-value"}}}, result.Cookies)
|
|
assert.Equal(t, types.Body{"new-body"}, result.Body)
|
|
assert.Equal(t, types.Proxies{url.URL{Scheme: "http", Host: "new-proxy.example.com:8080"}}, result.Proxies)
|
|
},
|
|
},
|
|
{
|
|
name: "merge only specified fields",
|
|
newConfig: &Config{
|
|
Method: utils.ToPtr("POST"),
|
|
URL: newURL,
|
|
Yes: utils.ToPtr(true),
|
|
},
|
|
assertions: func(t *testing.T, result *Config) {
|
|
assert.Equal(t, "POST", *result.Method)
|
|
assert.Equal(t, "https://new-example.com", result.URL.String())
|
|
assert.Equal(t, 5*time.Second, result.Timeout.Duration) // unchanged
|
|
assert.Equal(t, uint(10), *result.DodosCount) // unchanged
|
|
assert.Equal(t, uint(100), *result.RequestCount) // unchanged
|
|
assert.Equal(t, 1*time.Minute, result.Duration.Duration) // unchanged
|
|
assert.True(t, *result.Yes) // changed
|
|
assert.Equal(t, types.Params{{Key: "base-param", Value: []string{"base-value"}}}, result.Params) // unchanged
|
|
assert.Equal(t, types.Headers{{Key: "base-header", Value: []string{"base-value"}}}, result.Headers) // unchanged
|
|
assert.Equal(t, types.Cookies{{Key: "base-cookie", Value: []string{"base-value"}}}, result.Cookies) // unchanged
|
|
assert.Equal(t, types.Body{"base-body"}, result.Body) // unchanged
|
|
assert.Equal(t, types.Proxies{url.URL{Scheme: "http", Host: "base-proxy.example.com:8080"}}, result.Proxies) // unchanged
|
|
},
|
|
},
|
|
{
|
|
name: "merge empty config",
|
|
newConfig: &Config{},
|
|
assertions: func(t *testing.T, result *Config) {
|
|
// All fields should remain unchanged
|
|
assert.Equal(t, "GET", *result.Method)
|
|
assert.Equal(t, "https://example.com", result.URL.String())
|
|
assert.Equal(t, 5*time.Second, result.Timeout.Duration)
|
|
assert.Equal(t, uint(10), *result.DodosCount)
|
|
assert.Equal(t, uint(100), *result.RequestCount)
|
|
assert.Equal(t, 1*time.Minute, result.Duration.Duration)
|
|
assert.False(t, *result.Yes)
|
|
assert.Equal(t, types.Params{{Key: "base-param", Value: []string{"base-value"}}}, result.Params)
|
|
assert.Equal(t, types.Headers{{Key: "base-header", Value: []string{"base-value"}}}, result.Headers)
|
|
assert.Equal(t, types.Cookies{{Key: "base-cookie", Value: []string{"base-value"}}}, result.Cookies)
|
|
assert.Equal(t, types.Body{"base-body"}, result.Body)
|
|
assert.Equal(t, types.Proxies{url.URL{Scheme: "http", Host: "base-proxy.example.com:8080"}}, result.Proxies)
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
// Create a copy of the base config for each test
|
|
baseURL := func() *types.RequestURL { u := &types.RequestURL{}; u.Set("https://example.com"); return u }()
|
|
testConfig := &Config{
|
|
Method: utils.ToPtr(*baseConfig.Method),
|
|
URL: baseURL,
|
|
Timeout: &types.Timeout{Duration: baseConfig.Timeout.Duration},
|
|
DodosCount: utils.ToPtr(*baseConfig.DodosCount),
|
|
RequestCount: utils.ToPtr(*baseConfig.RequestCount),
|
|
Duration: &types.Duration{Duration: baseConfig.Duration.Duration},
|
|
Yes: utils.ToPtr(*baseConfig.Yes),
|
|
Params: append(types.Params{}, baseConfig.Params...),
|
|
Headers: append(types.Headers{}, baseConfig.Headers...),
|
|
Cookies: append(types.Cookies{}, baseConfig.Cookies...),
|
|
Body: append(types.Body{}, baseConfig.Body...),
|
|
Proxies: append(types.Proxies{}, baseConfig.Proxies...),
|
|
}
|
|
|
|
// Call the function being tested
|
|
testConfig.MergeConfig(tt.newConfig)
|
|
|
|
// Run assertions
|
|
tt.assertions(t, testConfig)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestSetDefaults(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
config *Config
|
|
validate func(t *testing.T, config *Config)
|
|
}{
|
|
{
|
|
name: "empty config",
|
|
config: &Config{},
|
|
validate: func(t *testing.T, config *Config) {
|
|
assert.Equal(t, DefaultMethod, *config.Method)
|
|
assert.Equal(t, DefaultTimeout, config.Timeout.Duration)
|
|
assert.Equal(t, DefaultDodosCount, *config.DodosCount)
|
|
assert.Equal(t, DefaultRequestCount, *config.RequestCount)
|
|
assert.Equal(t, DefaultDuration, config.Duration.Duration)
|
|
assert.Equal(t, DefaultYes, *config.Yes)
|
|
assert.True(t, config.Headers.Has("User-Agent"))
|
|
userAgent := config.Headers.GetValue("User-Agent")
|
|
assert.NotNil(t, userAgent)
|
|
assert.Contains(t, (*userAgent)[0], DefaultUserAgent)
|
|
},
|
|
},
|
|
{
|
|
name: "partial config",
|
|
config: &Config{
|
|
Method: utils.ToPtr("POST"),
|
|
Timeout: &types.Timeout{Duration: 30 * time.Second},
|
|
Headers: types.Headers{{Key: "Custom-Header", Value: []string{"value"}}},
|
|
},
|
|
validate: func(t *testing.T, config *Config) {
|
|
assert.Equal(t, "POST", *config.Method) // should keep existing value
|
|
assert.Equal(t, 30*time.Second, config.Timeout.Duration) // should keep existing value
|
|
assert.Equal(t, DefaultDodosCount, *config.DodosCount) // should set default
|
|
assert.Equal(t, DefaultRequestCount, *config.RequestCount) // should set default
|
|
assert.Equal(t, DefaultDuration, config.Duration.Duration) // should set default
|
|
assert.Equal(t, DefaultYes, *config.Yes) // should set default
|
|
assert.True(t, config.Headers.Has("Custom-Header")) // should keep existing header
|
|
assert.True(t, config.Headers.Has("User-Agent")) // should add User-Agent
|
|
userAgent := config.Headers.GetValue("User-Agent")
|
|
assert.NotNil(t, userAgent)
|
|
assert.Contains(t, (*userAgent)[0], DefaultUserAgent)
|
|
},
|
|
},
|
|
{
|
|
name: "complete config",
|
|
config: &Config{
|
|
Method: utils.ToPtr("DELETE"),
|
|
URL: func() *types.RequestURL { u := &types.RequestURL{}; u.Set("https://example.com"); return u }(),
|
|
Timeout: &types.Timeout{Duration: 15 * time.Second},
|
|
DodosCount: utils.ToPtr(uint(5)),
|
|
RequestCount: utils.ToPtr(uint(500)),
|
|
Duration: &types.Duration{Duration: 5 * time.Minute},
|
|
Yes: utils.ToPtr(true),
|
|
Headers: types.Headers{{Key: "User-Agent", Value: []string{"CustomAgent"}}},
|
|
},
|
|
validate: func(t *testing.T, config *Config) {
|
|
assert.Equal(t, "DELETE", *config.Method)
|
|
assert.Equal(t, 15*time.Second, config.Timeout.Duration)
|
|
assert.Equal(t, uint(5), *config.DodosCount)
|
|
assert.Equal(t, uint(500), *config.RequestCount)
|
|
assert.Equal(t, 5*time.Minute, config.Duration.Duration)
|
|
assert.True(t, *config.Yes)
|
|
assert.True(t, config.Headers.Has("User-Agent"))
|
|
userAgent := config.Headers.GetValue("User-Agent")
|
|
assert.NotNil(t, userAgent)
|
|
assert.Equal(t, "CustomAgent", (*userAgent)[0]) // should keep custom user agent
|
|
assert.NotEqual(t, DefaultUserAgent, (*userAgent)[0]) // should not overwrite
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
// Call the function being tested
|
|
tt.config.SetDefaults()
|
|
|
|
// Validate the result
|
|
tt.validate(t, tt.config)
|
|
})
|
|
}
|
|
}
|