mirror of
https://github.com/aykhans/dodo.git
synced 2025-09-10 04:50:47 +00:00
Compare commits
3 Commits
fd7c4c6454
...
1eb969480b
Author | SHA1 | Date | |
---|---|---|---|
1eb969480b | |||
7e89fa174b | |||
81383d1ea7 |
@@ -4,16 +4,27 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/aykhans/dodo/pkg/config"
|
||||
"github.com/aykhans/dodo/pkg/config/parser"
|
||||
"github.com/aykhans/dodo/pkg/types"
|
||||
"github.com/aykhans/dodo/pkg/utils"
|
||||
"github.com/jedib0t/go-pretty/v6/text"
|
||||
"github.com/k0kubun/pp/v3"
|
||||
)
|
||||
|
||||
func main() {
|
||||
cliParser := config.NewConfigCLIParser(os.Args)
|
||||
cfg, err := cliParser.Parse()
|
||||
envParser := parser.NewConfigENVParser("DODO")
|
||||
envConfig, err := envParser.Parse()
|
||||
_ = utils.HandleErrorOrDie(err,
|
||||
utils.OnCustomError(func(err types.FieldParseErrors) error {
|
||||
printValidationErrors("ENV", err.Errors...)
|
||||
fmt.Println()
|
||||
os.Exit(1)
|
||||
return nil
|
||||
}),
|
||||
)
|
||||
|
||||
cliParser := parser.NewConfigCLIParser(os.Args)
|
||||
cliConf, err := cliParser.Parse()
|
||||
_ = utils.HandleErrorOrDie(err,
|
||||
utils.OnSentinelError(types.ErrCLINoArgs, func(err error) error {
|
||||
cliParser.PrintHelp()
|
||||
@@ -35,7 +46,9 @@ func main() {
|
||||
}),
|
||||
)
|
||||
|
||||
fmt.Println(cfg)
|
||||
envConfig.Merge(cliConf)
|
||||
pp.Println(cliConf) //nolint
|
||||
pp.Println(envConfig) //nolint
|
||||
}
|
||||
|
||||
func printValidationErrors(parserName string, errors ...types.FieldParseError) {
|
||||
|
5
go.mod
5
go.mod
@@ -4,15 +4,18 @@ go 1.25
|
||||
|
||||
require (
|
||||
github.com/jedib0t/go-pretty/v6 v6.6.8
|
||||
github.com/k0kubun/pp/v3 v3.5.0
|
||||
github.com/stretchr/testify v1.10.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.16 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/rivo/uniseg v0.4.7 // indirect
|
||||
golang.org/x/sys v0.30.0 // indirect
|
||||
golang.org/x/text v0.22.0 // indirect
|
||||
golang.org/x/text v0.26.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
11
go.sum
11
go.sum
@@ -2,6 +2,12 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/jedib0t/go-pretty/v6 v6.6.8 h1:JnnzQeRz2bACBobIaa/r+nqjvws4yEhcmaZ4n1QzsEc=
|
||||
github.com/jedib0t/go-pretty/v6 v6.6.8/go.mod h1:YwC5CE4fJ1HFUDeivSV1r//AmANFHyqczZk+U6BDALU=
|
||||
github.com/k0kubun/pp/v3 v3.5.0 h1:iYNlYA5HJAJvkD4ibuf9c8y6SHM0QFhaBuCqm1zHp0w=
|
||||
github.com/k0kubun/pp/v3 v3.5.0/go.mod h1:5lzno5ZZeEeTV/Ky6vs3g6d1U3WarDrH8k240vMtGro=
|
||||
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
|
||||
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
|
||||
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
@@ -11,10 +17,11 @@ github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
||||
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
|
||||
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
|
||||
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
|
||||
golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M=
|
||||
golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
|
@@ -49,7 +49,7 @@ func NewConfig() *Config {
|
||||
return &Config{}
|
||||
}
|
||||
|
||||
func (config *Config) MergeConfig(newConfig *Config) {
|
||||
func (config *Config) Merge(newConfig *Config) {
|
||||
config.Files = append(config.Files, newConfig.Files...)
|
||||
if newConfig.Method != nil {
|
||||
config.Method = newConfig.Method
|
||||
@@ -76,19 +76,19 @@ func (config *Config) MergeConfig(newConfig *Config) {
|
||||
config.SkipVerify = newConfig.SkipVerify
|
||||
}
|
||||
if len(newConfig.Params) != 0 {
|
||||
config.Params = newConfig.Params
|
||||
config.Params.Append(newConfig.Params...)
|
||||
}
|
||||
if len(newConfig.Headers) != 0 {
|
||||
config.Headers = newConfig.Headers
|
||||
config.Headers.Append(newConfig.Headers...)
|
||||
}
|
||||
if len(newConfig.Cookies) != 0 {
|
||||
config.Cookies = newConfig.Cookies
|
||||
config.Cookies.Append(newConfig.Cookies...)
|
||||
}
|
||||
if len(newConfig.Bodies) != 0 {
|
||||
config.Bodies = newConfig.Bodies
|
||||
config.Bodies.Append(newConfig.Bodies...)
|
||||
}
|
||||
if len(newConfig.Proxies) != 0 {
|
||||
config.Proxies = newConfig.Proxies
|
||||
config.Proxies.Append(newConfig.Proxies...)
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -56,7 +56,7 @@ func TestMergeConfig(t *testing.T) {
|
||||
Proxies: types.Proxies{},
|
||||
}
|
||||
|
||||
config.MergeConfig(newConfig)
|
||||
config.Merge(newConfig)
|
||||
|
||||
assert.Equal(t, "POST", *config.Method)
|
||||
assert.Equal(t, newURL, config.URL)
|
||||
@@ -66,10 +66,10 @@ func TestMergeConfig(t *testing.T) {
|
||||
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.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)
|
||||
})
|
||||
|
||||
@@ -93,7 +93,7 @@ func TestMergeConfig(t *testing.T) {
|
||||
DodosCount: utils.ToPtr(uint(10)),
|
||||
}
|
||||
|
||||
config.MergeConfig(newConfig)
|
||||
config.Merge(newConfig)
|
||||
|
||||
assert.Equal(t, "GET", *config.Method, "Method should remain unchanged")
|
||||
assert.Equal(t, newURL, config.URL, "URL should be updated")
|
||||
@@ -127,7 +127,7 @@ func TestMergeConfig(t *testing.T) {
|
||||
}
|
||||
|
||||
originalConfigCopy := *config
|
||||
config.MergeConfig(newConfig)
|
||||
config.Merge(newConfig)
|
||||
|
||||
assert.Equal(t, originalConfigCopy.Method, config.Method)
|
||||
assert.Equal(t, originalConfigCopy.URL, config.URL)
|
||||
@@ -157,7 +157,7 @@ func TestMergeConfig(t *testing.T) {
|
||||
Proxies: types.Proxies{},
|
||||
}
|
||||
|
||||
config.MergeConfig(newConfig)
|
||||
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")
|
||||
@@ -182,7 +182,7 @@ func TestMergeConfig(t *testing.T) {
|
||||
Method: utils.ToPtr("POST"),
|
||||
}
|
||||
|
||||
config.MergeConfig(newConfig)
|
||||
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")
|
||||
@@ -213,7 +213,7 @@ func TestMergeConfig(t *testing.T) {
|
||||
Proxies: types.Proxies{},
|
||||
}
|
||||
|
||||
config.MergeConfig(newConfig)
|
||||
config.Merge(newConfig)
|
||||
|
||||
assert.Equal(t, "POST", *config.Method)
|
||||
assert.Equal(t, newURL, config.URL)
|
||||
@@ -352,3 +352,176 @@ func TestSetDefaults(t *testing.T) {
|
||||
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")
|
||||
})
|
||||
}
|
||||
|
7
pkg/config/parser/base.go
Normal file
7
pkg/config/parser/base.go
Normal file
@@ -0,0 +1,7 @@
|
||||
package parser
|
||||
|
||||
import "github.com/aykhans/dodo/pkg/config"
|
||||
|
||||
type IParser interface {
|
||||
Parse() (*config.Config, error)
|
||||
}
|
@@ -1,4 +1,4 @@
|
||||
package config
|
||||
package parser
|
||||
|
||||
import (
|
||||
"errors"
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/aykhans/dodo/pkg/config"
|
||||
"github.com/aykhans/dodo/pkg/types"
|
||||
"github.com/aykhans/dodo/pkg/utils"
|
||||
)
|
||||
@@ -52,6 +53,8 @@ Flags:
|
||||
-x, -proxy [string] Proxy for the request (e.g. "http://proxy.example.com:8080")
|
||||
-skip-verify bool Skip SSL/TLS certificate verification (default %v)`
|
||||
|
||||
var _ IParser = ConfigCLIParser{}
|
||||
|
||||
type ConfigCLIParser struct {
|
||||
args []string
|
||||
}
|
||||
@@ -79,13 +82,13 @@ func (arg *stringSliceArg) Set(value string) error {
|
||||
// - types.ErrCLINoArgs
|
||||
// - types.CLIUnexpectedArgsError
|
||||
// - types.FieldParseErrors
|
||||
func (parser *ConfigCLIParser) Parse() (*Config, error) {
|
||||
func (parser ConfigCLIParser) Parse() (*config.Config, error) {
|
||||
flagSet := flag.NewFlagSet("dodo", flag.ExitOnError)
|
||||
|
||||
flagSet.Usage = func() { parser.PrintHelp() }
|
||||
|
||||
var (
|
||||
config = &Config{}
|
||||
config = &config.Config{}
|
||||
configFiles = stringSliceArg{}
|
||||
yes bool
|
||||
skipVerify bool
|
||||
@@ -259,14 +262,14 @@ func (parser *ConfigCLIParser) Parse() (*Config, error) {
|
||||
return config, nil
|
||||
}
|
||||
|
||||
func (parser *ConfigCLIParser) PrintHelp() {
|
||||
func (parser ConfigCLIParser) PrintHelp() {
|
||||
fmt.Printf(
|
||||
cliUsageText+"\n",
|
||||
Defaults.Yes,
|
||||
Defaults.DodosCount,
|
||||
Defaults.RequestTimeout,
|
||||
Defaults.Method,
|
||||
Defaults.SkipVerify,
|
||||
config.Defaults.Yes,
|
||||
config.Defaults.DodosCount,
|
||||
config.Defaults.RequestTimeout,
|
||||
config.Defaults.Method,
|
||||
config.Defaults.SkipVerify,
|
||||
)
|
||||
}
|
||||
|
@@ -1,4 +1,4 @@
|
||||
package config
|
||||
package parser
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/aykhans/dodo/pkg/config"
|
||||
"github.com/aykhans/dodo/pkg/types"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
@@ -485,7 +486,7 @@ func TestConfigCLIParser_PrintHelp(t *testing.T) {
|
||||
assert.Contains(t, output, "-f, -config-file")
|
||||
|
||||
// Verify default values are included
|
||||
assert.Contains(t, output, Defaults.Method)
|
||||
assert.Contains(t, output, config.Defaults.Method)
|
||||
assert.Contains(t, output, "1") // DodosCount default
|
||||
assert.Contains(t, output, "10s") // RequestTimeout default
|
||||
assert.Contains(t, output, "false") // Yes default
|
236
pkg/config/parser/env.go
Normal file
236
pkg/config/parser/env.go
Normal file
@@ -0,0 +1,236 @@
|
||||
package parser
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/aykhans/dodo/pkg/config"
|
||||
"github.com/aykhans/dodo/pkg/types"
|
||||
"github.com/aykhans/dodo/pkg/utils"
|
||||
)
|
||||
|
||||
var _ IParser = ConfigENVParser{}
|
||||
|
||||
type ConfigENVParser struct {
|
||||
envPrefix string
|
||||
}
|
||||
|
||||
func NewConfigENVParser(envPrefix string) *ConfigENVParser {
|
||||
return &ConfigENVParser{envPrefix}
|
||||
}
|
||||
|
||||
// Parse parses env arguments into a Config object.
|
||||
// It can return the following errors:
|
||||
// - types.FieldParseErrors
|
||||
func (parser ConfigENVParser) Parse() (*config.Config, error) {
|
||||
var (
|
||||
config = &config.Config{}
|
||||
fieldParseErrors []types.FieldParseError
|
||||
)
|
||||
|
||||
if configFile := parser.getEnv("CONFIG_FILE"); configFile != "" {
|
||||
configFileParsed, err := types.ParseConfigFile(configFile)
|
||||
|
||||
_ = utils.HandleErrorOrDie(err,
|
||||
utils.OnSentinelError(types.ErrConfigFileExtensionNotFound, func(err error) error {
|
||||
fieldParseErrors = append(
|
||||
fieldParseErrors,
|
||||
*types.NewFieldParseError(
|
||||
parser.getFullEnvName("CONFIG_FILE"),
|
||||
configFile,
|
||||
errors.New("file extension not found"),
|
||||
),
|
||||
)
|
||||
return nil
|
||||
}),
|
||||
utils.OnCustomError(func(err types.RemoteConfigFileParseError) error {
|
||||
fieldParseErrors = append(
|
||||
fieldParseErrors,
|
||||
*types.NewFieldParseError(
|
||||
parser.getFullEnvName("CONFIG_FILE"),
|
||||
configFile,
|
||||
fmt.Errorf("parse error: %w", err),
|
||||
),
|
||||
)
|
||||
return nil
|
||||
}),
|
||||
utils.OnCustomError(func(err types.UnknownConfigFileTypeError) error {
|
||||
fieldParseErrors = append(
|
||||
fieldParseErrors,
|
||||
*types.NewFieldParseError(
|
||||
parser.getFullEnvName("CONFIG_FILE"),
|
||||
configFile,
|
||||
fmt.Errorf("file type '%s' not supported (supported types: %s)", err.Type, types.ConfigFileTypeYAML),
|
||||
),
|
||||
)
|
||||
return nil
|
||||
}),
|
||||
)
|
||||
|
||||
if err == nil {
|
||||
config.Files = append(config.Files, *configFileParsed)
|
||||
}
|
||||
}
|
||||
|
||||
if yes := parser.getEnv("YES"); yes != "" {
|
||||
yesParsed, err := utils.ParseString[bool](yes)
|
||||
if err != nil {
|
||||
fieldParseErrors = append(
|
||||
fieldParseErrors,
|
||||
*types.NewFieldParseError(
|
||||
parser.getFullEnvName("YES"),
|
||||
yes,
|
||||
errors.New("invalid value for boolean, expected 'true' or 'false'"),
|
||||
),
|
||||
)
|
||||
} else {
|
||||
config.Yes = &yesParsed
|
||||
}
|
||||
}
|
||||
|
||||
if skipVerify := parser.getEnv("SKIP_VERIFY"); skipVerify != "" {
|
||||
skipVerifyParsed, err := utils.ParseString[bool](skipVerify)
|
||||
if err != nil {
|
||||
fieldParseErrors = append(
|
||||
fieldParseErrors,
|
||||
*types.NewFieldParseError(
|
||||
parser.getFullEnvName("SKIP_VERIFY"),
|
||||
skipVerify,
|
||||
errors.New("invalid value for boolean, expected 'true' or 'false'"),
|
||||
),
|
||||
)
|
||||
} else {
|
||||
config.SkipVerify = &skipVerifyParsed
|
||||
}
|
||||
}
|
||||
|
||||
if method := parser.getEnv("METHOD"); method != "" {
|
||||
config.Method = &method
|
||||
}
|
||||
|
||||
if urlEnv := parser.getEnv("URL"); urlEnv != "" {
|
||||
urlEnvParsed, err := url.Parse(urlEnv)
|
||||
if err != nil {
|
||||
fieldParseErrors = append(
|
||||
fieldParseErrors,
|
||||
*types.NewFieldParseError(parser.getFullEnvName("URL"), urlEnv, err),
|
||||
)
|
||||
} else {
|
||||
config.URL = urlEnvParsed
|
||||
}
|
||||
}
|
||||
|
||||
if dodos := parser.getEnv("DODOS"); dodos != "" {
|
||||
dodosParsed, err := utils.ParseString[uint](dodos)
|
||||
if err != nil {
|
||||
fieldParseErrors = append(
|
||||
fieldParseErrors,
|
||||
*types.NewFieldParseError(
|
||||
parser.getFullEnvName("DODOS"),
|
||||
dodos,
|
||||
errors.New("invalid value for unsigned integer"),
|
||||
),
|
||||
)
|
||||
} else {
|
||||
config.DodosCount = &dodosParsed
|
||||
}
|
||||
}
|
||||
|
||||
if requests := parser.getEnv("REQUESTS"); requests != "" {
|
||||
requestsParsed, err := utils.ParseString[uint](requests)
|
||||
if err != nil {
|
||||
fieldParseErrors = append(
|
||||
fieldParseErrors,
|
||||
*types.NewFieldParseError(
|
||||
parser.getFullEnvName("REQUESTS"),
|
||||
requests,
|
||||
errors.New("invalid value for unsigned integer"),
|
||||
),
|
||||
)
|
||||
} else {
|
||||
config.RequestCount = &requestsParsed
|
||||
}
|
||||
}
|
||||
|
||||
if duration := parser.getEnv("DURATION"); duration != "" {
|
||||
durationParsed, err := utils.ParseString[time.Duration](duration)
|
||||
if err != nil {
|
||||
fieldParseErrors = append(
|
||||
fieldParseErrors,
|
||||
*types.NewFieldParseError(
|
||||
parser.getFullEnvName("DURATION"),
|
||||
duration,
|
||||
errors.New("invalid value duration, expected a duration string (e.g., '10s', '1h30m')"),
|
||||
),
|
||||
)
|
||||
} else {
|
||||
config.Duration = &durationParsed
|
||||
}
|
||||
}
|
||||
|
||||
if timeout := parser.getEnv("TIMEOUT"); timeout != "" {
|
||||
timeoutParsed, err := utils.ParseString[time.Duration](timeout)
|
||||
if err != nil {
|
||||
fieldParseErrors = append(
|
||||
fieldParseErrors,
|
||||
*types.NewFieldParseError(
|
||||
parser.getFullEnvName("TIMEOUT"),
|
||||
timeout,
|
||||
errors.New("invalid value duration, expected a duration string (e.g., '10s', '1h30m')"),
|
||||
),
|
||||
)
|
||||
} else {
|
||||
config.Timeout = &timeoutParsed
|
||||
}
|
||||
}
|
||||
|
||||
if param := parser.getEnv("PARAM"); param != "" {
|
||||
config.Params.Parse(param)
|
||||
}
|
||||
|
||||
if header := parser.getEnv("HEADER"); header != "" {
|
||||
config.Headers.Parse(header)
|
||||
}
|
||||
|
||||
if cookie := parser.getEnv("COOKIE"); cookie != "" {
|
||||
config.Cookies.Parse(cookie)
|
||||
}
|
||||
|
||||
if body := parser.getEnv("BODY"); body != "" {
|
||||
config.Bodies.Parse(body)
|
||||
}
|
||||
|
||||
if proxy := parser.getEnv("PROXY"); proxy != "" {
|
||||
err := config.Proxies.Parse(proxy)
|
||||
if err != nil {
|
||||
fieldParseErrors = append(
|
||||
fieldParseErrors,
|
||||
*types.NewFieldParseError(
|
||||
parser.getFullEnvName("PROXY"),
|
||||
proxy,
|
||||
err,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if len(fieldParseErrors) > 0 {
|
||||
return nil, types.NewFieldParseErrors(fieldParseErrors)
|
||||
}
|
||||
|
||||
return config, nil
|
||||
}
|
||||
|
||||
func (parser ConfigENVParser) getFullEnvName(envName string) string {
|
||||
if parser.envPrefix == "" {
|
||||
return envName
|
||||
}
|
||||
return parser.envPrefix + "_" + envName
|
||||
}
|
||||
|
||||
func (parser ConfigENVParser) getEnv(envName string) string {
|
||||
return os.Getenv(parser.getFullEnvName(envName))
|
||||
}
|
1129
pkg/config/parser/env_test.go
Normal file
1129
pkg/config/parser/env_test.go
Normal file
File diff suppressed because it is too large
Load Diff
@@ -8,8 +8,8 @@ func (body Body) String() string {
|
||||
|
||||
type Bodies []Body
|
||||
|
||||
func (bodies *Bodies) Append(body Body) {
|
||||
*bodies = append(*bodies, body)
|
||||
func (bodies *Bodies) Append(body ...Body) {
|
||||
*bodies = append(*bodies, body...)
|
||||
}
|
||||
|
||||
func (bodies *Bodies) Parse(rawValues ...string) {
|
||||
|
@@ -49,6 +49,16 @@ func TestBodies_Append(t *testing.T) {
|
||||
assert.Equal(t, Body("third"), (*bodies)[2])
|
||||
})
|
||||
|
||||
t.Run("Append multiple bodies in single call", func(t *testing.T) {
|
||||
bodies := &Bodies{}
|
||||
bodies.Append(Body("first"), Body("second"), Body("third"))
|
||||
|
||||
assert.Len(t, *bodies, 3)
|
||||
assert.Equal(t, Body("first"), (*bodies)[0])
|
||||
assert.Equal(t, Body("second"), (*bodies)[1])
|
||||
assert.Equal(t, Body("third"), (*bodies)[2])
|
||||
})
|
||||
|
||||
t.Run("Append to existing bodies", func(t *testing.T) {
|
||||
bodies := &Bodies{Body("existing")}
|
||||
bodies.Append(Body("new"))
|
||||
@@ -58,6 +68,17 @@ func TestBodies_Append(t *testing.T) {
|
||||
assert.Equal(t, Body("new"), (*bodies)[1])
|
||||
})
|
||||
|
||||
t.Run("Append multiple to existing bodies", func(t *testing.T) {
|
||||
bodies := &Bodies{Body("existing")}
|
||||
bodies.Append(Body("new1"), Body("new2"), Body("new3"))
|
||||
|
||||
assert.Len(t, *bodies, 4)
|
||||
assert.Equal(t, Body("existing"), (*bodies)[0])
|
||||
assert.Equal(t, Body("new1"), (*bodies)[1])
|
||||
assert.Equal(t, Body("new2"), (*bodies)[2])
|
||||
assert.Equal(t, Body("new3"), (*bodies)[3])
|
||||
})
|
||||
|
||||
t.Run("Append empty body", func(t *testing.T) {
|
||||
bodies := &Bodies{}
|
||||
bodies.Append(Body(""))
|
||||
@@ -65,6 +86,29 @@ func TestBodies_Append(t *testing.T) {
|
||||
assert.Len(t, *bodies, 1)
|
||||
assert.Empty(t, (*bodies)[0].String())
|
||||
})
|
||||
|
||||
t.Run("Append no bodies", func(t *testing.T) {
|
||||
bodies := &Bodies{Body("existing")}
|
||||
bodies.Append()
|
||||
|
||||
assert.Len(t, *bodies, 1)
|
||||
assert.Equal(t, Body("existing"), (*bodies)[0])
|
||||
})
|
||||
|
||||
t.Run("Append mixed bodies", func(t *testing.T) {
|
||||
bodies := &Bodies{}
|
||||
bodies.Append(Body("first"), Body(""))
|
||||
bodies.Append(Body("second"))
|
||||
bodies.Append(Body("third"), Body("fourth"), Body("fifth"))
|
||||
|
||||
assert.Len(t, *bodies, 6)
|
||||
assert.Equal(t, Body("first"), (*bodies)[0])
|
||||
assert.Equal(t, Body(""), (*bodies)[1])
|
||||
assert.Equal(t, Body("second"), (*bodies)[2])
|
||||
assert.Equal(t, Body("third"), (*bodies)[3])
|
||||
assert.Equal(t, Body("fourth"), (*bodies)[4])
|
||||
assert.Equal(t, Body("fifth"), (*bodies)[5])
|
||||
})
|
||||
}
|
||||
|
||||
func TestBodies_Parse(t *testing.T) {
|
||||
|
@@ -15,11 +15,13 @@ func (cookies Cookies) GetValue(key string) *[]string {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cookies *Cookies) Append(cookie Cookie) {
|
||||
if item := cookies.GetValue(cookie.Key); item != nil {
|
||||
*item = append(*item, cookie.Value...)
|
||||
} else {
|
||||
*cookies = append(*cookies, cookie)
|
||||
func (cookies *Cookies) Append(cookie ...Cookie) {
|
||||
for _, c := range cookie {
|
||||
if item := cookies.GetValue(c.Key); item != nil {
|
||||
*item = append(*item, c.Value...)
|
||||
} else {
|
||||
*cookies = append(*cookies, c)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -88,6 +88,39 @@ func TestCookies_Append(t *testing.T) {
|
||||
assert.Len(t, *cookies, 3)
|
||||
})
|
||||
|
||||
t.Run("Append multiple cookies in single call", func(t *testing.T) {
|
||||
cookies := &Cookies{}
|
||||
cookies.Append(
|
||||
Cookie{Key: "session", Value: []string{"abc123"}},
|
||||
Cookie{Key: "user", Value: []string{"john"}},
|
||||
Cookie{Key: "token", Value: []string{"xyz789"}},
|
||||
)
|
||||
|
||||
assert.Len(t, *cookies, 3)
|
||||
assert.Equal(t, "session", (*cookies)[0].Key)
|
||||
assert.Equal(t, []string{"abc123"}, (*cookies)[0].Value)
|
||||
assert.Equal(t, "user", (*cookies)[1].Key)
|
||||
assert.Equal(t, []string{"john"}, (*cookies)[1].Value)
|
||||
assert.Equal(t, "token", (*cookies)[2].Key)
|
||||
assert.Equal(t, []string{"xyz789"}, (*cookies)[2].Value)
|
||||
})
|
||||
|
||||
t.Run("Append multiple with existing keys", func(t *testing.T) {
|
||||
cookies := &Cookies{
|
||||
{Key: "prefs", Value: []string{"theme=dark"}},
|
||||
}
|
||||
cookies.Append(
|
||||
Cookie{Key: "prefs", Value: []string{"lang=en"}},
|
||||
Cookie{Key: "prefs", Value: []string{"tz=UTC"}},
|
||||
Cookie{Key: "session", Value: []string{"abc123"}},
|
||||
)
|
||||
|
||||
assert.Len(t, *cookies, 2)
|
||||
assert.Equal(t, []string{"theme=dark", "lang=en", "tz=UTC"}, (*cookies)[0].Value)
|
||||
assert.Equal(t, "session", (*cookies)[1].Key)
|
||||
assert.Equal(t, []string{"abc123"}, (*cookies)[1].Value)
|
||||
})
|
||||
|
||||
t.Run("Append multiple values at once", func(t *testing.T) {
|
||||
cookies := &Cookies{
|
||||
{Key: "tags", Value: []string{"tag1"}},
|
||||
@@ -105,6 +138,88 @@ func TestCookies_Append(t *testing.T) {
|
||||
assert.Len(t, *cookies, 1)
|
||||
assert.Equal(t, []string{""}, (*cookies)[0].Value)
|
||||
})
|
||||
|
||||
t.Run("Append no cookies", func(t *testing.T) {
|
||||
cookies := &Cookies{
|
||||
{Key: "existing", Value: []string{"value"}},
|
||||
}
|
||||
cookies.Append()
|
||||
|
||||
assert.Len(t, *cookies, 1)
|
||||
assert.Equal(t, "existing", (*cookies)[0].Key)
|
||||
assert.Equal(t, []string{"value"}, (*cookies)[0].Value)
|
||||
})
|
||||
|
||||
t.Run("Append mixed new and existing cookies", func(t *testing.T) {
|
||||
cookies := &Cookies{
|
||||
{Key: "session", Value: []string{"old_session"}},
|
||||
{Key: "theme", Value: []string{"dark"}},
|
||||
}
|
||||
cookies.Append(
|
||||
Cookie{Key: "session", Value: []string{"new_session"}},
|
||||
Cookie{Key: "user", Value: []string{"john"}},
|
||||
Cookie{Key: "theme", Value: []string{"light"}},
|
||||
Cookie{Key: "lang", Value: []string{"en"}},
|
||||
)
|
||||
|
||||
assert.Len(t, *cookies, 4)
|
||||
|
||||
sessionValue := cookies.GetValue("session")
|
||||
require.NotNil(t, sessionValue)
|
||||
assert.Equal(t, []string{"old_session", "new_session"}, *sessionValue)
|
||||
|
||||
themeValue := cookies.GetValue("theme")
|
||||
require.NotNil(t, themeValue)
|
||||
assert.Equal(t, []string{"dark", "light"}, *themeValue)
|
||||
|
||||
userValue := cookies.GetValue("user")
|
||||
require.NotNil(t, userValue)
|
||||
assert.Equal(t, []string{"john"}, *userValue)
|
||||
|
||||
langValue := cookies.GetValue("lang")
|
||||
require.NotNil(t, langValue)
|
||||
assert.Equal(t, []string{"en"}, *langValue)
|
||||
})
|
||||
|
||||
t.Run("Append cookies with multiple values", func(t *testing.T) {
|
||||
cookies := &Cookies{}
|
||||
cookies.Append(
|
||||
Cookie{Key: "permissions", Value: []string{"read", "write", "delete"}},
|
||||
Cookie{Key: "features", Value: []string{"advanced", "beta"}},
|
||||
)
|
||||
|
||||
assert.Len(t, *cookies, 2)
|
||||
assert.Equal(t, []string{"read", "write", "delete"}, (*cookies)[0].Value)
|
||||
assert.Equal(t, []string{"advanced", "beta"}, (*cookies)[1].Value)
|
||||
})
|
||||
|
||||
t.Run("Append typical session cookies", func(t *testing.T) {
|
||||
cookies := &Cookies{}
|
||||
cookies.Append(
|
||||
Cookie{Key: "JSESSIONID", Value: []string{"A1B2C3D4E5F6"}},
|
||||
Cookie{Key: "PHPSESSID", Value: []string{"abcdef123456"}},
|
||||
Cookie{Key: "session_token", Value: []string{"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9"}},
|
||||
Cookie{Key: "__Secure-next-auth.session-token", Value: []string{"secure_token_123"}},
|
||||
)
|
||||
|
||||
assert.Len(t, *cookies, 4)
|
||||
|
||||
jsessionValue := cookies.GetValue("JSESSIONID")
|
||||
require.NotNil(t, jsessionValue)
|
||||
assert.Equal(t, []string{"A1B2C3D4E5F6"}, *jsessionValue)
|
||||
|
||||
phpValue := cookies.GetValue("PHPSESSID")
|
||||
require.NotNil(t, phpValue)
|
||||
assert.Equal(t, []string{"abcdef123456"}, *phpValue)
|
||||
|
||||
sessionValue := cookies.GetValue("session_token")
|
||||
require.NotNil(t, sessionValue)
|
||||
assert.Equal(t, []string{"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9"}, *sessionValue)
|
||||
|
||||
secureValue := cookies.GetValue("__Secure-next-auth.session-token")
|
||||
require.NotNil(t, secureValue)
|
||||
assert.Equal(t, []string{"secure_token_123"}, *secureValue)
|
||||
})
|
||||
}
|
||||
|
||||
func TestCookies_Parse(t *testing.T) {
|
||||
|
@@ -25,11 +25,13 @@ func (headers Headers) GetValue(key string) *[]string {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (headers *Headers) Append(header Header) {
|
||||
if item := headers.GetValue(header.Key); item != nil {
|
||||
*item = append(*item, header.Value...)
|
||||
} else {
|
||||
*headers = append(*headers, header)
|
||||
func (headers *Headers) Append(header ...Header) {
|
||||
for _, h := range header {
|
||||
if item := headers.GetValue(h.Key); item != nil {
|
||||
*item = append(*item, h.Value...)
|
||||
} else {
|
||||
*headers = append(*headers, h)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -125,6 +125,39 @@ func TestHeaders_Append(t *testing.T) {
|
||||
assert.Len(t, *headers, 3)
|
||||
})
|
||||
|
||||
t.Run("Append multiple headers in single call", func(t *testing.T) {
|
||||
headers := &Headers{}
|
||||
headers.Append(
|
||||
Header{Key: "Content-Type", Value: []string{"application/json"}},
|
||||
Header{Key: "Authorization", Value: []string{"Bearer token"}},
|
||||
Header{Key: "Accept", Value: []string{"*/*"}},
|
||||
)
|
||||
|
||||
assert.Len(t, *headers, 3)
|
||||
assert.Equal(t, "Content-Type", (*headers)[0].Key)
|
||||
assert.Equal(t, []string{"application/json"}, (*headers)[0].Value)
|
||||
assert.Equal(t, "Authorization", (*headers)[1].Key)
|
||||
assert.Equal(t, []string{"Bearer token"}, (*headers)[1].Value)
|
||||
assert.Equal(t, "Accept", (*headers)[2].Key)
|
||||
assert.Equal(t, []string{"*/*"}, (*headers)[2].Value)
|
||||
})
|
||||
|
||||
t.Run("Append multiple with existing keys", func(t *testing.T) {
|
||||
headers := &Headers{
|
||||
{Key: "Accept", Value: []string{"text/html"}},
|
||||
}
|
||||
headers.Append(
|
||||
Header{Key: "Accept", Value: []string{"application/json"}},
|
||||
Header{Key: "Accept", Value: []string{"application/xml"}},
|
||||
Header{Key: "Content-Type", Value: []string{"text/plain"}},
|
||||
)
|
||||
|
||||
assert.Len(t, *headers, 2)
|
||||
assert.Equal(t, []string{"text/html", "application/json", "application/xml"}, (*headers)[0].Value)
|
||||
assert.Equal(t, "Content-Type", (*headers)[1].Key)
|
||||
assert.Equal(t, []string{"text/plain"}, (*headers)[1].Value)
|
||||
})
|
||||
|
||||
t.Run("Append multiple values at once", func(t *testing.T) {
|
||||
headers := &Headers{
|
||||
{Key: "Accept-Language", Value: []string{"en"}},
|
||||
@@ -142,6 +175,78 @@ func TestHeaders_Append(t *testing.T) {
|
||||
assert.Len(t, *headers, 1)
|
||||
assert.Equal(t, []string{""}, (*headers)[0].Value)
|
||||
})
|
||||
|
||||
t.Run("Append no headers", func(t *testing.T) {
|
||||
headers := &Headers{
|
||||
{Key: "Existing", Value: []string{"value"}},
|
||||
}
|
||||
headers.Append()
|
||||
|
||||
assert.Len(t, *headers, 1)
|
||||
assert.Equal(t, "Existing", (*headers)[0].Key)
|
||||
assert.Equal(t, []string{"value"}, (*headers)[0].Value)
|
||||
})
|
||||
|
||||
t.Run("Append mixed new and existing headers", func(t *testing.T) {
|
||||
headers := &Headers{
|
||||
{Key: "Cache-Control", Value: []string{"no-cache"}},
|
||||
{Key: "Accept", Value: []string{"text/html"}},
|
||||
}
|
||||
headers.Append(
|
||||
Header{Key: "Cache-Control", Value: []string{"no-store"}},
|
||||
Header{Key: "User-Agent", Value: []string{"Mozilla/5.0"}},
|
||||
Header{Key: "Accept", Value: []string{"application/json"}},
|
||||
Header{Key: "X-Custom", Value: []string{"custom-value"}},
|
||||
)
|
||||
|
||||
assert.Len(t, *headers, 4)
|
||||
|
||||
cacheValue := headers.GetValue("Cache-Control")
|
||||
require.NotNil(t, cacheValue)
|
||||
assert.Equal(t, []string{"no-cache", "no-store"}, *cacheValue)
|
||||
|
||||
acceptValue := headers.GetValue("Accept")
|
||||
require.NotNil(t, acceptValue)
|
||||
assert.Equal(t, []string{"text/html", "application/json"}, *acceptValue)
|
||||
|
||||
userAgentValue := headers.GetValue("User-Agent")
|
||||
require.NotNil(t, userAgentValue)
|
||||
assert.Equal(t, []string{"Mozilla/5.0"}, *userAgentValue)
|
||||
|
||||
customValue := headers.GetValue("X-Custom")
|
||||
require.NotNil(t, customValue)
|
||||
assert.Equal(t, []string{"custom-value"}, *customValue)
|
||||
})
|
||||
|
||||
t.Run("Append headers with multiple values", func(t *testing.T) {
|
||||
headers := &Headers{}
|
||||
headers.Append(
|
||||
Header{Key: "Accept", Value: []string{"text/html", "application/json", "application/xml"}},
|
||||
Header{Key: "Accept-Encoding", Value: []string{"gzip", "deflate"}},
|
||||
)
|
||||
|
||||
assert.Len(t, *headers, 2)
|
||||
assert.Equal(t, []string{"text/html", "application/json", "application/xml"}, (*headers)[0].Value)
|
||||
assert.Equal(t, []string{"gzip", "deflate"}, (*headers)[1].Value)
|
||||
})
|
||||
|
||||
t.Run("Append common HTTP headers", func(t *testing.T) {
|
||||
headers := &Headers{}
|
||||
headers.Append(
|
||||
Header{Key: "Content-Type", Value: []string{"application/json"}},
|
||||
Header{Key: "Authorization", Value: []string{"Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9"}},
|
||||
Header{Key: "User-Agent", Value: []string{"Go-http-client/1.1"}},
|
||||
Header{Key: "Accept", Value: []string{"*/*"}},
|
||||
Header{Key: "X-Requested-With", Value: []string{"XMLHttpRequest"}},
|
||||
)
|
||||
|
||||
assert.Len(t, *headers, 5)
|
||||
assert.True(t, headers.Has("Content-Type"))
|
||||
assert.True(t, headers.Has("Authorization"))
|
||||
assert.True(t, headers.Has("User-Agent"))
|
||||
assert.True(t, headers.Has("Accept"))
|
||||
assert.True(t, headers.Has("X-Requested-With"))
|
||||
})
|
||||
}
|
||||
|
||||
func TestHeaders_Parse(t *testing.T) {
|
||||
|
@@ -15,11 +15,13 @@ func (params Params) GetValue(key string) *[]string {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (params *Params) Append(param Param) {
|
||||
if item := params.GetValue(param.Key); item != nil {
|
||||
*item = append(*item, param.Value...)
|
||||
} else {
|
||||
*params = append(*params, param)
|
||||
func (params *Params) Append(param ...Param) {
|
||||
for _, p := range param {
|
||||
if item := params.GetValue(p.Key); item != nil {
|
||||
*item = append(*item, p.Value...)
|
||||
} else {
|
||||
*params = append(*params, p)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -88,6 +88,39 @@ func TestParams_Append(t *testing.T) {
|
||||
assert.Len(t, *params, 3)
|
||||
})
|
||||
|
||||
t.Run("Append multiple parameters in single call", func(t *testing.T) {
|
||||
params := &Params{}
|
||||
params.Append(
|
||||
Param{Key: "name", Value: []string{"john"}},
|
||||
Param{Key: "age", Value: []string{"25"}},
|
||||
Param{Key: "city", Value: []string{"NYC"}},
|
||||
)
|
||||
|
||||
assert.Len(t, *params, 3)
|
||||
assert.Equal(t, "name", (*params)[0].Key)
|
||||
assert.Equal(t, []string{"john"}, (*params)[0].Value)
|
||||
assert.Equal(t, "age", (*params)[1].Key)
|
||||
assert.Equal(t, []string{"25"}, (*params)[1].Value)
|
||||
assert.Equal(t, "city", (*params)[2].Key)
|
||||
assert.Equal(t, []string{"NYC"}, (*params)[2].Value)
|
||||
})
|
||||
|
||||
t.Run("Append multiple with existing keys", func(t *testing.T) {
|
||||
params := &Params{
|
||||
{Key: "tags", Value: []string{"go"}},
|
||||
}
|
||||
params.Append(
|
||||
Param{Key: "tags", Value: []string{"test"}},
|
||||
Param{Key: "tags", Value: []string{"api"}},
|
||||
Param{Key: "version", Value: []string{"1.0"}},
|
||||
)
|
||||
|
||||
assert.Len(t, *params, 2)
|
||||
assert.Equal(t, []string{"go", "test", "api"}, (*params)[0].Value)
|
||||
assert.Equal(t, "version", (*params)[1].Key)
|
||||
assert.Equal(t, []string{"1.0"}, (*params)[1].Value)
|
||||
})
|
||||
|
||||
t.Run("Append multiple values at once", func(t *testing.T) {
|
||||
params := &Params{
|
||||
{Key: "colors", Value: []string{"red"}},
|
||||
@@ -105,6 +138,60 @@ func TestParams_Append(t *testing.T) {
|
||||
assert.Len(t, *params, 1)
|
||||
assert.Equal(t, []string{""}, (*params)[0].Value)
|
||||
})
|
||||
|
||||
t.Run("Append no parameters", func(t *testing.T) {
|
||||
params := &Params{
|
||||
{Key: "existing", Value: []string{"value"}},
|
||||
}
|
||||
params.Append()
|
||||
|
||||
assert.Len(t, *params, 1)
|
||||
assert.Equal(t, "existing", (*params)[0].Key)
|
||||
assert.Equal(t, []string{"value"}, (*params)[0].Value)
|
||||
})
|
||||
|
||||
t.Run("Append mixed new and existing keys", func(t *testing.T) {
|
||||
params := &Params{
|
||||
{Key: "filter", Value: []string{"active"}},
|
||||
{Key: "sort", Value: []string{"name"}},
|
||||
}
|
||||
params.Append(
|
||||
Param{Key: "filter", Value: []string{"verified"}},
|
||||
Param{Key: "limit", Value: []string{"10"}},
|
||||
Param{Key: "sort", Value: []string{"date"}},
|
||||
Param{Key: "offset", Value: []string{"0"}},
|
||||
)
|
||||
|
||||
assert.Len(t, *params, 4)
|
||||
|
||||
filterValue := params.GetValue("filter")
|
||||
require.NotNil(t, filterValue)
|
||||
assert.Equal(t, []string{"active", "verified"}, *filterValue)
|
||||
|
||||
sortValue := params.GetValue("sort")
|
||||
require.NotNil(t, sortValue)
|
||||
assert.Equal(t, []string{"name", "date"}, *sortValue)
|
||||
|
||||
limitValue := params.GetValue("limit")
|
||||
require.NotNil(t, limitValue)
|
||||
assert.Equal(t, []string{"10"}, *limitValue)
|
||||
|
||||
offsetValue := params.GetValue("offset")
|
||||
require.NotNil(t, offsetValue)
|
||||
assert.Equal(t, []string{"0"}, *offsetValue)
|
||||
})
|
||||
|
||||
t.Run("Append parameters with multiple values", func(t *testing.T) {
|
||||
params := &Params{}
|
||||
params.Append(
|
||||
Param{Key: "ids", Value: []string{"1", "2", "3"}},
|
||||
Param{Key: "types", Value: []string{"A", "B"}},
|
||||
)
|
||||
|
||||
assert.Len(t, *params, 2)
|
||||
assert.Equal(t, []string{"1", "2", "3"}, (*params)[0].Value)
|
||||
assert.Equal(t, []string{"A", "B"}, (*params)[1].Value)
|
||||
})
|
||||
}
|
||||
|
||||
func TestParams_Parse(t *testing.T) {
|
||||
|
@@ -13,8 +13,8 @@ func (proxy Proxy) String() string {
|
||||
|
||||
type Proxies []Proxy
|
||||
|
||||
func (proxies *Proxies) Append(proxy Proxy) {
|
||||
*proxies = append(*proxies, proxy)
|
||||
func (proxies *Proxies) Append(proxy ...Proxy) {
|
||||
*proxies = append(*proxies, proxy...)
|
||||
}
|
||||
|
||||
func (proxies *Proxies) Parse(rawValue string) error {
|
||||
|
@@ -63,7 +63,7 @@ func TestProxies_Append(t *testing.T) {
|
||||
assert.Equal(t, "http://proxy1.example.com:8080", (*proxies)[0].String())
|
||||
})
|
||||
|
||||
t.Run("Append multiple proxies", func(t *testing.T) {
|
||||
t.Run("Append multiple proxies sequentially", func(t *testing.T) {
|
||||
proxies := &Proxies{}
|
||||
|
||||
url1, err := url.Parse("http://proxy1.example.com:8080")
|
||||
@@ -83,6 +83,24 @@ func TestProxies_Append(t *testing.T) {
|
||||
assert.Equal(t, "https://proxy3.example.com:443", (*proxies)[2].String())
|
||||
})
|
||||
|
||||
t.Run("Append multiple proxies in single call", func(t *testing.T) {
|
||||
proxies := &Proxies{}
|
||||
|
||||
url1, err := url.Parse("http://proxy1.example.com:8080")
|
||||
require.NoError(t, err)
|
||||
url2, err := url.Parse("http://proxy2.example.com:8081")
|
||||
require.NoError(t, err)
|
||||
url3, err := url.Parse("https://proxy3.example.com:443")
|
||||
require.NoError(t, err)
|
||||
|
||||
proxies.Append(Proxy(*url1), Proxy(*url2), Proxy(*url3))
|
||||
|
||||
assert.Len(t, *proxies, 3)
|
||||
assert.Equal(t, "http://proxy1.example.com:8080", (*proxies)[0].String())
|
||||
assert.Equal(t, "http://proxy2.example.com:8081", (*proxies)[1].String())
|
||||
assert.Equal(t, "https://proxy3.example.com:443", (*proxies)[2].String())
|
||||
})
|
||||
|
||||
t.Run("Append to existing proxies", func(t *testing.T) {
|
||||
existingURL, err := url.Parse("http://existing.example.com:8080")
|
||||
require.NoError(t, err)
|
||||
@@ -98,6 +116,77 @@ func TestProxies_Append(t *testing.T) {
|
||||
assert.Equal(t, "http://existing.example.com:8080", (*proxies)[0].String())
|
||||
assert.Equal(t, "http://new.example.com:8081", (*proxies)[1].String())
|
||||
})
|
||||
|
||||
t.Run("Append multiple to existing proxies", func(t *testing.T) {
|
||||
existingURL, err := url.Parse("http://existing.example.com:8080")
|
||||
require.NoError(t, err)
|
||||
|
||||
proxies := &Proxies{Proxy(*existingURL)}
|
||||
|
||||
url1, err := url.Parse("http://new1.example.com:8081")
|
||||
require.NoError(t, err)
|
||||
url2, err := url.Parse("http://new2.example.com:8082")
|
||||
require.NoError(t, err)
|
||||
url3, err := url.Parse("https://new3.example.com:443")
|
||||
require.NoError(t, err)
|
||||
|
||||
proxies.Append(Proxy(*url1), Proxy(*url2), Proxy(*url3))
|
||||
|
||||
assert.Len(t, *proxies, 4)
|
||||
assert.Equal(t, "http://existing.example.com:8080", (*proxies)[0].String())
|
||||
assert.Equal(t, "http://new1.example.com:8081", (*proxies)[1].String())
|
||||
assert.Equal(t, "http://new2.example.com:8082", (*proxies)[2].String())
|
||||
assert.Equal(t, "https://new3.example.com:443", (*proxies)[3].String())
|
||||
})
|
||||
|
||||
t.Run("Append no proxies", func(t *testing.T) {
|
||||
existingURL, err := url.Parse("http://existing.example.com:8080")
|
||||
require.NoError(t, err)
|
||||
|
||||
proxies := &Proxies{Proxy(*existingURL)}
|
||||
proxies.Append()
|
||||
|
||||
assert.Len(t, *proxies, 1)
|
||||
assert.Equal(t, "http://existing.example.com:8080", (*proxies)[0].String())
|
||||
})
|
||||
|
||||
t.Run("Append mixed proxies", func(t *testing.T) {
|
||||
proxies := &Proxies{}
|
||||
|
||||
url1, err := url.Parse("http://proxy1.example.com:8080")
|
||||
require.NoError(t, err)
|
||||
url2, err := url.Parse("socks5://proxy2.example.com:1080")
|
||||
require.NoError(t, err)
|
||||
url3, err := url.Parse("https://proxy3.example.com:443")
|
||||
require.NoError(t, err)
|
||||
url4, err := url.Parse("http://proxy4.example.com:3128")
|
||||
require.NoError(t, err)
|
||||
|
||||
proxies.Append(Proxy(*url1), Proxy(*url2))
|
||||
proxies.Append(Proxy(*url3))
|
||||
proxies.Append(Proxy(*url4))
|
||||
|
||||
assert.Len(t, *proxies, 4)
|
||||
assert.Equal(t, "http://proxy1.example.com:8080", (*proxies)[0].String())
|
||||
assert.Equal(t, "socks5://proxy2.example.com:1080", (*proxies)[1].String())
|
||||
assert.Equal(t, "https://proxy3.example.com:443", (*proxies)[2].String())
|
||||
assert.Equal(t, "http://proxy4.example.com:3128", (*proxies)[3].String())
|
||||
})
|
||||
|
||||
t.Run("Append proxies with authentication", func(t *testing.T) {
|
||||
proxies := &Proxies{}
|
||||
|
||||
url1, err := url.Parse("http://user1:pass1@proxy1.example.com:8080")
|
||||
require.NoError(t, err)
|
||||
url2, err := url.Parse("https://user2:pass2@proxy2.example.com:443")
|
||||
require.NoError(t, err)
|
||||
|
||||
proxies.Append(Proxy(*url1), Proxy(*url2))
|
||||
|
||||
assert.Len(t, *proxies, 2)
|
||||
assert.Equal(t, "http://user1:pass1@proxy1.example.com:8080", (*proxies)[0].String())
|
||||
assert.Equal(t, "https://user2:pass2@proxy2.example.com:443", (*proxies)[1].String())
|
||||
})
|
||||
}
|
||||
|
||||
func TestProxies_Parse(t *testing.T) {
|
||||
|
Reference in New Issue
Block a user