mirror of
https://github.com/aykhans/dodo.git
synced 2025-09-05 10:43:37 +00:00
Here we go again...
This commit is contained in:
23
pkg/types/body.go
Normal file
23
pkg/types/body.go
Normal file
@@ -0,0 +1,23 @@
|
||||
package types
|
||||
|
||||
type Body string
|
||||
|
||||
func (body Body) String() string {
|
||||
return string(body)
|
||||
}
|
||||
|
||||
type Bodies []Body
|
||||
|
||||
func (bodies *Bodies) Append(body Body) {
|
||||
*bodies = append(*bodies, body)
|
||||
}
|
||||
|
||||
func (bodies *Bodies) Parse(rawValues ...string) {
|
||||
for _, rawValue := range rawValues {
|
||||
bodies.Append(ParseBody(rawValue))
|
||||
}
|
||||
}
|
||||
|
||||
func ParseBody(rawValue string) Body {
|
||||
return Body(rawValue)
|
||||
}
|
160
pkg/types/body_test.go
Normal file
160
pkg/types/body_test.go
Normal file
@@ -0,0 +1,160 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestBody_String(t *testing.T) {
|
||||
t.Run("Body String returns correct value", func(t *testing.T) {
|
||||
body := Body("test body content")
|
||||
assert.Equal(t, "test body content", body.String())
|
||||
})
|
||||
|
||||
t.Run("Body String with empty body", func(t *testing.T) {
|
||||
body := Body("")
|
||||
assert.Empty(t, body.String())
|
||||
})
|
||||
|
||||
t.Run("Body String with JSON", func(t *testing.T) {
|
||||
body := Body(`{"key": "value", "number": 42}`)
|
||||
assert.JSONEq(t, `{"key": "value", "number": 42}`, body.String())
|
||||
})
|
||||
|
||||
t.Run("Body String with special characters", func(t *testing.T) {
|
||||
body := Body("special: !@#$%^&*()\nnewline\ttab")
|
||||
assert.Equal(t, "special: !@#$%^&*()\nnewline\ttab", body.String())
|
||||
})
|
||||
}
|
||||
|
||||
func TestBodies_Append(t *testing.T) {
|
||||
t.Run("Append single body", func(t *testing.T) {
|
||||
bodies := &Bodies{}
|
||||
bodies.Append(Body("first body"))
|
||||
|
||||
assert.Len(t, *bodies, 1)
|
||||
assert.Equal(t, Body("first body"), (*bodies)[0])
|
||||
})
|
||||
|
||||
t.Run("Append multiple bodies", func(t *testing.T) {
|
||||
bodies := &Bodies{}
|
||||
bodies.Append(Body("first"))
|
||||
bodies.Append(Body("second"))
|
||||
bodies.Append(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"))
|
||||
|
||||
assert.Len(t, *bodies, 2)
|
||||
assert.Equal(t, Body("existing"), (*bodies)[0])
|
||||
assert.Equal(t, Body("new"), (*bodies)[1])
|
||||
})
|
||||
|
||||
t.Run("Append empty body", func(t *testing.T) {
|
||||
bodies := &Bodies{}
|
||||
bodies.Append(Body(""))
|
||||
|
||||
assert.Len(t, *bodies, 1)
|
||||
assert.Empty(t, (*bodies)[0].String())
|
||||
})
|
||||
}
|
||||
|
||||
func TestBodies_Parse(t *testing.T) {
|
||||
t.Run("Parse single value", func(t *testing.T) {
|
||||
bodies := &Bodies{}
|
||||
bodies.Parse("test body")
|
||||
|
||||
assert.Len(t, *bodies, 1)
|
||||
assert.Equal(t, Body("test body"), (*bodies)[0])
|
||||
})
|
||||
|
||||
t.Run("Parse multiple values", func(t *testing.T) {
|
||||
bodies := &Bodies{}
|
||||
bodies.Parse("body1", "body2", "body3")
|
||||
|
||||
assert.Len(t, *bodies, 3)
|
||||
assert.Equal(t, Body("body1"), (*bodies)[0])
|
||||
assert.Equal(t, Body("body2"), (*bodies)[1])
|
||||
assert.Equal(t, Body("body3"), (*bodies)[2])
|
||||
})
|
||||
|
||||
t.Run("Parse with existing bodies", func(t *testing.T) {
|
||||
bodies := &Bodies{Body("existing")}
|
||||
bodies.Parse("new1", "new2")
|
||||
|
||||
assert.Len(t, *bodies, 3)
|
||||
assert.Equal(t, Body("existing"), (*bodies)[0])
|
||||
assert.Equal(t, Body("new1"), (*bodies)[1])
|
||||
assert.Equal(t, Body("new2"), (*bodies)[2])
|
||||
})
|
||||
|
||||
t.Run("Parse empty values", func(t *testing.T) {
|
||||
bodies := &Bodies{}
|
||||
bodies.Parse("", "", "")
|
||||
|
||||
assert.Len(t, *bodies, 3)
|
||||
for _, body := range *bodies {
|
||||
assert.Empty(t, body.String())
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Parse no arguments", func(t *testing.T) {
|
||||
bodies := &Bodies{}
|
||||
bodies.Parse()
|
||||
|
||||
assert.Empty(t, *bodies)
|
||||
})
|
||||
|
||||
t.Run("Parse JSON strings", func(t *testing.T) {
|
||||
bodies := &Bodies{}
|
||||
bodies.Parse(`{"key": "value"}`, `{"array": [1, 2, 3]}`)
|
||||
|
||||
assert.Len(t, *bodies, 2)
|
||||
assert.JSONEq(t, `{"key": "value"}`, (*bodies)[0].String())
|
||||
assert.JSONEq(t, `{"array": [1, 2, 3]}`, (*bodies)[1].String())
|
||||
})
|
||||
}
|
||||
|
||||
func TestParseBody(t *testing.T) {
|
||||
t.Run("ParseBody with regular string", func(t *testing.T) {
|
||||
body := ParseBody("test content")
|
||||
assert.Equal(t, Body("test content"), body)
|
||||
})
|
||||
|
||||
t.Run("ParseBody with empty string", func(t *testing.T) {
|
||||
body := ParseBody("")
|
||||
assert.Equal(t, Body(""), body)
|
||||
})
|
||||
|
||||
t.Run("ParseBody with multiline string", func(t *testing.T) {
|
||||
input := "line1\nline2\nline3"
|
||||
body := ParseBody(input)
|
||||
assert.Equal(t, Body(input), body)
|
||||
})
|
||||
|
||||
t.Run("ParseBody with special characters", func(t *testing.T) {
|
||||
input := "!@#$%^&*()_+-=[]{}|;':\",./<>?"
|
||||
body := ParseBody(input)
|
||||
assert.Equal(t, Body(input), body)
|
||||
})
|
||||
|
||||
t.Run("ParseBody with unicode", func(t *testing.T) {
|
||||
input := "Hello World 🌍"
|
||||
body := ParseBody(input)
|
||||
assert.Equal(t, Body(input), body)
|
||||
})
|
||||
|
||||
t.Run("ParseBody preserves whitespace", func(t *testing.T) {
|
||||
input := " leading and trailing spaces "
|
||||
body := ParseBody(input)
|
||||
assert.Equal(t, Body(input), body)
|
||||
})
|
||||
}
|
92
pkg/types/config_file.go
Normal file
92
pkg/types/config_file.go
Normal file
@@ -0,0 +1,92 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type ConfigFileType int
|
||||
|
||||
const (
|
||||
ConfigFileTypeYAML ConfigFileType = iota
|
||||
)
|
||||
|
||||
func (t ConfigFileType) String() string {
|
||||
switch t {
|
||||
case ConfigFileTypeYAML:
|
||||
return "yaml"
|
||||
default:
|
||||
return "unknown"
|
||||
}
|
||||
}
|
||||
|
||||
type ConfigFileLocationType int
|
||||
|
||||
const (
|
||||
ConfigFileLocationLocal ConfigFileLocationType = iota
|
||||
ConfigFileLocationRemote
|
||||
)
|
||||
|
||||
func (l ConfigFileLocationType) String() string {
|
||||
switch l {
|
||||
case ConfigFileLocationLocal:
|
||||
return "local"
|
||||
case ConfigFileLocationRemote:
|
||||
return "remote"
|
||||
default:
|
||||
return "unknown"
|
||||
}
|
||||
}
|
||||
|
||||
type ConfigFile struct {
|
||||
path string
|
||||
_type ConfigFileType
|
||||
locationType ConfigFileLocationType
|
||||
}
|
||||
|
||||
func (configFile ConfigFile) String() string {
|
||||
return configFile.path
|
||||
}
|
||||
|
||||
func (configFile ConfigFile) Type() ConfigFileType {
|
||||
return configFile._type
|
||||
}
|
||||
|
||||
func (configFile ConfigFile) LocationType() ConfigFileLocationType {
|
||||
return configFile.locationType
|
||||
}
|
||||
|
||||
func ParseConfigFile(configFileRaw string) (*ConfigFile, error) {
|
||||
configFileParsed := &ConfigFile{
|
||||
path: configFileRaw,
|
||||
locationType: ConfigFileLocationLocal,
|
||||
}
|
||||
|
||||
if strings.HasPrefix(configFileRaw, "http://") || strings.HasPrefix(configFileRaw, "https://") {
|
||||
configFileParsed.locationType = ConfigFileLocationRemote
|
||||
}
|
||||
|
||||
configFilePath := configFileRaw
|
||||
if configFileParsed.locationType == ConfigFileLocationRemote {
|
||||
remoteConfigFileParsed, err := url.Parse(configFileRaw)
|
||||
if err != nil {
|
||||
return nil, NewRemoteConfigFileParseError(err)
|
||||
}
|
||||
configFilePath = remoteConfigFileParsed.Path
|
||||
}
|
||||
|
||||
configFileExtension, _ := strings.CutPrefix(filepath.Ext(configFilePath), ".")
|
||||
if configFileExtension == "" {
|
||||
return nil, ErrConfigFileExtensionNotFound
|
||||
}
|
||||
|
||||
switch strings.ToLower(configFileExtension) {
|
||||
case "yml", "yaml":
|
||||
configFileParsed._type = ConfigFileTypeYAML
|
||||
default:
|
||||
return nil, NewUnknownConfigFileTypeError(configFileExtension)
|
||||
}
|
||||
|
||||
return configFileParsed, nil
|
||||
}
|
216
pkg/types/config_file_test.go
Normal file
216
pkg/types/config_file_test.go
Normal file
@@ -0,0 +1,216 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestConfigFileType_String(t *testing.T) {
|
||||
t.Run("ConfigFileTypeYAML returns yaml", func(t *testing.T) {
|
||||
configType := ConfigFileTypeYAML
|
||||
assert.Equal(t, "yaml", configType.String())
|
||||
})
|
||||
|
||||
t.Run("Unknown config file type returns unknown", func(t *testing.T) {
|
||||
configType := ConfigFileType(999)
|
||||
assert.Equal(t, "unknown", configType.String())
|
||||
})
|
||||
}
|
||||
|
||||
func TestConfigFileLocationType_String(t *testing.T) {
|
||||
t.Run("ConfigFileLocationLocal returns local", func(t *testing.T) {
|
||||
locationType := ConfigFileLocationLocal
|
||||
assert.Equal(t, "local", locationType.String())
|
||||
})
|
||||
|
||||
t.Run("ConfigFileLocationRemote returns remote", func(t *testing.T) {
|
||||
locationType := ConfigFileLocationRemote
|
||||
assert.Equal(t, "remote", locationType.String())
|
||||
})
|
||||
|
||||
t.Run("Unknown location type returns unknown", func(t *testing.T) {
|
||||
locationType := ConfigFileLocationType(999)
|
||||
assert.Equal(t, "unknown", locationType.String())
|
||||
})
|
||||
}
|
||||
|
||||
func TestConfigFile_String(t *testing.T) {
|
||||
t.Run("String returns the file path", func(t *testing.T) {
|
||||
configFile := ConfigFile{path: "/path/to/config.yaml"}
|
||||
assert.Equal(t, "/path/to/config.yaml", configFile.String())
|
||||
})
|
||||
|
||||
t.Run("String returns empty path", func(t *testing.T) {
|
||||
configFile := ConfigFile{path: ""}
|
||||
assert.Empty(t, configFile.String())
|
||||
})
|
||||
}
|
||||
|
||||
func TestConfigFile_Type(t *testing.T) {
|
||||
t.Run("Type returns the config file type", func(t *testing.T) {
|
||||
configFile := ConfigFile{_type: ConfigFileTypeYAML}
|
||||
assert.Equal(t, ConfigFileTypeYAML, configFile.Type())
|
||||
})
|
||||
}
|
||||
|
||||
func TestConfigFile_LocationType(t *testing.T) {
|
||||
t.Run("LocationType returns local", func(t *testing.T) {
|
||||
configFile := ConfigFile{locationType: ConfigFileLocationLocal}
|
||||
assert.Equal(t, ConfigFileLocationLocal, configFile.LocationType())
|
||||
})
|
||||
|
||||
t.Run("LocationType returns remote", func(t *testing.T) {
|
||||
configFile := ConfigFile{locationType: ConfigFileLocationRemote}
|
||||
assert.Equal(t, ConfigFileLocationRemote, configFile.LocationType())
|
||||
})
|
||||
}
|
||||
|
||||
func TestParseConfigFile(t *testing.T) {
|
||||
t.Run("Parse local YAML file with yml extension", func(t *testing.T) {
|
||||
configFile, err := ParseConfigFile("config.yml")
|
||||
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, configFile)
|
||||
assert.Equal(t, "config.yml", configFile.String())
|
||||
assert.Equal(t, ConfigFileTypeYAML, configFile.Type())
|
||||
assert.Equal(t, ConfigFileLocationLocal, configFile.LocationType())
|
||||
})
|
||||
|
||||
t.Run("Parse local YAML file with yaml extension", func(t *testing.T) {
|
||||
configFile, err := ParseConfigFile("config.yaml")
|
||||
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, configFile)
|
||||
assert.Equal(t, "config.yaml", configFile.String())
|
||||
assert.Equal(t, ConfigFileTypeYAML, configFile.Type())
|
||||
assert.Equal(t, ConfigFileLocationLocal, configFile.LocationType())
|
||||
})
|
||||
|
||||
t.Run("Parse local YAML file with uppercase extensions", func(t *testing.T) {
|
||||
testCases := []string{"config.YML", "config.YAML", "config.Yml", "config.Yaml"}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
t.Run("Extension: "+testCase, func(t *testing.T) {
|
||||
configFile, err := ParseConfigFile(testCase)
|
||||
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, configFile)
|
||||
assert.Equal(t, testCase, configFile.String())
|
||||
assert.Equal(t, ConfigFileTypeYAML, configFile.Type())
|
||||
assert.Equal(t, ConfigFileLocationLocal, configFile.LocationType())
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Parse remote HTTP YAML file", func(t *testing.T) {
|
||||
configFile, err := ParseConfigFile("http://example.com/config.yaml")
|
||||
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, configFile)
|
||||
assert.Equal(t, "http://example.com/config.yaml", configFile.String())
|
||||
assert.Equal(t, ConfigFileTypeYAML, configFile.Type())
|
||||
assert.Equal(t, ConfigFileLocationRemote, configFile.LocationType())
|
||||
})
|
||||
|
||||
t.Run("Parse remote HTTPS YAML file", func(t *testing.T) {
|
||||
configFile, err := ParseConfigFile("https://example.com/path/config.yml")
|
||||
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, configFile)
|
||||
assert.Equal(t, "https://example.com/path/config.yml", configFile.String())
|
||||
assert.Equal(t, ConfigFileTypeYAML, configFile.Type())
|
||||
assert.Equal(t, ConfigFileLocationRemote, configFile.LocationType())
|
||||
})
|
||||
|
||||
t.Run("Parse file with path separators", func(t *testing.T) {
|
||||
configFile, err := ParseConfigFile("/path/to/config.yaml")
|
||||
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, configFile)
|
||||
assert.Equal(t, "/path/to/config.yaml", configFile.String())
|
||||
assert.Equal(t, ConfigFileTypeYAML, configFile.Type())
|
||||
assert.Equal(t, ConfigFileLocationLocal, configFile.LocationType())
|
||||
})
|
||||
|
||||
t.Run("Parse file without extension returns error", func(t *testing.T) {
|
||||
configFile, err := ParseConfigFile("config")
|
||||
|
||||
require.Error(t, err)
|
||||
assert.Equal(t, ErrConfigFileExtensionNotFound, err)
|
||||
assert.Nil(t, configFile)
|
||||
})
|
||||
|
||||
t.Run("Parse file with unsupported extension returns error", func(t *testing.T) {
|
||||
configFile, err := ParseConfigFile("config.json")
|
||||
|
||||
require.Error(t, err)
|
||||
assert.IsType(t, &UnknownConfigFileTypeError{}, err)
|
||||
assert.Contains(t, err.Error(), "json")
|
||||
assert.Nil(t, configFile)
|
||||
})
|
||||
|
||||
t.Run("Parse remote file with invalid URL returns error", func(t *testing.T) {
|
||||
configFile, err := ParseConfigFile("http://192.168.1.%30/config.yaml")
|
||||
|
||||
require.Error(t, err)
|
||||
assert.IsType(t, &RemoteConfigFileParseError{}, err)
|
||||
assert.Nil(t, configFile)
|
||||
})
|
||||
|
||||
t.Run("Parse remote file without extension returns error", func(t *testing.T) {
|
||||
configFile, err := ParseConfigFile("https://example.com/config")
|
||||
|
||||
require.Error(t, err)
|
||||
assert.Equal(t, ErrConfigFileExtensionNotFound, err)
|
||||
assert.Nil(t, configFile)
|
||||
})
|
||||
|
||||
t.Run("Parse remote file with unsupported extension returns error", func(t *testing.T) {
|
||||
configFile, err := ParseConfigFile("https://example.com/config.txt")
|
||||
|
||||
require.Error(t, err)
|
||||
assert.IsType(t, &UnknownConfigFileTypeError{}, err)
|
||||
assert.Contains(t, err.Error(), "txt")
|
||||
assert.Nil(t, configFile)
|
||||
})
|
||||
|
||||
t.Run("Parse empty string returns error", func(t *testing.T) {
|
||||
configFile, err := ParseConfigFile("")
|
||||
|
||||
require.Error(t, err)
|
||||
assert.Equal(t, ErrConfigFileExtensionNotFound, err)
|
||||
assert.Nil(t, configFile)
|
||||
})
|
||||
|
||||
t.Run("Parse file with multiple dots in name", func(t *testing.T) {
|
||||
configFile, err := ParseConfigFile("config.test.yaml")
|
||||
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, configFile)
|
||||
assert.Equal(t, "config.test.yaml", configFile.String())
|
||||
assert.Equal(t, ConfigFileTypeYAML, configFile.Type())
|
||||
assert.Equal(t, ConfigFileLocationLocal, configFile.LocationType())
|
||||
})
|
||||
|
||||
t.Run("Parse remote URL with query parameters and fragment", func(t *testing.T) {
|
||||
configFile, err := ParseConfigFile("https://example.com/path/config.yaml?version=1&format=yaml#section")
|
||||
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, configFile)
|
||||
assert.Equal(t, "https://example.com/path/config.yaml?version=1&format=yaml#section", configFile.String())
|
||||
assert.Equal(t, ConfigFileTypeYAML, configFile.Type())
|
||||
assert.Equal(t, ConfigFileLocationRemote, configFile.LocationType())
|
||||
})
|
||||
|
||||
t.Run("Parse remote URL with port", func(t *testing.T) {
|
||||
configFile, err := ParseConfigFile("https://example.com:8080/config.yml")
|
||||
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, configFile)
|
||||
assert.Equal(t, "https://example.com:8080/config.yml", configFile.String())
|
||||
assert.Equal(t, ConfigFileTypeYAML, configFile.Type())
|
||||
assert.Equal(t, ConfigFileLocationRemote, configFile.LocationType())
|
||||
})
|
||||
}
|
42
pkg/types/cookie.go
Normal file
42
pkg/types/cookie.go
Normal file
@@ -0,0 +1,42 @@
|
||||
package types
|
||||
|
||||
import "strings"
|
||||
|
||||
type Cookie KeyValue[string, []string]
|
||||
|
||||
type Cookies []Cookie
|
||||
|
||||
func (cookies Cookies) GetValue(key string) *[]string {
|
||||
for i := range cookies {
|
||||
if cookies[i].Key == key {
|
||||
return &cookies[i].Value
|
||||
}
|
||||
}
|
||||
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) Parse(rawValues ...string) {
|
||||
for _, rawValue := range rawValues {
|
||||
cookies.Append(*ParseCookie(rawValue))
|
||||
}
|
||||
}
|
||||
|
||||
func ParseCookie(rawValue string) *Cookie {
|
||||
parts := strings.SplitN(rawValue, "=", 2)
|
||||
switch len(parts) {
|
||||
case 1:
|
||||
return &Cookie{Key: parts[0], Value: []string{""}}
|
||||
case 2:
|
||||
return &Cookie{Key: parts[0], Value: []string{parts[1]}}
|
||||
default:
|
||||
return &Cookie{Key: "", Value: []string{""}}
|
||||
}
|
||||
}
|
240
pkg/types/cookie_test.go
Normal file
240
pkg/types/cookie_test.go
Normal file
@@ -0,0 +1,240 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestCookies_GetValue(t *testing.T) {
|
||||
t.Run("GetValue returns existing cookie value", func(t *testing.T) {
|
||||
cookies := Cookies{
|
||||
{Key: "session", Value: []string{"abc123"}},
|
||||
{Key: "user", Value: []string{"john"}},
|
||||
}
|
||||
|
||||
value := cookies.GetValue("session")
|
||||
require.NotNil(t, value)
|
||||
assert.Equal(t, []string{"abc123"}, *value)
|
||||
})
|
||||
|
||||
t.Run("GetValue returns nil for non-existent cookie", func(t *testing.T) {
|
||||
cookies := Cookies{
|
||||
{Key: "session", Value: []string{"abc123"}},
|
||||
}
|
||||
|
||||
value := cookies.GetValue("nonexistent")
|
||||
assert.Nil(t, value)
|
||||
})
|
||||
|
||||
t.Run("GetValue with empty cookies", func(t *testing.T) {
|
||||
cookies := Cookies{}
|
||||
|
||||
value := cookies.GetValue("any")
|
||||
assert.Nil(t, value)
|
||||
})
|
||||
|
||||
t.Run("GetValue with multiple values", func(t *testing.T) {
|
||||
cookies := Cookies{
|
||||
{Key: "multi", Value: []string{"val1", "val2", "val3"}},
|
||||
}
|
||||
|
||||
value := cookies.GetValue("multi")
|
||||
require.NotNil(t, value)
|
||||
assert.Equal(t, []string{"val1", "val2", "val3"}, *value)
|
||||
})
|
||||
|
||||
t.Run("GetValue case sensitive", func(t *testing.T) {
|
||||
cookies := Cookies{
|
||||
{Key: "Cookie", Value: []string{"value"}},
|
||||
}
|
||||
|
||||
value1 := cookies.GetValue("Cookie")
|
||||
require.NotNil(t, value1)
|
||||
assert.Equal(t, []string{"value"}, *value1)
|
||||
|
||||
value2 := cookies.GetValue("cookie")
|
||||
assert.Nil(t, value2)
|
||||
})
|
||||
}
|
||||
|
||||
func TestCookies_Append(t *testing.T) {
|
||||
t.Run("Append new cookie", func(t *testing.T) {
|
||||
cookies := &Cookies{}
|
||||
cookies.Append(Cookie{Key: "session", Value: []string{"abc123"}})
|
||||
|
||||
assert.Len(t, *cookies, 1)
|
||||
assert.Equal(t, "session", (*cookies)[0].Key)
|
||||
assert.Equal(t, []string{"abc123"}, (*cookies)[0].Value)
|
||||
})
|
||||
|
||||
t.Run("Append to existing cookie key", func(t *testing.T) {
|
||||
cookies := &Cookies{
|
||||
{Key: "session", Value: []string{"abc123"}},
|
||||
}
|
||||
cookies.Append(Cookie{Key: "session", Value: []string{"def456"}})
|
||||
|
||||
assert.Len(t, *cookies, 1)
|
||||
assert.Equal(t, []string{"abc123", "def456"}, (*cookies)[0].Value)
|
||||
})
|
||||
|
||||
t.Run("Append different cookies", func(t *testing.T) {
|
||||
cookies := &Cookies{}
|
||||
cookies.Append(Cookie{Key: "session", Value: []string{"abc"}})
|
||||
cookies.Append(Cookie{Key: "user", Value: []string{"john"}})
|
||||
cookies.Append(Cookie{Key: "token", Value: []string{"xyz"}})
|
||||
|
||||
assert.Len(t, *cookies, 3)
|
||||
})
|
||||
|
||||
t.Run("Append multiple values at once", func(t *testing.T) {
|
||||
cookies := &Cookies{
|
||||
{Key: "tags", Value: []string{"tag1"}},
|
||||
}
|
||||
cookies.Append(Cookie{Key: "tags", Value: []string{"tag2", "tag3"}})
|
||||
|
||||
assert.Len(t, *cookies, 1)
|
||||
assert.Equal(t, []string{"tag1", "tag2", "tag3"}, (*cookies)[0].Value)
|
||||
})
|
||||
|
||||
t.Run("Append empty value", func(t *testing.T) {
|
||||
cookies := &Cookies{}
|
||||
cookies.Append(Cookie{Key: "empty", Value: []string{""}})
|
||||
|
||||
assert.Len(t, *cookies, 1)
|
||||
assert.Equal(t, []string{""}, (*cookies)[0].Value)
|
||||
})
|
||||
}
|
||||
|
||||
func TestCookies_Parse(t *testing.T) {
|
||||
t.Run("Parse single cookie", func(t *testing.T) {
|
||||
cookies := &Cookies{}
|
||||
cookies.Parse("session=abc123")
|
||||
|
||||
assert.Len(t, *cookies, 1)
|
||||
assert.Equal(t, "session", (*cookies)[0].Key)
|
||||
assert.Equal(t, []string{"abc123"}, (*cookies)[0].Value)
|
||||
})
|
||||
|
||||
t.Run("Parse multiple cookies", func(t *testing.T) {
|
||||
cookies := &Cookies{}
|
||||
cookies.Parse("session=abc123", "user=john", "token=xyz789")
|
||||
|
||||
assert.Len(t, *cookies, 3)
|
||||
assert.Equal(t, "session", (*cookies)[0].Key)
|
||||
assert.Equal(t, "user", (*cookies)[1].Key)
|
||||
assert.Equal(t, "token", (*cookies)[2].Key)
|
||||
})
|
||||
|
||||
t.Run("Parse cookies with same key", func(t *testing.T) {
|
||||
cookies := &Cookies{}
|
||||
cookies.Parse("pref=dark", "pref=large", "pref=en")
|
||||
|
||||
assert.Len(t, *cookies, 1)
|
||||
assert.Equal(t, []string{"dark", "large", "en"}, (*cookies)[0].Value)
|
||||
})
|
||||
|
||||
t.Run("Parse cookie without value", func(t *testing.T) {
|
||||
cookies := &Cookies{}
|
||||
cookies.Parse("sessionid")
|
||||
|
||||
assert.Len(t, *cookies, 1)
|
||||
assert.Equal(t, "sessionid", (*cookies)[0].Key)
|
||||
assert.Equal(t, []string{""}, (*cookies)[0].Value)
|
||||
})
|
||||
|
||||
t.Run("Parse cookie with empty value", func(t *testing.T) {
|
||||
cookies := &Cookies{}
|
||||
cookies.Parse("empty=")
|
||||
|
||||
assert.Len(t, *cookies, 1)
|
||||
assert.Equal(t, "empty", (*cookies)[0].Key)
|
||||
assert.Equal(t, []string{""}, (*cookies)[0].Value)
|
||||
})
|
||||
|
||||
t.Run("Parse cookie with multiple equals", func(t *testing.T) {
|
||||
cookies := &Cookies{}
|
||||
cookies.Parse("data=key=value=test")
|
||||
|
||||
assert.Len(t, *cookies, 1)
|
||||
assert.Equal(t, "data", (*cookies)[0].Key)
|
||||
assert.Equal(t, []string{"key=value=test"}, (*cookies)[0].Value)
|
||||
})
|
||||
|
||||
t.Run("Parse no arguments", func(t *testing.T) {
|
||||
cookies := &Cookies{}
|
||||
cookies.Parse()
|
||||
|
||||
assert.Empty(t, *cookies)
|
||||
})
|
||||
|
||||
t.Run("Parse with existing cookies", func(t *testing.T) {
|
||||
cookies := &Cookies{
|
||||
{Key: "existing", Value: []string{"value"}},
|
||||
}
|
||||
cookies.Parse("new=cookie")
|
||||
|
||||
assert.Len(t, *cookies, 2)
|
||||
assert.Equal(t, "existing", (*cookies)[0].Key)
|
||||
assert.Equal(t, "new", (*cookies)[1].Key)
|
||||
})
|
||||
}
|
||||
|
||||
func TestParseCookie(t *testing.T) {
|
||||
t.Run("ParseCookie with key and value", func(t *testing.T) {
|
||||
cookie := ParseCookie("session=abc123")
|
||||
require.NotNil(t, cookie)
|
||||
assert.Equal(t, "session", cookie.Key)
|
||||
assert.Equal(t, []string{"abc123"}, cookie.Value)
|
||||
})
|
||||
|
||||
t.Run("ParseCookie with only key", func(t *testing.T) {
|
||||
cookie := ParseCookie("sessionid")
|
||||
require.NotNil(t, cookie)
|
||||
assert.Equal(t, "sessionid", cookie.Key)
|
||||
assert.Equal(t, []string{""}, cookie.Value)
|
||||
})
|
||||
|
||||
t.Run("ParseCookie with empty value", func(t *testing.T) {
|
||||
cookie := ParseCookie("key=")
|
||||
require.NotNil(t, cookie)
|
||||
assert.Equal(t, "key", cookie.Key)
|
||||
assert.Equal(t, []string{""}, cookie.Value)
|
||||
})
|
||||
|
||||
t.Run("ParseCookie with multiple equals", func(t *testing.T) {
|
||||
cookie := ParseCookie("data=base64=encoded=value")
|
||||
require.NotNil(t, cookie)
|
||||
assert.Equal(t, "data", cookie.Key)
|
||||
assert.Equal(t, []string{"base64=encoded=value"}, cookie.Value)
|
||||
})
|
||||
|
||||
t.Run("ParseCookie with empty string", func(t *testing.T) {
|
||||
cookie := ParseCookie("")
|
||||
require.NotNil(t, cookie)
|
||||
assert.Empty(t, cookie.Key)
|
||||
assert.Equal(t, []string{""}, cookie.Value)
|
||||
})
|
||||
|
||||
t.Run("ParseCookie with spaces", func(t *testing.T) {
|
||||
cookie := ParseCookie("key with spaces=value with spaces")
|
||||
require.NotNil(t, cookie)
|
||||
assert.Equal(t, "key with spaces", cookie.Key)
|
||||
assert.Equal(t, []string{"value with spaces"}, cookie.Value)
|
||||
})
|
||||
|
||||
t.Run("ParseCookie with special characters", func(t *testing.T) {
|
||||
cookie := ParseCookie("key-._~=val!@#$%^&*()")
|
||||
require.NotNil(t, cookie)
|
||||
assert.Equal(t, "key-._~", cookie.Key)
|
||||
assert.Equal(t, []string{"val!@#$%^&*()"}, cookie.Value)
|
||||
})
|
||||
|
||||
t.Run("ParseCookie with URL encoded value", func(t *testing.T) {
|
||||
cookie := ParseCookie("data=hello%20world%3D%26")
|
||||
require.NotNil(t, cookie)
|
||||
assert.Equal(t, "data", cookie.Key)
|
||||
assert.Equal(t, []string{"hello%20world%3D%26"}, cookie.Value)
|
||||
})
|
||||
}
|
113
pkg/types/errors.go
Normal file
113
pkg/types/errors.go
Normal file
@@ -0,0 +1,113 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
// General
|
||||
ErrNoError = errors.New("no error (internal)")
|
||||
|
||||
// CLI
|
||||
ErrCLINoArgs = errors.New("CLI expects arguments but received none")
|
||||
ErrCLIUnexpectedArgs = errors.New("CLI received unexpected arguments")
|
||||
|
||||
// Config File
|
||||
ErrConfigFileExtensionNotFound = errors.New("config file extension not found")
|
||||
)
|
||||
|
||||
// ======================================== General ========================================
|
||||
|
||||
type FieldParseError struct {
|
||||
Field string
|
||||
Err error
|
||||
}
|
||||
|
||||
func NewFieldParseError(field string, err error) *FieldParseError {
|
||||
if err == nil {
|
||||
err = ErrNoError
|
||||
}
|
||||
return &FieldParseError{field, err}
|
||||
}
|
||||
|
||||
func (e FieldParseError) Error() string {
|
||||
return fmt.Sprintf("Field '%s' parse failed: %v", e.Field, e.Err)
|
||||
}
|
||||
|
||||
func (e FieldParseError) Unwrap() error {
|
||||
return e.Err
|
||||
}
|
||||
|
||||
type FieldParseErrors struct {
|
||||
Errors []FieldParseError
|
||||
}
|
||||
|
||||
func NewFieldParseErrors(fieldParseErrors []FieldParseError) FieldParseErrors {
|
||||
return FieldParseErrors{fieldParseErrors}
|
||||
}
|
||||
|
||||
func (e FieldParseErrors) Error() string {
|
||||
if len(e.Errors) == 0 {
|
||||
return "No field parse errors"
|
||||
}
|
||||
if len(e.Errors) == 1 {
|
||||
return e.Errors[0].Error()
|
||||
}
|
||||
|
||||
errorString := ""
|
||||
for _, err := range e.Errors {
|
||||
errorString += err.Error() + "\n"
|
||||
}
|
||||
errorString, _ = strings.CutSuffix(errorString, "\n")
|
||||
|
||||
return errorString
|
||||
}
|
||||
|
||||
// ======================================== CLI ========================================
|
||||
|
||||
type CLIUnexpectedArgsError struct {
|
||||
Args []string
|
||||
}
|
||||
|
||||
func NewCLIUnexpectedArgsError(args []string) CLIUnexpectedArgsError {
|
||||
return CLIUnexpectedArgsError{args}
|
||||
}
|
||||
|
||||
func (e CLIUnexpectedArgsError) Error() string {
|
||||
return fmt.Sprintf("CLI received unexpected arguments: %v", strings.Join(e.Args, ","))
|
||||
}
|
||||
|
||||
// ======================================== Config File ========================================
|
||||
|
||||
type RemoteConfigFileParseError struct {
|
||||
error error
|
||||
}
|
||||
|
||||
func NewRemoteConfigFileParseError(err error) *RemoteConfigFileParseError {
|
||||
if err == nil {
|
||||
err = ErrNoError
|
||||
}
|
||||
return &RemoteConfigFileParseError{err}
|
||||
}
|
||||
|
||||
func (e RemoteConfigFileParseError) Error() string {
|
||||
return "Remote config file parse error: " + e.error.Error()
|
||||
}
|
||||
|
||||
func (e RemoteConfigFileParseError) Unwrap() error {
|
||||
return e.error
|
||||
}
|
||||
|
||||
type UnknownConfigFileTypeError struct {
|
||||
Type string
|
||||
}
|
||||
|
||||
func NewUnknownConfigFileTypeError(_type string) *UnknownConfigFileTypeError {
|
||||
return &UnknownConfigFileTypeError{_type}
|
||||
}
|
||||
|
||||
func (e UnknownConfigFileTypeError) Error() string {
|
||||
return "Unknown config file type: " + e.Type
|
||||
}
|
284
pkg/types/errors_test.go
Normal file
284
pkg/types/errors_test.go
Normal file
@@ -0,0 +1,284 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestFieldParseError_Error(t *testing.T) {
|
||||
t.Run("Error returns formatted message", func(t *testing.T) {
|
||||
originalErr := errors.New("invalid value")
|
||||
fieldErr := NewFieldParseError("username", originalErr)
|
||||
|
||||
expected := "Field 'username' parse failed: invalid value"
|
||||
assert.Equal(t, expected, fieldErr.Error())
|
||||
})
|
||||
|
||||
t.Run("Error with empty field name", func(t *testing.T) {
|
||||
originalErr := errors.New("test error")
|
||||
fieldErr := NewFieldParseError("", originalErr)
|
||||
|
||||
expected := "Field '' parse failed: test error"
|
||||
assert.Equal(t, expected, fieldErr.Error())
|
||||
})
|
||||
|
||||
t.Run("Error with nil underlying error", func(t *testing.T) {
|
||||
fieldErr := NewFieldParseError("field", nil)
|
||||
|
||||
expected := "Field 'field' parse failed: no error (internal)"
|
||||
assert.Equal(t, expected, fieldErr.Error())
|
||||
})
|
||||
}
|
||||
|
||||
func TestFieldParseError_Unwrap(t *testing.T) {
|
||||
t.Run("Unwrap returns original error", func(t *testing.T) {
|
||||
originalErr := errors.New("original error")
|
||||
fieldErr := NewFieldParseError("field", originalErr)
|
||||
|
||||
assert.Equal(t, originalErr, fieldErr.Unwrap())
|
||||
})
|
||||
|
||||
t.Run("Unwrap with nil error", func(t *testing.T) {
|
||||
fieldErr := NewFieldParseError("field", nil)
|
||||
|
||||
assert.Equal(t, ErrNoError, fieldErr.Unwrap())
|
||||
})
|
||||
}
|
||||
|
||||
func TestNewFieldParseError(t *testing.T) {
|
||||
t.Run("Creates FieldParseError with correct values", func(t *testing.T) {
|
||||
originalErr := errors.New("test error")
|
||||
fieldErr := NewFieldParseError("testField", originalErr)
|
||||
|
||||
assert.Equal(t, "testField", fieldErr.Field)
|
||||
assert.Equal(t, originalErr, fieldErr.Err)
|
||||
})
|
||||
|
||||
t.Run("Creates FieldParseError with ErrNoError when nil passed", func(t *testing.T) {
|
||||
fieldErr := NewFieldParseError("testField", nil)
|
||||
|
||||
assert.Equal(t, "testField", fieldErr.Field)
|
||||
assert.Equal(t, ErrNoError, fieldErr.Err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestFieldParseErrors_Error(t *testing.T) {
|
||||
t.Run("Error with no errors returns default message", func(t *testing.T) {
|
||||
fieldErrors := NewFieldParseErrors([]FieldParseError{})
|
||||
|
||||
assert.Equal(t, "No field parse errors", fieldErrors.Error())
|
||||
})
|
||||
|
||||
t.Run("Error with single error returns single error message", func(t *testing.T) {
|
||||
fieldErr := *NewFieldParseError("field1", errors.New("error1"))
|
||||
fieldErrors := NewFieldParseErrors([]FieldParseError{fieldErr})
|
||||
|
||||
expected := "Field 'field1' parse failed: error1"
|
||||
assert.Equal(t, expected, fieldErrors.Error())
|
||||
})
|
||||
|
||||
t.Run("Error with multiple errors returns concatenated messages", func(t *testing.T) {
|
||||
fieldErr1 := *NewFieldParseError("field1", errors.New("error1"))
|
||||
fieldErr2 := *NewFieldParseError("field2", errors.New("error2"))
|
||||
fieldErr3 := *NewFieldParseError("field3", errors.New("error3"))
|
||||
fieldErrors := NewFieldParseErrors([]FieldParseError{fieldErr1, fieldErr2, fieldErr3})
|
||||
|
||||
expected := "Field 'field1' parse failed: error1\nField 'field2' parse failed: error2\nField 'field3' parse failed: error3"
|
||||
assert.Equal(t, expected, fieldErrors.Error())
|
||||
})
|
||||
|
||||
t.Run("Error with two errors", func(t *testing.T) {
|
||||
fieldErr1 := *NewFieldParseError("username", errors.New("too short"))
|
||||
fieldErr2 := *NewFieldParseError("email", errors.New("invalid format"))
|
||||
fieldErrors := NewFieldParseErrors([]FieldParseError{fieldErr1, fieldErr2})
|
||||
|
||||
expected := "Field 'username' parse failed: too short\nField 'email' parse failed: invalid format"
|
||||
assert.Equal(t, expected, fieldErrors.Error())
|
||||
})
|
||||
}
|
||||
|
||||
func TestNewFieldParseErrors(t *testing.T) {
|
||||
t.Run("Creates FieldParseErrors with correct values", func(t *testing.T) {
|
||||
fieldErr1 := *NewFieldParseError("field1", errors.New("error1"))
|
||||
fieldErr2 := *NewFieldParseError("field2", errors.New("error2"))
|
||||
fieldErrors := NewFieldParseErrors([]FieldParseError{fieldErr1, fieldErr2})
|
||||
|
||||
assert.Len(t, fieldErrors.Errors, 2)
|
||||
assert.Equal(t, fieldErr1, fieldErrors.Errors[0])
|
||||
assert.Equal(t, fieldErr2, fieldErrors.Errors[1])
|
||||
})
|
||||
|
||||
t.Run("Creates FieldParseErrors with empty slice", func(t *testing.T) {
|
||||
fieldErrors := NewFieldParseErrors([]FieldParseError{})
|
||||
|
||||
assert.Empty(t, fieldErrors.Errors)
|
||||
})
|
||||
}
|
||||
|
||||
func TestCLIUnexpectedArgsError_Error(t *testing.T) {
|
||||
t.Run("Error with single argument", func(t *testing.T) {
|
||||
err := NewCLIUnexpectedArgsError([]string{"arg1"})
|
||||
|
||||
expected := "CLI received unexpected arguments: arg1"
|
||||
assert.Equal(t, expected, err.Error())
|
||||
})
|
||||
|
||||
t.Run("Error with multiple arguments", func(t *testing.T) {
|
||||
err := NewCLIUnexpectedArgsError([]string{"arg1", "arg2", "arg3"})
|
||||
|
||||
expected := "CLI received unexpected arguments: arg1,arg2,arg3"
|
||||
assert.Equal(t, expected, err.Error())
|
||||
})
|
||||
|
||||
t.Run("Error with empty arguments", func(t *testing.T) {
|
||||
err := NewCLIUnexpectedArgsError([]string{})
|
||||
|
||||
expected := "CLI received unexpected arguments: "
|
||||
assert.Equal(t, expected, err.Error())
|
||||
})
|
||||
|
||||
t.Run("Error with arguments containing special characters", func(t *testing.T) {
|
||||
err := NewCLIUnexpectedArgsError([]string{"--flag", "value with spaces", "-x"})
|
||||
|
||||
expected := "CLI received unexpected arguments: --flag,value with spaces,-x"
|
||||
assert.Equal(t, expected, err.Error())
|
||||
})
|
||||
}
|
||||
|
||||
func TestNewCLIUnexpectedArgsError(t *testing.T) {
|
||||
t.Run("Creates CLIUnexpectedArgsError with correct values", func(t *testing.T) {
|
||||
args := []string{"arg1", "arg2"}
|
||||
err := NewCLIUnexpectedArgsError(args)
|
||||
|
||||
assert.Equal(t, args, err.Args)
|
||||
})
|
||||
}
|
||||
|
||||
func TestRemoteConfigFileParseError_Error(t *testing.T) {
|
||||
t.Run("Error returns formatted message", func(t *testing.T) {
|
||||
originalErr := errors.New("invalid URL")
|
||||
err := NewRemoteConfigFileParseError(originalErr)
|
||||
|
||||
expected := "Remote config file parse error: invalid URL"
|
||||
assert.Equal(t, expected, err.Error())
|
||||
})
|
||||
|
||||
t.Run("Error with nil underlying error", func(t *testing.T) {
|
||||
err := NewRemoteConfigFileParseError(nil)
|
||||
|
||||
expected := "Remote config file parse error: no error (internal)"
|
||||
assert.Equal(t, expected, err.Error())
|
||||
})
|
||||
}
|
||||
|
||||
func TestRemoteConfigFileParseError_Unwrap(t *testing.T) {
|
||||
t.Run("Unwrap returns original error", func(t *testing.T) {
|
||||
originalErr := errors.New("original error")
|
||||
err := NewRemoteConfigFileParseError(originalErr)
|
||||
|
||||
assert.Equal(t, originalErr, err.Unwrap())
|
||||
})
|
||||
|
||||
t.Run("Unwrap with nil error", func(t *testing.T) {
|
||||
err := NewRemoteConfigFileParseError(nil)
|
||||
|
||||
assert.Equal(t, ErrNoError, err.Unwrap())
|
||||
})
|
||||
}
|
||||
|
||||
func TestNewRemoteConfigFileParseError(t *testing.T) {
|
||||
t.Run("Creates RemoteConfigFileParseError with correct values", func(t *testing.T) {
|
||||
originalErr := errors.New("test error")
|
||||
err := NewRemoteConfigFileParseError(originalErr)
|
||||
|
||||
assert.Equal(t, originalErr, err.error)
|
||||
})
|
||||
|
||||
t.Run("Creates RemoteConfigFileParseError with ErrNoError when nil passed", func(t *testing.T) {
|
||||
err := NewRemoteConfigFileParseError(nil)
|
||||
|
||||
assert.Equal(t, ErrNoError, err.error)
|
||||
})
|
||||
}
|
||||
|
||||
func TestUnknownConfigFileTypeError_Error(t *testing.T) {
|
||||
t.Run("Error returns formatted message", func(t *testing.T) {
|
||||
err := NewUnknownConfigFileTypeError("json")
|
||||
|
||||
expected := "Unknown config file type: json"
|
||||
assert.Equal(t, expected, err.Error())
|
||||
})
|
||||
|
||||
t.Run("Error with empty type", func(t *testing.T) {
|
||||
err := NewUnknownConfigFileTypeError("")
|
||||
|
||||
expected := "Unknown config file type: "
|
||||
assert.Equal(t, expected, err.Error())
|
||||
})
|
||||
|
||||
t.Run("Error with special characters in type", func(t *testing.T) {
|
||||
err := NewUnknownConfigFileTypeError("type.with.dots")
|
||||
|
||||
expected := "Unknown config file type: type.with.dots"
|
||||
assert.Equal(t, expected, err.Error())
|
||||
})
|
||||
}
|
||||
|
||||
func TestNewUnknownConfigFileTypeError(t *testing.T) {
|
||||
t.Run("Creates UnknownConfigFileTypeError with correct values", func(t *testing.T) {
|
||||
err := NewUnknownConfigFileTypeError("xml")
|
||||
|
||||
assert.Equal(t, "xml", err.Type)
|
||||
})
|
||||
}
|
||||
|
||||
func TestErrorConstants(t *testing.T) {
|
||||
t.Run("ErrNoError has correct message", func(t *testing.T) {
|
||||
expected := "no error (internal)"
|
||||
assert.Equal(t, expected, ErrNoError.Error())
|
||||
})
|
||||
|
||||
t.Run("ErrCLINoArgs has correct message", func(t *testing.T) {
|
||||
expected := "CLI expects arguments but received none"
|
||||
assert.Equal(t, expected, ErrCLINoArgs.Error())
|
||||
})
|
||||
|
||||
t.Run("ErrCLIUnexpectedArgs has correct message", func(t *testing.T) {
|
||||
expected := "CLI received unexpected arguments"
|
||||
assert.Equal(t, expected, ErrCLIUnexpectedArgs.Error())
|
||||
})
|
||||
|
||||
t.Run("ErrConfigFileExtensionNotFound has correct message", func(t *testing.T) {
|
||||
expected := "config file extension not found"
|
||||
assert.Equal(t, expected, ErrConfigFileExtensionNotFound.Error())
|
||||
})
|
||||
}
|
||||
|
||||
func TestErrorImplementsErrorInterface(t *testing.T) {
|
||||
t.Run("FieldParseError implements error interface", func(t *testing.T) {
|
||||
var err error = NewFieldParseError("field", errors.New("test"))
|
||||
assert.Error(t, err)
|
||||
})
|
||||
|
||||
t.Run("FieldParseErrors implements error interface", func(t *testing.T) {
|
||||
var err error = NewFieldParseErrors([]FieldParseError{})
|
||||
assert.Error(t, err)
|
||||
})
|
||||
|
||||
t.Run("CLIUnexpectedArgsError implements error interface", func(t *testing.T) {
|
||||
var err error = NewCLIUnexpectedArgsError([]string{})
|
||||
assert.Error(t, err)
|
||||
})
|
||||
|
||||
t.Run("RemoteConfigFileParseError implements error interface", func(t *testing.T) {
|
||||
var err error = NewRemoteConfigFileParseError(errors.New("test"))
|
||||
assert.Error(t, err)
|
||||
})
|
||||
|
||||
t.Run("UnknownConfigFileTypeError implements error interface", func(t *testing.T) {
|
||||
var err error = NewUnknownConfigFileTypeError("test")
|
||||
assert.Error(t, err)
|
||||
})
|
||||
}
|
52
pkg/types/header.go
Normal file
52
pkg/types/header.go
Normal file
@@ -0,0 +1,52 @@
|
||||
package types
|
||||
|
||||
import "strings"
|
||||
|
||||
type Header KeyValue[string, []string]
|
||||
|
||||
type Headers []Header
|
||||
|
||||
// Has checks if a header with the given key exists.
|
||||
func (headers Headers) Has(key string) bool {
|
||||
for i := range headers {
|
||||
if headers[i].Key == key {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (headers Headers) GetValue(key string) *[]string {
|
||||
for i := range headers {
|
||||
if headers[i].Key == key {
|
||||
return &headers[i].Value
|
||||
}
|
||||
}
|
||||
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) Parse(rawValues ...string) {
|
||||
for _, rawValue := range rawValues {
|
||||
headers.Append(*ParseHeader(rawValue))
|
||||
}
|
||||
}
|
||||
|
||||
func ParseHeader(rawValue string) *Header {
|
||||
parts := strings.SplitN(rawValue, ": ", 2)
|
||||
switch len(parts) {
|
||||
case 1:
|
||||
return &Header{Key: parts[0], Value: []string{""}}
|
||||
case 2:
|
||||
return &Header{Key: parts[0], Value: []string{parts[1]}}
|
||||
default:
|
||||
return &Header{Key: "", Value: []string{""}}
|
||||
}
|
||||
}
|
277
pkg/types/header_test.go
Normal file
277
pkg/types/header_test.go
Normal file
@@ -0,0 +1,277 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestHeaders_Has(t *testing.T) {
|
||||
t.Run("Has returns true for existing header", func(t *testing.T) {
|
||||
headers := Headers{
|
||||
{Key: "Content-Type", Value: []string{"application/json"}},
|
||||
{Key: "Authorization", Value: []string{"Bearer token"}},
|
||||
}
|
||||
|
||||
assert.True(t, headers.Has("Content-Type"))
|
||||
assert.True(t, headers.Has("Authorization"))
|
||||
})
|
||||
|
||||
t.Run("Has returns false for non-existent header", func(t *testing.T) {
|
||||
headers := Headers{
|
||||
{Key: "Content-Type", Value: []string{"application/json"}},
|
||||
}
|
||||
|
||||
assert.False(t, headers.Has("Authorization"))
|
||||
assert.False(t, headers.Has("X-Custom-Header"))
|
||||
})
|
||||
|
||||
t.Run("Has with empty headers", func(t *testing.T) {
|
||||
headers := Headers{}
|
||||
|
||||
assert.False(t, headers.Has("Any-Header"))
|
||||
})
|
||||
|
||||
t.Run("Has is case sensitive", func(t *testing.T) {
|
||||
headers := Headers{
|
||||
{Key: "Content-Type", Value: []string{"text/html"}},
|
||||
}
|
||||
|
||||
assert.True(t, headers.Has("Content-Type"))
|
||||
assert.False(t, headers.Has("content-type"))
|
||||
assert.False(t, headers.Has("CONTENT-TYPE"))
|
||||
})
|
||||
}
|
||||
|
||||
func TestHeaders_GetValue(t *testing.T) {
|
||||
t.Run("GetValue returns existing header value", func(t *testing.T) {
|
||||
headers := Headers{
|
||||
{Key: "Content-Type", Value: []string{"application/json"}},
|
||||
{Key: "Accept", Value: []string{"text/html"}},
|
||||
}
|
||||
|
||||
value := headers.GetValue("Content-Type")
|
||||
require.NotNil(t, value)
|
||||
assert.Equal(t, []string{"application/json"}, *value)
|
||||
})
|
||||
|
||||
t.Run("GetValue returns nil for non-existent header", func(t *testing.T) {
|
||||
headers := Headers{
|
||||
{Key: "Content-Type", Value: []string{"application/json"}},
|
||||
}
|
||||
|
||||
value := headers.GetValue("Authorization")
|
||||
assert.Nil(t, value)
|
||||
})
|
||||
|
||||
t.Run("GetValue with empty headers", func(t *testing.T) {
|
||||
headers := Headers{}
|
||||
|
||||
value := headers.GetValue("Any-Header")
|
||||
assert.Nil(t, value)
|
||||
})
|
||||
|
||||
t.Run("GetValue with multiple values", func(t *testing.T) {
|
||||
headers := Headers{
|
||||
{Key: "Accept", Value: []string{"text/html", "application/xml", "application/json"}},
|
||||
}
|
||||
|
||||
value := headers.GetValue("Accept")
|
||||
require.NotNil(t, value)
|
||||
assert.Equal(t, []string{"text/html", "application/xml", "application/json"}, *value)
|
||||
})
|
||||
|
||||
t.Run("GetValue is case sensitive", func(t *testing.T) {
|
||||
headers := Headers{
|
||||
{Key: "X-Custom-Header", Value: []string{"value"}},
|
||||
}
|
||||
|
||||
value1 := headers.GetValue("X-Custom-Header")
|
||||
require.NotNil(t, value1)
|
||||
assert.Equal(t, []string{"value"}, *value1)
|
||||
|
||||
value2 := headers.GetValue("x-custom-header")
|
||||
assert.Nil(t, value2)
|
||||
})
|
||||
}
|
||||
|
||||
func TestHeaders_Append(t *testing.T) {
|
||||
t.Run("Append new header", func(t *testing.T) {
|
||||
headers := &Headers{}
|
||||
headers.Append(Header{Key: "Content-Type", Value: []string{"application/json"}})
|
||||
|
||||
assert.Len(t, *headers, 1)
|
||||
assert.Equal(t, "Content-Type", (*headers)[0].Key)
|
||||
assert.Equal(t, []string{"application/json"}, (*headers)[0].Value)
|
||||
})
|
||||
|
||||
t.Run("Append to existing header key", func(t *testing.T) {
|
||||
headers := &Headers{
|
||||
{Key: "Accept", Value: []string{"text/html"}},
|
||||
}
|
||||
headers.Append(Header{Key: "Accept", Value: []string{"application/json"}})
|
||||
|
||||
assert.Len(t, *headers, 1)
|
||||
assert.Equal(t, []string{"text/html", "application/json"}, (*headers)[0].Value)
|
||||
})
|
||||
|
||||
t.Run("Append different headers", func(t *testing.T) {
|
||||
headers := &Headers{}
|
||||
headers.Append(Header{Key: "Content-Type", Value: []string{"application/json"}})
|
||||
headers.Append(Header{Key: "Authorization", Value: []string{"Bearer token"}})
|
||||
headers.Append(Header{Key: "Accept", Value: []string{"*/*"}})
|
||||
|
||||
assert.Len(t, *headers, 3)
|
||||
})
|
||||
|
||||
t.Run("Append multiple values at once", func(t *testing.T) {
|
||||
headers := &Headers{
|
||||
{Key: "Accept-Language", Value: []string{"en"}},
|
||||
}
|
||||
headers.Append(Header{Key: "Accept-Language", Value: []string{"fr", "de"}})
|
||||
|
||||
assert.Len(t, *headers, 1)
|
||||
assert.Equal(t, []string{"en", "fr", "de"}, (*headers)[0].Value)
|
||||
})
|
||||
|
||||
t.Run("Append empty value", func(t *testing.T) {
|
||||
headers := &Headers{}
|
||||
headers.Append(Header{Key: "Empty-Header", Value: []string{""}})
|
||||
|
||||
assert.Len(t, *headers, 1)
|
||||
assert.Equal(t, []string{""}, (*headers)[0].Value)
|
||||
})
|
||||
}
|
||||
|
||||
func TestHeaders_Parse(t *testing.T) {
|
||||
t.Run("Parse single header", func(t *testing.T) {
|
||||
headers := &Headers{}
|
||||
headers.Parse("Content-Type: application/json")
|
||||
|
||||
assert.Len(t, *headers, 1)
|
||||
assert.Equal(t, "Content-Type", (*headers)[0].Key)
|
||||
assert.Equal(t, []string{"application/json"}, (*headers)[0].Value)
|
||||
})
|
||||
|
||||
t.Run("Parse multiple headers", func(t *testing.T) {
|
||||
headers := &Headers{}
|
||||
headers.Parse("Content-Type: application/json", "Authorization: Bearer token", "Accept: */*")
|
||||
|
||||
assert.Len(t, *headers, 3)
|
||||
assert.Equal(t, "Content-Type", (*headers)[0].Key)
|
||||
assert.Equal(t, "Authorization", (*headers)[1].Key)
|
||||
assert.Equal(t, "Accept", (*headers)[2].Key)
|
||||
})
|
||||
|
||||
t.Run("Parse headers with same key", func(t *testing.T) {
|
||||
headers := &Headers{}
|
||||
headers.Parse("Accept: text/html", "Accept: application/json", "Accept: application/xml")
|
||||
|
||||
assert.Len(t, *headers, 1)
|
||||
assert.Equal(t, []string{"text/html", "application/json", "application/xml"}, (*headers)[0].Value)
|
||||
})
|
||||
|
||||
t.Run("Parse header without value", func(t *testing.T) {
|
||||
headers := &Headers{}
|
||||
headers.Parse("X-Empty-Header")
|
||||
|
||||
assert.Len(t, *headers, 1)
|
||||
assert.Equal(t, "X-Empty-Header", (*headers)[0].Key)
|
||||
assert.Equal(t, []string{""}, (*headers)[0].Value)
|
||||
})
|
||||
|
||||
t.Run("Parse header with empty value", func(t *testing.T) {
|
||||
headers := &Headers{}
|
||||
headers.Parse("X-Empty: ")
|
||||
|
||||
assert.Len(t, *headers, 1)
|
||||
assert.Equal(t, "X-Empty", (*headers)[0].Key)
|
||||
assert.Equal(t, []string{""}, (*headers)[0].Value)
|
||||
})
|
||||
|
||||
t.Run("Parse header with multiple colons", func(t *testing.T) {
|
||||
headers := &Headers{}
|
||||
headers.Parse("X-Time: 12:34:56")
|
||||
|
||||
assert.Len(t, *headers, 1)
|
||||
assert.Equal(t, "X-Time", (*headers)[0].Key)
|
||||
assert.Equal(t, []string{"12:34:56"}, (*headers)[0].Value)
|
||||
})
|
||||
|
||||
t.Run("Parse no arguments", func(t *testing.T) {
|
||||
headers := &Headers{}
|
||||
headers.Parse()
|
||||
|
||||
assert.Empty(t, *headers)
|
||||
})
|
||||
|
||||
t.Run("Parse with existing headers", func(t *testing.T) {
|
||||
headers := &Headers{
|
||||
{Key: "Existing", Value: []string{"value"}},
|
||||
}
|
||||
headers.Parse("New: header")
|
||||
|
||||
assert.Len(t, *headers, 2)
|
||||
assert.Equal(t, "Existing", (*headers)[0].Key)
|
||||
assert.Equal(t, "New", (*headers)[1].Key)
|
||||
})
|
||||
}
|
||||
|
||||
func TestParseHeader(t *testing.T) {
|
||||
t.Run("ParseHeader with key and value", func(t *testing.T) {
|
||||
header := ParseHeader("Content-Type: application/json")
|
||||
require.NotNil(t, header)
|
||||
assert.Equal(t, "Content-Type", header.Key)
|
||||
assert.Equal(t, []string{"application/json"}, header.Value)
|
||||
})
|
||||
|
||||
t.Run("ParseHeader with only key", func(t *testing.T) {
|
||||
header := ParseHeader("X-Header")
|
||||
require.NotNil(t, header)
|
||||
assert.Equal(t, "X-Header", header.Key)
|
||||
assert.Equal(t, []string{""}, header.Value)
|
||||
})
|
||||
|
||||
t.Run("ParseHeader with empty value", func(t *testing.T) {
|
||||
header := ParseHeader("Key: ")
|
||||
require.NotNil(t, header)
|
||||
assert.Equal(t, "Key", header.Key)
|
||||
assert.Equal(t, []string{""}, header.Value)
|
||||
})
|
||||
|
||||
t.Run("ParseHeader with multiple colons", func(t *testing.T) {
|
||||
header := ParseHeader("X-URL: https://example.com:8080/path")
|
||||
require.NotNil(t, header)
|
||||
assert.Equal(t, "X-URL", header.Key)
|
||||
assert.Equal(t, []string{"https://example.com:8080/path"}, header.Value)
|
||||
})
|
||||
|
||||
t.Run("ParseHeader with empty string", func(t *testing.T) {
|
||||
header := ParseHeader("")
|
||||
require.NotNil(t, header)
|
||||
assert.Empty(t, header.Key)
|
||||
assert.Equal(t, []string{""}, header.Value)
|
||||
})
|
||||
|
||||
t.Run("ParseHeader with spaces in value", func(t *testing.T) {
|
||||
header := ParseHeader("User-Agent: Mozilla/5.0 (Windows NT 10.0)")
|
||||
require.NotNil(t, header)
|
||||
assert.Equal(t, "User-Agent", header.Key)
|
||||
assert.Equal(t, []string{"Mozilla/5.0 (Windows NT 10.0)"}, header.Value)
|
||||
})
|
||||
|
||||
t.Run("ParseHeader without colon-space separator", func(t *testing.T) {
|
||||
header := ParseHeader("Content-Type:application/json")
|
||||
require.NotNil(t, header)
|
||||
assert.Equal(t, "Content-Type:application/json", header.Key)
|
||||
assert.Equal(t, []string{""}, header.Value)
|
||||
})
|
||||
|
||||
t.Run("ParseHeader with trailing spaces", func(t *testing.T) {
|
||||
header := ParseHeader("Header: value with spaces ")
|
||||
require.NotNil(t, header)
|
||||
assert.Equal(t, "Header", header.Key)
|
||||
assert.Equal(t, []string{"value with spaces "}, header.Value)
|
||||
})
|
||||
}
|
6
pkg/types/key_value.go
Normal file
6
pkg/types/key_value.go
Normal file
@@ -0,0 +1,6 @@
|
||||
package types
|
||||
|
||||
type KeyValue[K comparable, V any] struct {
|
||||
Key K
|
||||
Value V
|
||||
}
|
42
pkg/types/param.go
Normal file
42
pkg/types/param.go
Normal file
@@ -0,0 +1,42 @@
|
||||
package types
|
||||
|
||||
import "strings"
|
||||
|
||||
type Param KeyValue[string, []string]
|
||||
|
||||
type Params []Param
|
||||
|
||||
func (params Params) GetValue(key string) *[]string {
|
||||
for i := range params {
|
||||
if params[i].Key == key {
|
||||
return ¶ms[i].Value
|
||||
}
|
||||
}
|
||||
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) Parse(rawValues ...string) {
|
||||
for _, rawValue := range rawValues {
|
||||
params.Append(*ParseParam(rawValue))
|
||||
}
|
||||
}
|
||||
|
||||
func ParseParam(rawValue string) *Param {
|
||||
parts := strings.SplitN(rawValue, "=", 2)
|
||||
switch len(parts) {
|
||||
case 1:
|
||||
return &Param{Key: parts[0], Value: []string{""}}
|
||||
case 2:
|
||||
return &Param{Key: parts[0], Value: []string{parts[1]}}
|
||||
default:
|
||||
return &Param{Key: "", Value: []string{""}}
|
||||
}
|
||||
}
|
281
pkg/types/param_test.go
Normal file
281
pkg/types/param_test.go
Normal file
@@ -0,0 +1,281 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestParams_GetValue(t *testing.T) {
|
||||
t.Run("GetValue returns existing parameter value", func(t *testing.T) {
|
||||
params := Params{
|
||||
{Key: "name", Value: []string{"john"}},
|
||||
{Key: "age", Value: []string{"25"}},
|
||||
}
|
||||
|
||||
value := params.GetValue("name")
|
||||
require.NotNil(t, value)
|
||||
assert.Equal(t, []string{"john"}, *value)
|
||||
})
|
||||
|
||||
t.Run("GetValue returns nil for non-existent parameter", func(t *testing.T) {
|
||||
params := Params{
|
||||
{Key: "name", Value: []string{"john"}},
|
||||
}
|
||||
|
||||
value := params.GetValue("nonexistent")
|
||||
assert.Nil(t, value)
|
||||
})
|
||||
|
||||
t.Run("GetValue with empty params", func(t *testing.T) {
|
||||
params := Params{}
|
||||
|
||||
value := params.GetValue("any")
|
||||
assert.Nil(t, value)
|
||||
})
|
||||
|
||||
t.Run("GetValue with multiple values", func(t *testing.T) {
|
||||
params := Params{
|
||||
{Key: "tags", Value: []string{"go", "test", "api"}},
|
||||
}
|
||||
|
||||
value := params.GetValue("tags")
|
||||
require.NotNil(t, value)
|
||||
assert.Equal(t, []string{"go", "test", "api"}, *value)
|
||||
})
|
||||
|
||||
t.Run("GetValue case sensitive", func(t *testing.T) {
|
||||
params := Params{
|
||||
{Key: "Name", Value: []string{"value"}},
|
||||
}
|
||||
|
||||
value1 := params.GetValue("Name")
|
||||
require.NotNil(t, value1)
|
||||
assert.Equal(t, []string{"value"}, *value1)
|
||||
|
||||
value2 := params.GetValue("name")
|
||||
assert.Nil(t, value2)
|
||||
})
|
||||
}
|
||||
|
||||
func TestParams_Append(t *testing.T) {
|
||||
t.Run("Append new parameter", func(t *testing.T) {
|
||||
params := &Params{}
|
||||
params.Append(Param{Key: "name", Value: []string{"john"}})
|
||||
|
||||
assert.Len(t, *params, 1)
|
||||
assert.Equal(t, "name", (*params)[0].Key)
|
||||
assert.Equal(t, []string{"john"}, (*params)[0].Value)
|
||||
})
|
||||
|
||||
t.Run("Append to existing parameter key", func(t *testing.T) {
|
||||
params := &Params{
|
||||
{Key: "tags", Value: []string{"go"}},
|
||||
}
|
||||
params.Append(Param{Key: "tags", Value: []string{"test"}})
|
||||
|
||||
assert.Len(t, *params, 1)
|
||||
assert.Equal(t, []string{"go", "test"}, (*params)[0].Value)
|
||||
})
|
||||
|
||||
t.Run("Append different parameters", func(t *testing.T) {
|
||||
params := &Params{}
|
||||
params.Append(Param{Key: "name", Value: []string{"john"}})
|
||||
params.Append(Param{Key: "age", Value: []string{"25"}})
|
||||
params.Append(Param{Key: "city", Value: []string{"NYC"}})
|
||||
|
||||
assert.Len(t, *params, 3)
|
||||
})
|
||||
|
||||
t.Run("Append multiple values at once", func(t *testing.T) {
|
||||
params := &Params{
|
||||
{Key: "colors", Value: []string{"red"}},
|
||||
}
|
||||
params.Append(Param{Key: "colors", Value: []string{"blue", "green"}})
|
||||
|
||||
assert.Len(t, *params, 1)
|
||||
assert.Equal(t, []string{"red", "blue", "green"}, (*params)[0].Value)
|
||||
})
|
||||
|
||||
t.Run("Append empty value", func(t *testing.T) {
|
||||
params := &Params{}
|
||||
params.Append(Param{Key: "empty", Value: []string{""}})
|
||||
|
||||
assert.Len(t, *params, 1)
|
||||
assert.Equal(t, []string{""}, (*params)[0].Value)
|
||||
})
|
||||
}
|
||||
|
||||
func TestParams_Parse(t *testing.T) {
|
||||
t.Run("Parse single parameter", func(t *testing.T) {
|
||||
params := &Params{}
|
||||
params.Parse("name=john")
|
||||
|
||||
assert.Len(t, *params, 1)
|
||||
assert.Equal(t, "name", (*params)[0].Key)
|
||||
assert.Equal(t, []string{"john"}, (*params)[0].Value)
|
||||
})
|
||||
|
||||
t.Run("Parse multiple parameters", func(t *testing.T) {
|
||||
params := &Params{}
|
||||
params.Parse("name=john", "age=25", "city=NYC")
|
||||
|
||||
assert.Len(t, *params, 3)
|
||||
assert.Equal(t, "name", (*params)[0].Key)
|
||||
assert.Equal(t, "age", (*params)[1].Key)
|
||||
assert.Equal(t, "city", (*params)[2].Key)
|
||||
})
|
||||
|
||||
t.Run("Parse parameters with same key", func(t *testing.T) {
|
||||
params := &Params{}
|
||||
params.Parse("filter=name", "filter=age", "filter=city")
|
||||
|
||||
assert.Len(t, *params, 1)
|
||||
assert.Equal(t, []string{"name", "age", "city"}, (*params)[0].Value)
|
||||
})
|
||||
|
||||
t.Run("Parse parameter without value", func(t *testing.T) {
|
||||
params := &Params{}
|
||||
params.Parse("debug")
|
||||
|
||||
assert.Len(t, *params, 1)
|
||||
assert.Equal(t, "debug", (*params)[0].Key)
|
||||
assert.Equal(t, []string{""}, (*params)[0].Value)
|
||||
})
|
||||
|
||||
t.Run("Parse parameter with empty value", func(t *testing.T) {
|
||||
params := &Params{}
|
||||
params.Parse("empty=")
|
||||
|
||||
assert.Len(t, *params, 1)
|
||||
assert.Equal(t, "empty", (*params)[0].Key)
|
||||
assert.Equal(t, []string{""}, (*params)[0].Value)
|
||||
})
|
||||
|
||||
t.Run("Parse parameter with multiple equals", func(t *testing.T) {
|
||||
params := &Params{}
|
||||
params.Parse("equation=x=y+z")
|
||||
|
||||
assert.Len(t, *params, 1)
|
||||
assert.Equal(t, "equation", (*params)[0].Key)
|
||||
assert.Equal(t, []string{"x=y+z"}, (*params)[0].Value)
|
||||
})
|
||||
|
||||
t.Run("Parse no arguments", func(t *testing.T) {
|
||||
params := &Params{}
|
||||
params.Parse()
|
||||
|
||||
assert.Empty(t, *params)
|
||||
})
|
||||
|
||||
t.Run("Parse with existing parameters", func(t *testing.T) {
|
||||
params := &Params{
|
||||
{Key: "existing", Value: []string{"value"}},
|
||||
}
|
||||
params.Parse("new=param")
|
||||
|
||||
assert.Len(t, *params, 2)
|
||||
assert.Equal(t, "existing", (*params)[0].Key)
|
||||
assert.Equal(t, "new", (*params)[1].Key)
|
||||
})
|
||||
|
||||
t.Run("Parse URL-encoded values", func(t *testing.T) {
|
||||
params := &Params{}
|
||||
params.Parse("query=hello%20world", "special=%21%40%23")
|
||||
|
||||
assert.Len(t, *params, 2)
|
||||
assert.Equal(t, []string{"hello%20world"}, (*params)[0].Value)
|
||||
assert.Equal(t, []string{"%21%40%23"}, (*params)[1].Value)
|
||||
})
|
||||
}
|
||||
|
||||
func TestParseParam(t *testing.T) {
|
||||
t.Run("ParseParam with key and value", func(t *testing.T) {
|
||||
param := ParseParam("name=john")
|
||||
require.NotNil(t, param)
|
||||
assert.Equal(t, "name", param.Key)
|
||||
assert.Equal(t, []string{"john"}, param.Value)
|
||||
})
|
||||
|
||||
t.Run("ParseParam with only key", func(t *testing.T) {
|
||||
param := ParseParam("debug")
|
||||
require.NotNil(t, param)
|
||||
assert.Equal(t, "debug", param.Key)
|
||||
assert.Equal(t, []string{""}, param.Value)
|
||||
})
|
||||
|
||||
t.Run("ParseParam with empty value", func(t *testing.T) {
|
||||
param := ParseParam("key=")
|
||||
require.NotNil(t, param)
|
||||
assert.Equal(t, "key", param.Key)
|
||||
assert.Equal(t, []string{""}, param.Value)
|
||||
})
|
||||
|
||||
t.Run("ParseParam with multiple equals", func(t *testing.T) {
|
||||
param := ParseParam("data=key=value=test")
|
||||
require.NotNil(t, param)
|
||||
assert.Equal(t, "data", param.Key)
|
||||
assert.Equal(t, []string{"key=value=test"}, param.Value)
|
||||
})
|
||||
|
||||
t.Run("ParseParam with empty string", func(t *testing.T) {
|
||||
param := ParseParam("")
|
||||
require.NotNil(t, param)
|
||||
assert.Empty(t, param.Key)
|
||||
assert.Equal(t, []string{""}, param.Value)
|
||||
})
|
||||
|
||||
t.Run("ParseParam with spaces", func(t *testing.T) {
|
||||
param := ParseParam("key with spaces=value with spaces")
|
||||
require.NotNil(t, param)
|
||||
assert.Equal(t, "key with spaces", param.Key)
|
||||
assert.Equal(t, []string{"value with spaces"}, param.Value)
|
||||
})
|
||||
|
||||
t.Run("ParseParam with special characters", func(t *testing.T) {
|
||||
param := ParseParam("key-._~=val!@#$%^&*()")
|
||||
require.NotNil(t, param)
|
||||
assert.Equal(t, "key-._~", param.Key)
|
||||
assert.Equal(t, []string{"val!@#$%^&*()"}, param.Value)
|
||||
})
|
||||
|
||||
t.Run("ParseParam with numeric values", func(t *testing.T) {
|
||||
param := ParseParam("count=42")
|
||||
require.NotNil(t, param)
|
||||
assert.Equal(t, "count", param.Key)
|
||||
assert.Equal(t, []string{"42"}, param.Value)
|
||||
})
|
||||
|
||||
t.Run("ParseParam with boolean-like values", func(t *testing.T) {
|
||||
testCases := []struct {
|
||||
input string
|
||||
expected string
|
||||
}{
|
||||
{"active=true", "true"},
|
||||
{"enabled=false", "false"},
|
||||
{"visible=1", "1"},
|
||||
{"hidden=0", "0"},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
param := ParseParam(testCase.input)
|
||||
require.NotNil(t, param)
|
||||
assert.Equal(t, []string{testCase.expected}, param.Value)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("ParseParam with URL-encoded value", func(t *testing.T) {
|
||||
param := ParseParam("message=hello%20world")
|
||||
require.NotNil(t, param)
|
||||
assert.Equal(t, "message", param.Key)
|
||||
assert.Equal(t, []string{"hello%20world"}, param.Value)
|
||||
})
|
||||
|
||||
t.Run("ParseParam with JSON-like value", func(t *testing.T) {
|
||||
param := ParseParam(`data={"key":"value"}`)
|
||||
require.NotNil(t, param)
|
||||
assert.Equal(t, "data", param.Key)
|
||||
assert.Equal(t, []string{`{"key":"value"}`}, param.Value)
|
||||
})
|
||||
}
|
38
pkg/types/proxy.go
Normal file
38
pkg/types/proxy.go
Normal file
@@ -0,0 +1,38 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
type Proxy url.URL
|
||||
|
||||
func (proxy Proxy) String() string {
|
||||
return (*url.URL)(&proxy).String()
|
||||
}
|
||||
|
||||
type Proxies []Proxy
|
||||
|
||||
func (proxies *Proxies) Append(proxy Proxy) {
|
||||
*proxies = append(*proxies, proxy)
|
||||
}
|
||||
|
||||
func (proxies *Proxies) Parse(rawValue string) error {
|
||||
parsedProxy, err := ParseProxy(rawValue)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
proxies.Append(*parsedProxy)
|
||||
return nil
|
||||
}
|
||||
|
||||
func ParseProxy(rawValue string) (*Proxy, error) {
|
||||
urlParsed, err := url.Parse(rawValue)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse proxy URL: %w", err)
|
||||
}
|
||||
|
||||
proxyParsed := Proxy(*urlParsed)
|
||||
return &proxyParsed, nil
|
||||
}
|
285
pkg/types/proxy_test.go
Normal file
285
pkg/types/proxy_test.go
Normal file
@@ -0,0 +1,285 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestProxy_String(t *testing.T) {
|
||||
t.Run("Proxy String returns correct URL", func(t *testing.T) {
|
||||
u, err := url.Parse("http://proxy.example.com:8080")
|
||||
require.NoError(t, err)
|
||||
|
||||
proxy := Proxy(*u)
|
||||
assert.Equal(t, "http://proxy.example.com:8080", proxy.String())
|
||||
})
|
||||
|
||||
t.Run("Proxy String with HTTPS", func(t *testing.T) {
|
||||
u, err := url.Parse("https://secure-proxy.example.com:443")
|
||||
require.NoError(t, err)
|
||||
|
||||
proxy := Proxy(*u)
|
||||
assert.Equal(t, "https://secure-proxy.example.com:443", proxy.String())
|
||||
})
|
||||
|
||||
t.Run("Proxy String with authentication", func(t *testing.T) {
|
||||
u, err := url.Parse("http://user:pass@proxy.example.com:8080")
|
||||
require.NoError(t, err)
|
||||
|
||||
proxy := Proxy(*u)
|
||||
assert.Equal(t, "http://user:pass@proxy.example.com:8080", proxy.String())
|
||||
})
|
||||
|
||||
t.Run("Proxy String with path", func(t *testing.T) {
|
||||
u, err := url.Parse("http://proxy.example.com:8080/proxy/path")
|
||||
require.NoError(t, err)
|
||||
|
||||
proxy := Proxy(*u)
|
||||
assert.Equal(t, "http://proxy.example.com:8080/proxy/path", proxy.String())
|
||||
})
|
||||
|
||||
t.Run("Proxy String with query params", func(t *testing.T) {
|
||||
u, err := url.Parse("http://proxy.example.com:8080/?timeout=30&retry=3")
|
||||
require.NoError(t, err)
|
||||
|
||||
proxy := Proxy(*u)
|
||||
assert.Equal(t, "http://proxy.example.com:8080/?timeout=30&retry=3", proxy.String())
|
||||
})
|
||||
}
|
||||
|
||||
func TestProxies_Append(t *testing.T) {
|
||||
t.Run("Append single proxy", func(t *testing.T) {
|
||||
proxies := &Proxies{}
|
||||
u, err := url.Parse("http://proxy1.example.com:8080")
|
||||
require.NoError(t, err)
|
||||
|
||||
proxy := Proxy(*u)
|
||||
proxies.Append(proxy)
|
||||
|
||||
assert.Len(t, *proxies, 1)
|
||||
assert.Equal(t, "http://proxy1.example.com:8080", (*proxies)[0].String())
|
||||
})
|
||||
|
||||
t.Run("Append multiple proxies", 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))
|
||||
proxies.Append(Proxy(*url2))
|
||||
proxies.Append(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)
|
||||
|
||||
proxies := &Proxies{Proxy(*existingURL)}
|
||||
|
||||
newURL, err := url.Parse("http://new.example.com:8081")
|
||||
require.NoError(t, err)
|
||||
|
||||
proxies.Append(Proxy(*newURL))
|
||||
|
||||
assert.Len(t, *proxies, 2)
|
||||
assert.Equal(t, "http://existing.example.com:8080", (*proxies)[0].String())
|
||||
assert.Equal(t, "http://new.example.com:8081", (*proxies)[1].String())
|
||||
})
|
||||
}
|
||||
|
||||
func TestProxies_Parse(t *testing.T) {
|
||||
t.Run("Parse valid proxy URL", func(t *testing.T) {
|
||||
proxies := &Proxies{}
|
||||
err := proxies.Parse("http://proxy.example.com:8080")
|
||||
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, *proxies, 1)
|
||||
assert.Equal(t, "http://proxy.example.com:8080", (*proxies)[0].String())
|
||||
})
|
||||
|
||||
t.Run("Parse HTTPS proxy URL", func(t *testing.T) {
|
||||
proxies := &Proxies{}
|
||||
err := proxies.Parse("https://secure-proxy.example.com:443")
|
||||
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, *proxies, 1)
|
||||
assert.Equal(t, "https://secure-proxy.example.com:443", (*proxies)[0].String())
|
||||
})
|
||||
|
||||
t.Run("Parse proxy URL with authentication", func(t *testing.T) {
|
||||
proxies := &Proxies{}
|
||||
err := proxies.Parse("http://user:pass@proxy.example.com:8080")
|
||||
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, *proxies, 1)
|
||||
assert.Equal(t, "http://user:pass@proxy.example.com:8080", (*proxies)[0].String())
|
||||
})
|
||||
|
||||
t.Run("Parse invalid proxy URL", func(t *testing.T) {
|
||||
proxies := &Proxies{}
|
||||
err := proxies.Parse("://invalid-url")
|
||||
|
||||
require.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "failed to parse proxy URL")
|
||||
assert.Empty(t, *proxies)
|
||||
})
|
||||
|
||||
t.Run("Parse empty string", func(t *testing.T) {
|
||||
proxies := &Proxies{}
|
||||
err := proxies.Parse("")
|
||||
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, *proxies, 1)
|
||||
assert.Empty(t, (*proxies)[0].String())
|
||||
})
|
||||
|
||||
t.Run("Parse to existing proxies", func(t *testing.T) {
|
||||
existingURL, err := url.Parse("http://existing.example.com:8080")
|
||||
require.NoError(t, err)
|
||||
|
||||
proxies := &Proxies{Proxy(*existingURL)}
|
||||
err = proxies.Parse("http://new.example.com:8081")
|
||||
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, *proxies, 2)
|
||||
assert.Equal(t, "http://existing.example.com:8080", (*proxies)[0].String())
|
||||
assert.Equal(t, "http://new.example.com:8081", (*proxies)[1].String())
|
||||
})
|
||||
|
||||
t.Run("Parse proxy with special characters", func(t *testing.T) {
|
||||
proxies := &Proxies{}
|
||||
err := proxies.Parse("http://proxy.example.com:8080/path?param=value&other=test")
|
||||
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, *proxies, 1)
|
||||
assert.Equal(t, "http://proxy.example.com:8080/path?param=value&other=test", (*proxies)[0].String())
|
||||
})
|
||||
}
|
||||
|
||||
func TestParseProxy(t *testing.T) {
|
||||
t.Run("ParseProxy with valid HTTP URL", func(t *testing.T) {
|
||||
proxy, err := ParseProxy("http://proxy.example.com:8080")
|
||||
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, proxy)
|
||||
assert.Equal(t, "http://proxy.example.com:8080", proxy.String())
|
||||
})
|
||||
|
||||
t.Run("ParseProxy with valid HTTPS URL", func(t *testing.T) {
|
||||
proxy, err := ParseProxy("https://secure-proxy.example.com:443")
|
||||
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, proxy)
|
||||
assert.Equal(t, "https://secure-proxy.example.com:443", proxy.String())
|
||||
})
|
||||
|
||||
t.Run("ParseProxy with authentication", func(t *testing.T) {
|
||||
proxy, err := ParseProxy("http://user:password@proxy.example.com:8080")
|
||||
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, proxy)
|
||||
assert.Equal(t, "http://user:password@proxy.example.com:8080", proxy.String())
|
||||
})
|
||||
|
||||
t.Run("ParseProxy with path", func(t *testing.T) {
|
||||
proxy, err := ParseProxy("http://proxy.example.com:8080/proxy/endpoint")
|
||||
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, proxy)
|
||||
assert.Equal(t, "http://proxy.example.com:8080/proxy/endpoint", proxy.String())
|
||||
})
|
||||
|
||||
t.Run("ParseProxy with query parameters", func(t *testing.T) {
|
||||
proxy, err := ParseProxy("http://proxy.example.com:8080/?timeout=30&retry=3")
|
||||
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, proxy)
|
||||
assert.Equal(t, "http://proxy.example.com:8080/?timeout=30&retry=3", proxy.String())
|
||||
})
|
||||
|
||||
t.Run("ParseProxy with malformed URL", func(t *testing.T) {
|
||||
proxy, err := ParseProxy("://malformed-url")
|
||||
|
||||
require.Error(t, err)
|
||||
assert.Nil(t, proxy)
|
||||
assert.Contains(t, err.Error(), "failed to parse proxy URL")
|
||||
})
|
||||
|
||||
t.Run("ParseProxy with empty string", func(t *testing.T) {
|
||||
proxy, err := ParseProxy("")
|
||||
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, proxy)
|
||||
assert.Empty(t, proxy.String())
|
||||
})
|
||||
|
||||
t.Run("ParseProxy with localhost", func(t *testing.T) {
|
||||
proxy, err := ParseProxy("http://localhost:3128")
|
||||
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, proxy)
|
||||
assert.Equal(t, "http://localhost:3128", proxy.String())
|
||||
})
|
||||
|
||||
t.Run("ParseProxy with IP address", func(t *testing.T) {
|
||||
proxy, err := ParseProxy("http://192.168.1.100:8080")
|
||||
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, proxy)
|
||||
assert.Equal(t, "http://192.168.1.100:8080", proxy.String())
|
||||
})
|
||||
|
||||
t.Run("ParseProxy without scheme", func(t *testing.T) {
|
||||
proxy, err := ParseProxy("proxy.example.com:8080")
|
||||
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, proxy)
|
||||
assert.Equal(t, "proxy.example.com:8080", proxy.String())
|
||||
})
|
||||
|
||||
t.Run("ParseProxy with SOCKS protocol", func(t *testing.T) {
|
||||
proxy, err := ParseProxy("socks5://proxy.example.com:1080")
|
||||
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, proxy)
|
||||
assert.Equal(t, "socks5://proxy.example.com:1080", proxy.String())
|
||||
})
|
||||
|
||||
t.Run("ParseProxy preserves URL components", func(t *testing.T) {
|
||||
rawURL := "http://user:pass@proxy.example.com:8080/path?param=value#fragment"
|
||||
proxy, err := ParseProxy(rawURL)
|
||||
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, proxy)
|
||||
assert.Equal(t, rawURL, proxy.String())
|
||||
})
|
||||
|
||||
t.Run("ParseProxy with percent encoding", func(t *testing.T) {
|
||||
proxy, err := ParseProxy("http://proxy.example.com:8080/path%20with%20spaces")
|
||||
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, proxy)
|
||||
assert.Equal(t, "http://proxy.example.com:8080/path%20with%20spaces", proxy.String())
|
||||
})
|
||||
|
||||
t.Run("ParseProxy error message format", func(t *testing.T) {
|
||||
_, err := ParseProxy("http://[invalid-ipv6")
|
||||
|
||||
require.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "failed to parse proxy URL:")
|
||||
assert.Contains(t, err.Error(), "missing ']' in host")
|
||||
})
|
||||
}
|
Reference in New Issue
Block a user