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.MergeConfig(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) }) 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.MergeConfig(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.MergeConfig(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.MergeConfig(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.MergeConfig(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.MergeConfig(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]) }) }