Files
dodo/pkg/config/config_test.go

947 lines
31 KiB
Go

package config
import (
"errors"
"net/url"
"testing"
"time"
"github.com/aykhans/dodo/pkg/types"
"github.com/aykhans/dodo/pkg/utils"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestMergeConfig(t *testing.T) {
t.Run("MergeConfig with all fields from new config", func(t *testing.T) {
originalURL, _ := url.Parse("https://original.example.com")
newURL, _ := url.Parse("https://new.example.com")
originalTimeout := 5 * time.Second
newTimeout := 10 * time.Second
originalDuration := 1 * time.Minute
newDuration := 2 * time.Minute
config := &Config{
Files: []types.ConfigFile{},
Method: utils.ToPtr("GET"),
URL: originalURL,
Timeout: &originalTimeout,
DodosCount: utils.ToPtr(uint(1)),
RequestCount: utils.ToPtr(uint(10)),
Duration: &originalDuration,
Yes: utils.ToPtr(false),
SkipVerify: utils.ToPtr(false),
Params: types.Params{{Key: "old", Value: []string{"value"}}},
Headers: types.Headers{{Key: "Old-Header", Value: []string{"old"}}},
Cookies: types.Cookies{{Key: "oldCookie", Value: []string{"oldValue"}}},
Bodies: types.Bodies{types.Body("old body")},
Proxies: types.Proxies{},
}
newConfig := &Config{
Files: []types.ConfigFile{},
Method: utils.ToPtr("POST"),
URL: newURL,
Timeout: &newTimeout,
DodosCount: utils.ToPtr(uint(5)),
RequestCount: utils.ToPtr(uint(20)),
Duration: &newDuration,
Yes: utils.ToPtr(true),
SkipVerify: utils.ToPtr(true),
Params: types.Params{{Key: "new", Value: []string{"value"}}},
Headers: types.Headers{{Key: "New-Header", Value: []string{"new"}}},
Cookies: types.Cookies{{Key: "newCookie", Value: []string{"newValue"}}},
Bodies: types.Bodies{types.Body("new body")},
Proxies: types.Proxies{},
}
config.Merge(newConfig)
assert.Equal(t, "POST", *config.Method)
assert.Equal(t, newURL, config.URL)
assert.Equal(t, newTimeout, *config.Timeout)
assert.Equal(t, uint(5), *config.DodosCount)
assert.Equal(t, uint(20), *config.RequestCount)
assert.Equal(t, newDuration, *config.Duration)
assert.True(t, *config.Yes)
assert.True(t, *config.SkipVerify)
assert.Equal(t, types.Params{{Key: "old", Value: []string{"value"}}, {Key: "new", Value: []string{"value"}}}, config.Params)
assert.Equal(t, types.Headers{{Key: "Old-Header", Value: []string{"old"}}, {Key: "New-Header", Value: []string{"new"}}}, config.Headers)
assert.Equal(t, types.Cookies{{Key: "oldCookie", Value: []string{"oldValue"}}, {Key: "newCookie", Value: []string{"newValue"}}}, config.Cookies)
assert.Equal(t, types.Bodies{types.Body("old body"), types.Body("new body")}, config.Bodies)
assert.Empty(t, config.Proxies)
})
t.Run("MergeConfig with partial fields from new config", func(t *testing.T) {
originalURL, _ := url.Parse("https://original.example.com")
originalTimeout := 5 * time.Second
config := &Config{
Files: []types.ConfigFile{},
Method: utils.ToPtr("GET"),
URL: originalURL,
Timeout: &originalTimeout,
DodosCount: utils.ToPtr(uint(1)),
Headers: types.Headers{{Key: "Original-Header", Value: []string{"original"}}},
}
newURL, _ := url.Parse("https://new.example.com")
newConfig := &Config{
Files: []types.ConfigFile{},
URL: newURL,
DodosCount: utils.ToPtr(uint(10)),
}
config.Merge(newConfig)
assert.Equal(t, "GET", *config.Method, "Method should remain unchanged")
assert.Equal(t, newURL, config.URL, "URL should be updated")
assert.Equal(t, originalTimeout, *config.Timeout, "Timeout should remain unchanged")
assert.Equal(t, uint(10), *config.DodosCount, "DodosCount should be updated")
assert.Equal(t, types.Headers{{Key: "Original-Header", Value: []string{"original"}}}, config.Headers, "Headers should remain unchanged")
})
t.Run("MergeConfig with nil new config fields", func(t *testing.T) {
originalURL, _ := url.Parse("https://original.example.com")
originalTimeout := 5 * time.Second
config := &Config{
Files: []types.ConfigFile{},
Method: utils.ToPtr("GET"),
URL: originalURL,
Timeout: &originalTimeout,
DodosCount: utils.ToPtr(uint(1)),
Yes: utils.ToPtr(false),
SkipVerify: utils.ToPtr(false),
}
newConfig := &Config{
Files: []types.ConfigFile{},
Method: nil,
URL: nil,
Timeout: nil,
DodosCount: nil,
Yes: nil,
SkipVerify: nil,
}
originalConfigCopy := *config
config.Merge(newConfig)
assert.Equal(t, originalConfigCopy.Method, config.Method)
assert.Equal(t, originalConfigCopy.URL, config.URL)
assert.Equal(t, originalConfigCopy.Timeout, config.Timeout)
assert.Equal(t, originalConfigCopy.DodosCount, config.DodosCount)
assert.Equal(t, originalConfigCopy.Yes, config.Yes)
assert.Equal(t, originalConfigCopy.SkipVerify, config.SkipVerify)
})
t.Run("MergeConfig with empty slices", func(t *testing.T) {
configFile, _ := types.ParseConfigFile("original.yml")
config := &Config{
Files: []types.ConfigFile{*configFile},
Params: types.Params{{Key: "original", Value: []string{"value"}}},
Headers: types.Headers{{Key: "Original-Header", Value: []string{"original"}}},
Cookies: types.Cookies{{Key: "originalCookie", Value: []string{"originalValue"}}},
Bodies: types.Bodies{types.Body("original body")},
Proxies: types.Proxies{},
}
newConfig := &Config{
Files: []types.ConfigFile{},
Params: types.Params{},
Headers: types.Headers{},
Cookies: types.Cookies{},
Bodies: types.Bodies{},
Proxies: types.Proxies{},
}
config.Merge(newConfig)
assert.Equal(t, []types.ConfigFile{*configFile}, config.Files, "Empty Files should not override")
assert.Equal(t, types.Params{{Key: "original", Value: []string{"value"}}}, config.Params, "Empty Params should not override")
assert.Equal(t, types.Headers{{Key: "Original-Header", Value: []string{"original"}}}, config.Headers, "Empty Headers should not override")
assert.Equal(t, types.Cookies{{Key: "originalCookie", Value: []string{"originalValue"}}}, config.Cookies, "Empty Cookies should not override")
assert.Equal(t, types.Bodies{types.Body("original body")}, config.Bodies, "Empty Bodies should not override")
assert.Equal(t, types.Proxies{}, config.Proxies, "Empty Proxies should not override")
})
t.Run("MergeConfig with Files field", func(t *testing.T) {
configFile1, _ := types.ParseConfigFile("config1.yml")
configFile2, _ := types.ParseConfigFile("config2.yaml")
config := &Config{
Files: []types.ConfigFile{*configFile1},
Method: utils.ToPtr("GET"),
Headers: types.Headers{{Key: "Original-Header", Value: []string{"original"}}},
}
newConfig := &Config{
Files: []types.ConfigFile{*configFile2},
Method: utils.ToPtr("POST"),
}
config.Merge(newConfig)
assert.Equal(t, "POST", *config.Method, "Method should be updated")
assert.Equal(t, []types.ConfigFile{*configFile1, *configFile2}, config.Files, "Files should be appended")
assert.Equal(t, types.Headers{{Key: "Original-Header", Value: []string{"original"}}}, config.Headers, "Headers should remain unchanged")
})
t.Run("MergeConfig on empty original config", func(t *testing.T) {
config := &Config{}
newURL, _ := url.Parse("https://new.example.com")
newTimeout := 10 * time.Second
newDuration := 2 * time.Minute
newConfig := &Config{
Files: []types.ConfigFile{},
Method: utils.ToPtr("POST"),
URL: newURL,
Timeout: &newTimeout,
DodosCount: utils.ToPtr(uint(5)),
RequestCount: utils.ToPtr(uint(20)),
Duration: &newDuration,
Yes: utils.ToPtr(true),
SkipVerify: utils.ToPtr(true),
Params: types.Params{{Key: "new", Value: []string{"value"}}},
Headers: types.Headers{{Key: "New-Header", Value: []string{"new"}}},
Cookies: types.Cookies{{Key: "newCookie", Value: []string{"newValue"}}},
Bodies: types.Bodies{types.Body("new body")},
Proxies: types.Proxies{},
}
config.Merge(newConfig)
assert.Equal(t, "POST", *config.Method)
assert.Equal(t, newURL, config.URL)
assert.Equal(t, newTimeout, *config.Timeout)
assert.Equal(t, uint(5), *config.DodosCount)
assert.Equal(t, uint(20), *config.RequestCount)
assert.Equal(t, newDuration, *config.Duration)
assert.True(t, *config.Yes)
assert.True(t, *config.SkipVerify)
assert.Equal(t, types.Params{{Key: "new", Value: []string{"value"}}}, config.Params)
assert.Equal(t, types.Headers{{Key: "New-Header", Value: []string{"new"}}}, config.Headers)
assert.Equal(t, types.Cookies{{Key: "newCookie", Value: []string{"newValue"}}}, config.Cookies)
assert.Equal(t, types.Bodies{types.Body("new body")}, config.Bodies)
assert.Empty(t, config.Proxies)
})
}
func TestSetDefaults(t *testing.T) {
t.Run("SetDefaults on empty config", func(t *testing.T) {
config := &Config{}
config.SetDefaults()
require.NotNil(t, config.Method)
assert.Equal(t, Defaults.Method, *config.Method)
require.NotNil(t, config.Timeout)
assert.Equal(t, Defaults.RequestTimeout, *config.Timeout)
require.NotNil(t, config.DodosCount)
assert.Equal(t, Defaults.DodosCount, *config.DodosCount)
require.NotNil(t, config.Yes)
assert.Equal(t, Defaults.Yes, *config.Yes)
require.NotNil(t, config.SkipVerify)
assert.Equal(t, Defaults.SkipVerify, *config.SkipVerify)
assert.True(t, config.Headers.Has("User-Agent"))
assert.Equal(t, Defaults.UserAgent, config.Headers[0].Value[0])
})
t.Run("SetDefaults preserves existing values", func(t *testing.T) {
customTimeout := 30 * time.Second
config := &Config{
Method: utils.ToPtr("POST"),
Timeout: &customTimeout,
DodosCount: utils.ToPtr(uint(10)),
Yes: utils.ToPtr(true),
SkipVerify: utils.ToPtr(true),
Headers: types.Headers{{Key: "User-Agent", Value: []string{"custom-agent"}}},
}
config.SetDefaults()
assert.Equal(t, "POST", *config.Method, "Method should not be overridden")
assert.Equal(t, customTimeout, *config.Timeout, "Timeout should not be overridden")
assert.Equal(t, uint(10), *config.DodosCount, "DodosCount should not be overridden")
assert.True(t, *config.Yes, "Yes should not be overridden")
assert.True(t, *config.SkipVerify, "SkipVerify should not be overridden")
assert.Equal(t, "custom-agent", config.Headers[0].Value[0], "User-Agent should not be overridden")
assert.Len(t, config.Headers, 1, "Should not add duplicate User-Agent")
})
t.Run("SetDefaults adds User-Agent when missing", func(t *testing.T) {
config := &Config{
Files: []types.ConfigFile{},
Headers: types.Headers{{Key: "Content-Type", Value: []string{"application/json"}}},
}
config.SetDefaults()
assert.Len(t, config.Headers, 2)
assert.True(t, config.Headers.Has("User-Agent"))
assert.True(t, config.Headers.Has("Content-Type"))
var userAgentFound bool
for _, h := range config.Headers {
if h.Key == "User-Agent" {
userAgentFound = true
assert.Equal(t, Defaults.UserAgent, h.Value[0])
break
}
}
assert.True(t, userAgentFound, "User-Agent header should be added")
})
t.Run("SetDefaults with partial config", func(t *testing.T) {
config := &Config{
Files: []types.ConfigFile{},
Method: utils.ToPtr("PUT"),
Yes: utils.ToPtr(true),
}
config.SetDefaults()
assert.Equal(t, "PUT", *config.Method, "Existing Method should be preserved")
assert.True(t, *config.Yes, "Existing Yes should be preserved")
require.NotNil(t, config.Timeout)
assert.Equal(t, Defaults.RequestTimeout, *config.Timeout, "Timeout should be set to default")
require.NotNil(t, config.DodosCount)
assert.Equal(t, Defaults.DodosCount, *config.DodosCount, "DodosCount should be set to default")
require.NotNil(t, config.SkipVerify)
assert.Equal(t, Defaults.SkipVerify, *config.SkipVerify, "SkipVerify should be set to default")
assert.True(t, config.Headers.Has("User-Agent"))
})
t.Run("SetDefaults idempotent", func(t *testing.T) {
config := &Config{}
config.SetDefaults()
firstCallHeaders := len(config.Headers)
firstCallMethod := *config.Method
firstCallTimeout := *config.Timeout
config.SetDefaults()
assert.Len(t, config.Headers, firstCallHeaders, "Headers count should not change on second call")
assert.Equal(t, firstCallMethod, *config.Method, "Method should not change on second call")
assert.Equal(t, firstCallTimeout, *config.Timeout, "Timeout should not change on second call")
})
t.Run("SetDefaults with empty Headers initializes correctly", func(t *testing.T) {
config := &Config{
Files: []types.ConfigFile{},
Headers: types.Headers{},
}
config.SetDefaults()
assert.Len(t, config.Headers, 1)
assert.Equal(t, "User-Agent", config.Headers[0].Key)
assert.Equal(t, Defaults.UserAgent, config.Headers[0].Value[0])
})
}
func TestMergeConfig_AppendBehavior(t *testing.T) {
t.Run("MergeConfig appends params with same key", func(t *testing.T) {
config := &Config{
Params: types.Params{{Key: "filter", Value: []string{"active"}}},
}
newConfig := &Config{
Params: types.Params{{Key: "filter", Value: []string{"verified"}}},
}
config.Merge(newConfig)
assert.Len(t, config.Params, 1)
paramValue := config.Params.GetValue("filter")
require.NotNil(t, paramValue)
assert.Equal(t, []string{"active", "verified"}, *paramValue)
})
t.Run("MergeConfig appends headers with same key", func(t *testing.T) {
config := &Config{
Headers: types.Headers{{Key: "Accept", Value: []string{"text/html"}}},
}
newConfig := &Config{
Headers: types.Headers{{Key: "Accept", Value: []string{"application/json"}}},
}
config.Merge(newConfig)
assert.Len(t, config.Headers, 1)
headerValue := config.Headers.GetValue("Accept")
require.NotNil(t, headerValue)
assert.Equal(t, []string{"text/html", "application/json"}, *headerValue)
})
t.Run("MergeConfig appends cookies with same key", func(t *testing.T) {
config := &Config{
Cookies: types.Cookies{{Key: "session", Value: []string{"old_token"}}},
}
newConfig := &Config{
Cookies: types.Cookies{{Key: "session", Value: []string{"new_token"}}},
}
config.Merge(newConfig)
assert.Len(t, config.Cookies, 1)
cookieValue := config.Cookies.GetValue("session")
require.NotNil(t, cookieValue)
assert.Equal(t, []string{"old_token", "new_token"}, *cookieValue)
})
t.Run("MergeConfig appends bodies", func(t *testing.T) {
config := &Config{
Bodies: types.Bodies{types.Body("first body")},
}
newConfig := &Config{
Bodies: types.Bodies{types.Body("second body"), types.Body("third body")},
}
config.Merge(newConfig)
assert.Len(t, config.Bodies, 3)
assert.Equal(t, types.Body("first body"), config.Bodies[0])
assert.Equal(t, types.Body("second body"), config.Bodies[1])
assert.Equal(t, types.Body("third body"), config.Bodies[2])
})
t.Run("MergeConfig appends proxies", func(t *testing.T) {
proxy1URL, _ := url.Parse("http://proxy1.example.com:8080")
proxy2URL, _ := url.Parse("http://proxy2.example.com:8080")
proxy3URL, _ := url.Parse("https://proxy3.example.com:443")
config := &Config{
Proxies: types.Proxies{types.Proxy(*proxy1URL)},
}
newConfig := &Config{
Proxies: types.Proxies{types.Proxy(*proxy2URL), types.Proxy(*proxy3URL)},
}
config.Merge(newConfig)
assert.Len(t, config.Proxies, 3)
assert.Equal(t, "http://proxy1.example.com:8080", config.Proxies[0].String())
assert.Equal(t, "http://proxy2.example.com:8080", config.Proxies[1].String())
assert.Equal(t, "https://proxy3.example.com:443", config.Proxies[2].String())
})
t.Run("MergeConfig appends mixed content", func(t *testing.T) {
config := &Config{
Params: types.Params{{Key: "limit", Value: []string{"10"}}},
Headers: types.Headers{{Key: "Authorization", Value: []string{"Bearer token1"}}},
Cookies: types.Cookies{{Key: "theme", Value: []string{"dark"}}},
Bodies: types.Bodies{types.Body("original")},
}
newConfig := &Config{
Params: types.Params{{Key: "offset", Value: []string{"0"}}, {Key: "limit", Value: []string{"20"}}},
Headers: types.Headers{{Key: "Content-Type", Value: []string{"application/json"}}, {Key: "Authorization", Value: []string{"Bearer token2"}}},
Cookies: types.Cookies{{Key: "lang", Value: []string{"en"}}, {Key: "theme", Value: []string{"light"}}},
Bodies: types.Bodies{types.Body("updated")},
}
config.Merge(newConfig)
// Check params
assert.Len(t, config.Params, 2)
limitValue := config.Params.GetValue("limit")
require.NotNil(t, limitValue)
assert.Equal(t, []string{"10", "20"}, *limitValue)
offsetValue := config.Params.GetValue("offset")
require.NotNil(t, offsetValue)
assert.Equal(t, []string{"0"}, *offsetValue)
// Check headers
assert.Len(t, config.Headers, 2)
authValue := config.Headers.GetValue("Authorization")
require.NotNil(t, authValue)
assert.Equal(t, []string{"Bearer token1", "Bearer token2"}, *authValue)
contentTypeValue := config.Headers.GetValue("Content-Type")
require.NotNil(t, contentTypeValue)
assert.Equal(t, []string{"application/json"}, *contentTypeValue)
// Check cookies
assert.Len(t, config.Cookies, 2)
themeValue := config.Cookies.GetValue("theme")
require.NotNil(t, themeValue)
assert.Equal(t, []string{"dark", "light"}, *themeValue)
langValue := config.Cookies.GetValue("lang")
require.NotNil(t, langValue)
assert.Equal(t, []string{"en"}, *langValue)
// Check bodies
assert.Len(t, config.Bodies, 2)
assert.Equal(t, types.Body("original"), config.Bodies[0])
assert.Equal(t, types.Body("updated"), config.Bodies[1])
})
t.Run("MergeConfig with empty slices does not append", func(t *testing.T) {
config := &Config{
Params: types.Params{{Key: "existing", Value: []string{"value"}}},
Headers: types.Headers{{Key: "Existing-Header", Value: []string{"value"}}},
Cookies: types.Cookies{{Key: "existing", Value: []string{"value"}}},
Bodies: types.Bodies{types.Body("existing")},
Proxies: types.Proxies{},
}
originalParams := len(config.Params)
originalHeaders := len(config.Headers)
originalCookies := len(config.Cookies)
originalBodies := len(config.Bodies)
originalProxies := len(config.Proxies)
newConfig := &Config{
Params: types.Params{},
Headers: types.Headers{},
Cookies: types.Cookies{},
Bodies: types.Bodies{},
Proxies: types.Proxies{},
}
config.Merge(newConfig)
assert.Len(t, config.Params, originalParams, "Empty params should not change existing params")
assert.Len(t, config.Headers, originalHeaders, "Empty headers should not change existing headers")
assert.Len(t, config.Cookies, originalCookies, "Empty cookies should not change existing cookies")
assert.Len(t, config.Bodies, originalBodies, "Empty bodies should not change existing bodies")
assert.Len(t, config.Proxies, originalProxies, "Empty proxies should not change existing proxies")
})
}
func TestParseConfigFile(t *testing.T) {
t.Run("parseConfigFile with maxDepth 0", func(t *testing.T) {
// Create a mock config file
configFile, _ := types.ParseConfigFile("test.yaml")
// Since we can't actually test file reading without a real file,
// we'll test the function's behavior with maxDepth
config, err := parseConfigFile(*configFile, 0)
// The function will return an error because the file doesn't exist
require.Error(t, err)
assert.Nil(t, config)
})
t.Run("parseConfigFile returns ConfigFileReadError", func(t *testing.T) {
configFile, _ := types.ParseConfigFile("/nonexistent/file.yaml")
config, err := parseConfigFile(*configFile, 1)
require.Error(t, err)
assert.Nil(t, config)
// Check if error is of type ConfigFileReadError
var readErr types.ConfigFileReadError
assert.ErrorAs(t, err, &readErr)
})
}
func TestPrintParseErrors(t *testing.T) {
t.Run("printParseErrors with empty value", func(t *testing.T) {
// This function prints to stdout, so we can't easily test its output
// But we can test that it doesn't panic
errors := []types.FieldParseError{
{
Field: "test_field",
Value: "",
Err: errors.New("test error"),
},
}
// Should not panic
assert.NotPanics(t, func() {
printParseErrors("TEST", errors...)
})
})
t.Run("printParseErrors with value", func(t *testing.T) {
errors := []types.FieldParseError{
{
Field: "test_field",
Value: "test_value",
Err: errors.New("test error"),
},
}
// Should not panic
assert.NotPanics(t, func() {
printParseErrors("TEST", errors...)
})
})
t.Run("printParseErrors with multiple errors", func(t *testing.T) {
errors := []types.FieldParseError{
{
Field: "field1",
Value: "value1",
Err: errors.New("error1"),
},
{
Field: "field2",
Value: "",
Err: errors.New("error2"),
},
}
// Should not panic
assert.NotPanics(t, func() {
printParseErrors("TEST", errors...)
})
})
}
func TestValidate(t *testing.T) {
t.Run("Valid config returns no error", func(t *testing.T) {
testURL, _ := url.Parse("https://example.com")
timeout := 30 * time.Second
duration := 1 * time.Minute
config := Config{
Method: utils.ToPtr("GET"),
URL: testURL,
Timeout: &timeout,
DodosCount: utils.ToPtr(uint(5)),
RequestCount: utils.ToPtr(uint(100)),
Duration: &duration,
Yes: utils.ToPtr(true),
SkipVerify: utils.ToPtr(false),
}
err := config.Validate()
assert.NoError(t, err)
})
t.Run("Missing Method returns validation error", func(t *testing.T) {
testURL, _ := url.Parse("https://example.com")
config := Config{
URL: testURL,
DodosCount: utils.ToPtr(uint(5)),
Yes: utils.ToPtr(true),
SkipVerify: utils.ToPtr(false),
}
err := config.Validate()
require.Error(t, err)
var validationErr types.FieldValidationErrors
require.ErrorAs(t, err, &validationErr)
assert.Contains(t, err.Error(), "method is required")
})
t.Run("Missing URL returns validation error", func(t *testing.T) {
config := Config{
Method: utils.ToPtr("GET"),
DodosCount: utils.ToPtr(uint(5)),
Yes: utils.ToPtr(true),
SkipVerify: utils.ToPtr(false),
}
err := config.Validate()
require.Error(t, err)
var validationErr types.FieldValidationErrors
require.ErrorAs(t, err, &validationErr)
assert.Contains(t, err.Error(), "URL is required")
})
t.Run("Invalid URL scheme returns validation error", func(t *testing.T) {
testURL, _ := url.Parse("ftp://example.com")
config := Config{
Method: utils.ToPtr("GET"),
URL: testURL,
DodosCount: utils.ToPtr(uint(5)),
Yes: utils.ToPtr(true),
SkipVerify: utils.ToPtr(false),
}
err := config.Validate()
require.Error(t, err)
var validationErr types.FieldValidationErrors
require.ErrorAs(t, err, &validationErr)
assert.Contains(t, err.Error(), "URL scheme must be one of")
})
t.Run("Missing DodosCount returns validation error", func(t *testing.T) {
testURL, _ := url.Parse("https://example.com")
config := Config{
Method: utils.ToPtr("GET"),
URL: testURL,
Yes: utils.ToPtr(true),
SkipVerify: utils.ToPtr(false),
}
err := config.Validate()
require.Error(t, err)
var validationErr types.FieldValidationErrors
require.ErrorAs(t, err, &validationErr)
assert.Contains(t, err.Error(), "dodos count is required")
})
t.Run("Zero DodosCount returns validation error", func(t *testing.T) {
testURL, _ := url.Parse("https://example.com")
config := Config{
Method: utils.ToPtr("GET"),
URL: testURL,
DodosCount: utils.ToPtr(uint(0)),
Yes: utils.ToPtr(true),
SkipVerify: utils.ToPtr(false),
}
err := config.Validate()
require.Error(t, err)
var validationErr types.FieldValidationErrors
require.ErrorAs(t, err, &validationErr)
assert.Contains(t, err.Error(), "dodos count must be greater than 0")
})
t.Run("Missing both RequestCount and Duration returns validation error", func(t *testing.T) {
testURL, _ := url.Parse("https://example.com")
config := Config{
Method: utils.ToPtr("GET"),
URL: testURL,
DodosCount: utils.ToPtr(uint(5)),
Yes: utils.ToPtr(true),
SkipVerify: utils.ToPtr(false),
}
err := config.Validate()
require.Error(t, err)
var validationErr types.FieldValidationErrors
require.ErrorAs(t, err, &validationErr)
assert.Contains(t, err.Error(), "either request count or duration must be specified")
})
t.Run("Both RequestCount and Duration zero returns validation error", func(t *testing.T) {
testURL, _ := url.Parse("https://example.com")
duration := time.Duration(0)
config := Config{
Method: utils.ToPtr("GET"),
URL: testURL,
DodosCount: utils.ToPtr(uint(5)),
RequestCount: utils.ToPtr(uint(0)),
Duration: &duration,
Yes: utils.ToPtr(true),
SkipVerify: utils.ToPtr(false),
}
err := config.Validate()
require.Error(t, err)
var validationErr types.FieldValidationErrors
require.ErrorAs(t, err, &validationErr)
assert.Contains(t, err.Error(), "both request count and duration cannot be zero")
})
t.Run("Zero RequestCount only returns validation error", func(t *testing.T) {
testURL, _ := url.Parse("https://example.com")
config := Config{
Method: utils.ToPtr("GET"),
URL: testURL,
DodosCount: utils.ToPtr(uint(5)),
RequestCount: utils.ToPtr(uint(0)),
Yes: utils.ToPtr(true),
SkipVerify: utils.ToPtr(false),
}
err := config.Validate()
require.Error(t, err)
var validationErr types.FieldValidationErrors
require.ErrorAs(t, err, &validationErr)
assert.Contains(t, err.Error(), "request count must be greater than 0")
})
t.Run("Zero Duration only returns validation error", func(t *testing.T) {
testURL, _ := url.Parse("https://example.com")
duration := time.Duration(0)
config := Config{
Method: utils.ToPtr("GET"),
URL: testURL,
DodosCount: utils.ToPtr(uint(5)),
Duration: &duration,
Yes: utils.ToPtr(true),
SkipVerify: utils.ToPtr(false),
}
err := config.Validate()
require.Error(t, err)
var validationErr types.FieldValidationErrors
require.ErrorAs(t, err, &validationErr)
assert.Contains(t, err.Error(), "duration must be greater than 0")
})
t.Run("Missing Yes returns validation error", func(t *testing.T) {
testURL, _ := url.Parse("https://example.com")
config := Config{
Method: utils.ToPtr("GET"),
URL: testURL,
DodosCount: utils.ToPtr(uint(5)),
RequestCount: utils.ToPtr(uint(100)),
SkipVerify: utils.ToPtr(false),
}
err := config.Validate()
require.Error(t, err)
var validationErr types.FieldValidationErrors
require.ErrorAs(t, err, &validationErr)
assert.Contains(t, err.Error(), "yes field is required")
})
t.Run("Missing SkipVerify returns validation error", func(t *testing.T) {
testURL, _ := url.Parse("https://example.com")
config := Config{
Method: utils.ToPtr("GET"),
URL: testURL,
DodosCount: utils.ToPtr(uint(5)),
RequestCount: utils.ToPtr(uint(100)),
Yes: utils.ToPtr(true),
}
err := config.Validate()
require.Error(t, err)
var validationErr types.FieldValidationErrors
require.ErrorAs(t, err, &validationErr)
assert.Contains(t, err.Error(), "skip verify field is required")
})
t.Run("Invalid proxy scheme returns validation error", func(t *testing.T) {
testURL, _ := url.Parse("https://example.com")
proxyURL, _ := url.Parse("ftp://proxy.example.com:8080")
config := Config{
Method: utils.ToPtr("GET"),
URL: testURL,
DodosCount: utils.ToPtr(uint(5)),
RequestCount: utils.ToPtr(uint(100)),
Yes: utils.ToPtr(true),
SkipVerify: utils.ToPtr(false),
Proxies: types.Proxies{types.Proxy(*proxyURL)},
}
err := config.Validate()
require.Error(t, err)
var validationErr types.FieldValidationErrors
require.ErrorAs(t, err, &validationErr)
assert.Contains(t, err.Error(), "proxy scheme must be one of")
assert.Contains(t, err.Error(), "Proxy[0]")
})
t.Run("Multiple invalid proxies return validation errors", func(t *testing.T) {
testURL, _ := url.Parse("https://example.com")
proxyURL1, _ := url.Parse("ftp://proxy1.example.com:8080")
proxyURL2, _ := url.Parse("ldap://proxy2.example.com:389")
config := Config{
Method: utils.ToPtr("GET"),
URL: testURL,
DodosCount: utils.ToPtr(uint(5)),
RequestCount: utils.ToPtr(uint(100)),
Yes: utils.ToPtr(true),
SkipVerify: utils.ToPtr(false),
Proxies: types.Proxies{types.Proxy(*proxyURL1), types.Proxy(*proxyURL2)},
}
err := config.Validate()
require.Error(t, err)
var validationErr types.FieldValidationErrors
require.ErrorAs(t, err, &validationErr)
assert.Len(t, validationErr.Errors, 2)
assert.Contains(t, err.Error(), "Proxy[0]")
assert.Contains(t, err.Error(), "Proxy[1]")
})
t.Run("Valid proxy schemes pass validation", func(t *testing.T) {
testURL, _ := url.Parse("https://example.com")
proxyURL1, _ := url.Parse("http://proxy1.example.com:8080")
proxyURL2, _ := url.Parse("socks5://proxy2.example.com:1080")
proxyURL3, _ := url.Parse("socks5h://proxy3.example.com:1080")
config := Config{
Method: utils.ToPtr("GET"),
URL: testURL,
DodosCount: utils.ToPtr(uint(5)),
RequestCount: utils.ToPtr(uint(100)),
Yes: utils.ToPtr(true),
SkipVerify: utils.ToPtr(false),
Proxies: types.Proxies{types.Proxy(*proxyURL1), types.Proxy(*proxyURL2), types.Proxy(*proxyURL3)},
}
err := config.Validate()
assert.NoError(t, err)
})
t.Run("Multiple validation errors are collected", func(t *testing.T) {
config := Config{
// Missing Method, URL, DodosCount, Yes, SkipVerify
// Missing both RequestCount and Duration
}
err := config.Validate()
require.Error(t, err)
var validationErr types.FieldValidationErrors
require.ErrorAs(t, err, &validationErr)
assert.Len(t, validationErr.Errors, 6) // All required fields missing
assert.Contains(t, err.Error(), "method is required")
assert.Contains(t, err.Error(), "URL is required")
assert.Contains(t, err.Error(), "dodos count is required")
assert.Contains(t, err.Error(), "either request count or duration must be specified")
assert.Contains(t, err.Error(), "yes field is required")
assert.Contains(t, err.Error(), "skip verify field is required")
})
t.Run("Valid config with Duration only passes validation", func(t *testing.T) {
testURL, _ := url.Parse("https://example.com")
duration := 30 * time.Second
config := Config{
Method: utils.ToPtr("POST"),
URL: testURL,
DodosCount: utils.ToPtr(uint(10)),
Duration: &duration,
Yes: utils.ToPtr(false),
SkipVerify: utils.ToPtr(true),
}
err := config.Validate()
assert.NoError(t, err)
})
t.Run("Valid config with RequestCount only passes validation", func(t *testing.T) {
testURL, _ := url.Parse("https://example.com")
config := Config{
Method: utils.ToPtr("PUT"),
URL: testURL,
DodosCount: utils.ToPtr(uint(3)),
RequestCount: utils.ToPtr(uint(50)),
Yes: utils.ToPtr(true),
SkipVerify: utils.ToPtr(false),
}
err := config.Validate()
assert.NoError(t, err)
})
}