Files
dodo/pkg/config/config_test.go

528 lines
19 KiB
Go

package config
import (
"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")
})
}