mirror of
https://github.com/aykhans/dodo.git
synced 2025-09-08 03:50:46 +00:00
1061 lines
30 KiB
Go
1061 lines
30 KiB
Go
package config
|
|
|
|
import (
|
|
"fmt"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"net/url"
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/aykhans/dodo/pkg/types"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
"go.yaml.in/yaml/v4"
|
|
)
|
|
|
|
func TestNewConfigFileParser(t *testing.T) {
|
|
t.Run("NewConfigFileParser with valid config file", func(t *testing.T) {
|
|
configFile, _ := types.ParseConfigFile("test.yaml")
|
|
parser := NewConfigFileParser(*configFile)
|
|
|
|
require.NotNil(t, parser)
|
|
assert.Equal(t, *configFile, parser.configFile)
|
|
})
|
|
|
|
t.Run("NewConfigFileParser with remote config file", func(t *testing.T) {
|
|
configFile, _ := types.ParseConfigFile("https://example.com/config.yaml")
|
|
parser := NewConfigFileParser(*configFile)
|
|
|
|
require.NotNil(t, parser)
|
|
assert.Equal(t, *configFile, parser.configFile)
|
|
assert.Equal(t, types.ConfigFileLocationRemote, parser.configFile.LocationType())
|
|
})
|
|
}
|
|
|
|
func TestStringOrSliceField_UnmarshalYAML(t *testing.T) {
|
|
t.Run("UnmarshalYAML with single string", func(t *testing.T) {
|
|
yamlData := `value: single_string`
|
|
type testStruct struct {
|
|
Value stringOrSliceField `yaml:"value"`
|
|
}
|
|
|
|
var ts testStruct
|
|
err := yaml.Unmarshal([]byte(yamlData), &ts)
|
|
|
|
require.NoError(t, err)
|
|
assert.Equal(t, stringOrSliceField{"single_string"}, ts.Value)
|
|
})
|
|
|
|
t.Run("UnmarshalYAML with array of strings", func(t *testing.T) {
|
|
yamlData := `value:
|
|
- first
|
|
- second
|
|
- third`
|
|
type testStruct struct {
|
|
Value stringOrSliceField `yaml:"value"`
|
|
}
|
|
|
|
var ts testStruct
|
|
err := yaml.Unmarshal([]byte(yamlData), &ts)
|
|
|
|
require.NoError(t, err)
|
|
assert.Equal(t, stringOrSliceField{"first", "second", "third"}, ts.Value)
|
|
})
|
|
|
|
t.Run("UnmarshalYAML with empty array", func(t *testing.T) {
|
|
yamlData := `value: []`
|
|
type testStruct struct {
|
|
Value stringOrSliceField `yaml:"value"`
|
|
}
|
|
|
|
var ts testStruct
|
|
err := yaml.Unmarshal([]byte(yamlData), &ts)
|
|
|
|
require.NoError(t, err)
|
|
assert.Empty(t, ts.Value)
|
|
})
|
|
|
|
t.Run("UnmarshalYAML with invalid type", func(t *testing.T) {
|
|
yamlData := `value:
|
|
key: value`
|
|
type testStruct struct {
|
|
Value stringOrSliceField `yaml:"value"`
|
|
}
|
|
|
|
var ts testStruct
|
|
err := yaml.Unmarshal([]byte(yamlData), &ts)
|
|
|
|
require.Error(t, err)
|
|
assert.Contains(t, err.Error(), "expected a string or a sequence of strings")
|
|
})
|
|
}
|
|
|
|
func TestConfigFileParser_ParseYAML(t *testing.T) {
|
|
t.Run("ParseYAML with all fields", func(t *testing.T) {
|
|
yamlData := `
|
|
method: POST
|
|
url: https://api.example.com/endpoint
|
|
timeout: 30s
|
|
dodos: 10
|
|
requests: 1000
|
|
duration: 5m
|
|
yes: true
|
|
skipVerify: true
|
|
params:
|
|
- key1=value1
|
|
- key2=value2
|
|
headers:
|
|
- "Content-Type: application/json"
|
|
- "Authorization: Bearer token"
|
|
cookies:
|
|
- session=abc123
|
|
- user=john
|
|
body:
|
|
- '{"data": "test1"}'
|
|
- '{"data": "test2"}'
|
|
proxy:
|
|
- http://proxy1.example.com:8080
|
|
- socks5://proxy2.example.com:1080
|
|
files:
|
|
- config1.yaml
|
|
- config2.yml`
|
|
|
|
configFile, _ := types.ParseConfigFile("test.yaml")
|
|
parser := NewConfigFileParser(*configFile)
|
|
config, err := parser.ParseYAML([]byte(yamlData))
|
|
|
|
require.NoError(t, err)
|
|
require.NotNil(t, config)
|
|
|
|
assert.Equal(t, "POST", *config.Method)
|
|
assert.Equal(t, "https://api.example.com/endpoint", config.URL.String())
|
|
assert.Equal(t, 30*time.Second, *config.Timeout)
|
|
assert.Equal(t, uint(10), *config.DodosCount)
|
|
assert.Equal(t, uint(1000), *config.RequestCount)
|
|
assert.Equal(t, 5*time.Minute, *config.Duration)
|
|
assert.True(t, *config.Yes)
|
|
assert.True(t, *config.SkipVerify)
|
|
|
|
assert.Len(t, config.Params, 2)
|
|
assert.Equal(t, "key1", config.Params[0].Key)
|
|
assert.Equal(t, []string{"value1"}, config.Params[0].Value)
|
|
|
|
assert.Len(t, config.Headers, 2)
|
|
assert.Equal(t, "Content-Type", config.Headers[0].Key)
|
|
assert.Equal(t, []string{"application/json"}, config.Headers[0].Value)
|
|
|
|
assert.Len(t, config.Cookies, 2)
|
|
assert.Equal(t, "session", config.Cookies[0].Key)
|
|
|
|
assert.Len(t, config.Bodies, 2)
|
|
assert.Equal(t, types.Body(`{"data": "test1"}`), config.Bodies[0]) //nolint:testifylint
|
|
|
|
assert.Len(t, config.Proxies, 2)
|
|
assert.Equal(t, "http://proxy1.example.com:8080", config.Proxies[0].String())
|
|
|
|
assert.Len(t, config.Files, 2)
|
|
})
|
|
|
|
t.Run("ParseYAML with single value fields as strings", func(t *testing.T) {
|
|
yamlData := `
|
|
params: key=value
|
|
headers: "Content-Type: application/json"
|
|
cookies: session=token
|
|
body: '{"data": "test"}'
|
|
proxy: http://proxy.example.com:8080
|
|
files: config.yaml`
|
|
|
|
configFile, _ := types.ParseConfigFile("test.yaml")
|
|
parser := NewConfigFileParser(*configFile)
|
|
config, err := parser.ParseYAML([]byte(yamlData))
|
|
|
|
require.NoError(t, err)
|
|
require.NotNil(t, config)
|
|
|
|
assert.Len(t, config.Params, 1)
|
|
assert.Equal(t, "key", config.Params[0].Key)
|
|
|
|
assert.Len(t, config.Headers, 1)
|
|
assert.Equal(t, "Content-Type", config.Headers[0].Key)
|
|
|
|
assert.Len(t, config.Cookies, 1)
|
|
assert.Equal(t, "session", config.Cookies[0].Key)
|
|
|
|
assert.Len(t, config.Bodies, 1)
|
|
assert.Equal(t, types.Body(`{"data": "test"}`), config.Bodies[0]) //nolint:testifylint
|
|
|
|
assert.Len(t, config.Proxies, 1)
|
|
assert.Equal(t, "http://proxy.example.com:8080", config.Proxies[0].String())
|
|
|
|
assert.Len(t, config.Files, 1)
|
|
})
|
|
|
|
t.Run("ParseYAML with minimal fields", func(t *testing.T) {
|
|
yamlData := `
|
|
method: GET
|
|
url: https://example.com`
|
|
|
|
configFile, _ := types.ParseConfigFile("test.yaml")
|
|
parser := NewConfigFileParser(*configFile)
|
|
config, err := parser.ParseYAML([]byte(yamlData))
|
|
|
|
require.NoError(t, err)
|
|
require.NotNil(t, config)
|
|
|
|
assert.Equal(t, "GET", *config.Method)
|
|
assert.Equal(t, "https://example.com", config.URL.String())
|
|
|
|
// Check other fields are nil or empty
|
|
assert.Nil(t, config.Timeout)
|
|
assert.Nil(t, config.DodosCount)
|
|
assert.Nil(t, config.RequestCount)
|
|
assert.Nil(t, config.Duration)
|
|
assert.Nil(t, config.Yes)
|
|
assert.Nil(t, config.SkipVerify)
|
|
assert.Empty(t, config.Params)
|
|
assert.Empty(t, config.Headers)
|
|
assert.Empty(t, config.Cookies)
|
|
assert.Empty(t, config.Bodies)
|
|
assert.Empty(t, config.Proxies)
|
|
assert.Empty(t, config.Files)
|
|
})
|
|
|
|
t.Run("ParseYAML with invalid URL", func(t *testing.T) {
|
|
yamlData := `
|
|
url: "://invalid-url"`
|
|
|
|
configFile, _ := types.ParseConfigFile("test.yaml")
|
|
parser := NewConfigFileParser(*configFile)
|
|
config, err := parser.ParseYAML([]byte(yamlData))
|
|
|
|
assert.Nil(t, config)
|
|
var fieldErr types.FieldParseErrors
|
|
require.ErrorAs(t, err, &fieldErr)
|
|
assert.Len(t, fieldErr.Errors, 1)
|
|
assert.Equal(t, "url", fieldErr.Errors[0].Field)
|
|
assert.Equal(t, "://invalid-url", fieldErr.Errors[0].Value)
|
|
})
|
|
|
|
t.Run("ParseYAML with invalid proxy", func(t *testing.T) {
|
|
yamlData := `
|
|
proxy:
|
|
- http://valid-proxy.com:8080
|
|
- "://invalid-proxy"`
|
|
|
|
configFile, _ := types.ParseConfigFile("test.yaml")
|
|
parser := NewConfigFileParser(*configFile)
|
|
config, err := parser.ParseYAML([]byte(yamlData))
|
|
|
|
assert.Nil(t, config)
|
|
var fieldErr types.FieldParseErrors
|
|
require.ErrorAs(t, err, &fieldErr)
|
|
assert.Len(t, fieldErr.Errors, 1)
|
|
assert.Equal(t, "proxy[1]", fieldErr.Errors[0].Field)
|
|
assert.Equal(t, "://invalid-proxy", fieldErr.Errors[0].Value)
|
|
})
|
|
|
|
t.Run("ParseYAML with invalid YAML", func(t *testing.T) {
|
|
yamlData := `
|
|
method: POST
|
|
invalid yaml structure:
|
|
nested: improperly
|
|
url: https://example.com`
|
|
|
|
configFile, _ := types.ParseConfigFile("test.yaml")
|
|
parser := NewConfigFileParser(*configFile)
|
|
config, err := parser.ParseYAML([]byte(yamlData))
|
|
|
|
// The YAML parser may handle this differently, so let's check if we get any error
|
|
if err == nil {
|
|
// If no error, check if the config has expected values
|
|
require.NotNil(t, config)
|
|
require.NotNil(t, config.Method)
|
|
assert.Equal(t, "POST", *config.Method)
|
|
} else {
|
|
// If error, it should be an UnmarshalError
|
|
var unmarshalErr types.UnmarshalError
|
|
require.ErrorAs(t, err, &unmarshalErr)
|
|
}
|
|
})
|
|
|
|
t.Run("ParseYAML with empty data", func(t *testing.T) {
|
|
yamlData := ``
|
|
|
|
configFile, _ := types.ParseConfigFile("test.yaml")
|
|
parser := NewConfigFileParser(*configFile)
|
|
config, err := parser.ParseYAML([]byte(yamlData))
|
|
|
|
require.NoError(t, err)
|
|
require.NotNil(t, config)
|
|
|
|
// All fields should be nil or empty
|
|
assert.Nil(t, config.Method)
|
|
assert.Nil(t, config.URL)
|
|
assert.Nil(t, config.Timeout)
|
|
assert.Nil(t, config.DodosCount)
|
|
assert.Nil(t, config.RequestCount)
|
|
assert.Nil(t, config.Duration)
|
|
assert.Nil(t, config.Yes)
|
|
assert.Nil(t, config.SkipVerify)
|
|
assert.Empty(t, config.Params)
|
|
assert.Empty(t, config.Headers)
|
|
assert.Empty(t, config.Cookies)
|
|
assert.Empty(t, config.Bodies)
|
|
assert.Empty(t, config.Proxies)
|
|
assert.Empty(t, config.Files)
|
|
})
|
|
|
|
t.Run("ParseYAML with boolean fields", func(t *testing.T) {
|
|
yamlData := `
|
|
yes: false
|
|
skipVerify: false`
|
|
|
|
configFile, _ := types.ParseConfigFile("test.yaml")
|
|
parser := NewConfigFileParser(*configFile)
|
|
config, err := parser.ParseYAML([]byte(yamlData))
|
|
|
|
require.NoError(t, err)
|
|
require.NotNil(t, config)
|
|
require.NotNil(t, config.Yes)
|
|
assert.False(t, *config.Yes)
|
|
require.NotNil(t, config.SkipVerify)
|
|
assert.False(t, *config.SkipVerify)
|
|
})
|
|
|
|
t.Run("ParseYAML with zero values", func(t *testing.T) {
|
|
yamlData := `
|
|
timeout: 0s
|
|
dodos: 0
|
|
requests: 0
|
|
duration: 0s`
|
|
|
|
configFile, _ := types.ParseConfigFile("test.yaml")
|
|
parser := NewConfigFileParser(*configFile)
|
|
config, err := parser.ParseYAML([]byte(yamlData))
|
|
|
|
require.NoError(t, err)
|
|
require.NotNil(t, config)
|
|
assert.Equal(t, time.Duration(0), *config.Timeout)
|
|
assert.Equal(t, uint(0), *config.DodosCount)
|
|
assert.Equal(t, uint(0), *config.RequestCount)
|
|
assert.Equal(t, time.Duration(0), *config.Duration)
|
|
})
|
|
|
|
t.Run("ParseYAML with nested config files", func(t *testing.T) {
|
|
yamlData := `
|
|
files:
|
|
- /path/to/config1.yaml
|
|
- https://example.com/config2.yml
|
|
- relative/config3.yaml`
|
|
|
|
configFile, _ := types.ParseConfigFile("test.yaml")
|
|
parser := NewConfigFileParser(*configFile)
|
|
config, err := parser.ParseYAML([]byte(yamlData))
|
|
|
|
require.NoError(t, err)
|
|
require.NotNil(t, config)
|
|
assert.Len(t, config.Files, 3)
|
|
|
|
// Check file paths
|
|
assert.Equal(t, "/path/to/config1.yaml", config.Files[0].Path())
|
|
assert.Equal(t, "https://example.com/config2.yml", config.Files[1].Path())
|
|
assert.Equal(t, "relative/config3.yaml", config.Files[2].Path())
|
|
|
|
// Check file types
|
|
assert.Equal(t, types.ConfigFileTypeYAML, config.Files[0].Type())
|
|
assert.Equal(t, types.ConfigFileTypeYAML, config.Files[1].Type())
|
|
assert.Equal(t, types.ConfigFileTypeYAML, config.Files[2].Type())
|
|
|
|
// Check location types
|
|
assert.Equal(t, types.ConfigFileLocationLocal, config.Files[0].LocationType())
|
|
assert.Equal(t, types.ConfigFileLocationRemote, config.Files[1].LocationType())
|
|
assert.Equal(t, types.ConfigFileLocationLocal, config.Files[2].LocationType())
|
|
})
|
|
|
|
t.Run("ParseYAML with complex durations", func(t *testing.T) {
|
|
yamlData := `
|
|
timeout: 1h30m45s
|
|
duration: 2h15m`
|
|
|
|
configFile, _ := types.ParseConfigFile("test.yaml")
|
|
parser := NewConfigFileParser(*configFile)
|
|
config, err := parser.ParseYAML([]byte(yamlData))
|
|
|
|
require.NoError(t, err)
|
|
require.NotNil(t, config)
|
|
expectedTimeout := time.Hour + 30*time.Minute + 45*time.Second
|
|
expectedDuration := 2*time.Hour + 15*time.Minute
|
|
assert.Equal(t, expectedTimeout, *config.Timeout)
|
|
assert.Equal(t, expectedDuration, *config.Duration)
|
|
})
|
|
|
|
t.Run("ParseYAML with multiple headers with same key", func(t *testing.T) {
|
|
yamlData := `
|
|
headers:
|
|
- "Accept: text/html"
|
|
- "Accept: application/json"
|
|
- "Content-Type: application/json"`
|
|
|
|
configFile, _ := types.ParseConfigFile("test.yaml")
|
|
parser := NewConfigFileParser(*configFile)
|
|
config, err := parser.ParseYAML([]byte(yamlData))
|
|
|
|
require.NoError(t, err)
|
|
require.NotNil(t, config)
|
|
assert.Len(t, config.Headers, 2)
|
|
|
|
// Check that Accept header has both values
|
|
acceptValue := config.Headers.GetValue("Accept")
|
|
require.NotNil(t, acceptValue)
|
|
assert.Equal(t, []string{"text/html", "application/json"}, *acceptValue)
|
|
})
|
|
|
|
t.Run("ParseYAML with multiple params with same key", func(t *testing.T) {
|
|
yamlData := `
|
|
params:
|
|
- filter=active
|
|
- filter=verified
|
|
- limit=10`
|
|
|
|
configFile, _ := types.ParseConfigFile("test.yaml")
|
|
parser := NewConfigFileParser(*configFile)
|
|
config, err := parser.ParseYAML([]byte(yamlData))
|
|
|
|
require.NoError(t, err)
|
|
require.NotNil(t, config)
|
|
assert.Len(t, config.Params, 2)
|
|
|
|
// Check that filter param has both values
|
|
filterValue := config.Params.GetValue("filter")
|
|
require.NotNil(t, filterValue)
|
|
assert.Equal(t, []string{"active", "verified"}, *filterValue)
|
|
})
|
|
|
|
t.Run("ParseYAML with special characters in values", func(t *testing.T) {
|
|
yamlData := `
|
|
headers:
|
|
- "X-Special: !@#$%^&*()"
|
|
body:
|
|
- '{"special": "characters: !@#$%^&*()"}'
|
|
cookies:
|
|
- "session=abc123!@#"`
|
|
|
|
configFile, _ := types.ParseConfigFile("test.yaml")
|
|
parser := NewConfigFileParser(*configFile)
|
|
config, err := parser.ParseYAML([]byte(yamlData))
|
|
|
|
require.NoError(t, err)
|
|
require.NotNil(t, config)
|
|
|
|
assert.Len(t, config.Headers, 1)
|
|
assert.Contains(t, config.Headers[0].Value[0], "!@#$%^&*()")
|
|
|
|
assert.Len(t, config.Bodies, 1)
|
|
assert.Contains(t, string(config.Bodies[0]), "!@#$%^&*()")
|
|
|
|
assert.Len(t, config.Cookies, 1)
|
|
assert.Equal(t, "abc123!@#", config.Cookies[0].Value[0])
|
|
})
|
|
}
|
|
|
|
func TestConfigFileParser_Parse_LocalFile(t *testing.T) {
|
|
t.Run("Parse local YAML file with valid content", func(t *testing.T) {
|
|
// Create a temporary file
|
|
tmpDir := t.TempDir()
|
|
tmpFile := filepath.Join(tmpDir, "config.yaml")
|
|
|
|
yamlContent := `
|
|
method: POST
|
|
url: https://api.example.com
|
|
dodos: 5
|
|
yes: true`
|
|
|
|
err := os.WriteFile(tmpFile, []byte(yamlContent), 0644)
|
|
require.NoError(t, err)
|
|
|
|
configFile, _ := types.ParseConfigFile(tmpFile)
|
|
parser := NewConfigFileParser(*configFile)
|
|
config, err := parser.Parse()
|
|
|
|
require.NoError(t, err)
|
|
require.NotNil(t, config)
|
|
assert.Equal(t, "POST", *config.Method)
|
|
assert.Equal(t, "https://api.example.com", config.URL.String())
|
|
assert.Equal(t, uint(5), *config.DodosCount)
|
|
assert.True(t, *config.Yes)
|
|
})
|
|
|
|
t.Run("Parse local file that doesn't exist", func(t *testing.T) {
|
|
configFile, _ := types.ParseConfigFile("/nonexistent/file.yaml")
|
|
parser := NewConfigFileParser(*configFile)
|
|
config, err := parser.Parse()
|
|
|
|
assert.Nil(t, config)
|
|
var readErr types.ConfigFileReadError
|
|
require.ErrorAs(t, err, &readErr)
|
|
})
|
|
|
|
t.Run("Parse local file with invalid YAML", func(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
tmpFile := filepath.Join(tmpDir, "invalid.yaml")
|
|
|
|
invalidYAML := `
|
|
method: POST
|
|
invalid indentation here:
|
|
nested: improperly
|
|
url: https://example.com`
|
|
|
|
err := os.WriteFile(tmpFile, []byte(invalidYAML), 0644)
|
|
require.NoError(t, err)
|
|
|
|
configFile, _ := types.ParseConfigFile(tmpFile)
|
|
parser := NewConfigFileParser(*configFile)
|
|
config, err := parser.Parse()
|
|
|
|
// The YAML parser may handle this differently
|
|
if err == nil {
|
|
// If no error, check if the config has expected values
|
|
require.NotNil(t, config)
|
|
require.NotNil(t, config.Method)
|
|
assert.Equal(t, "POST", *config.Method)
|
|
} else {
|
|
// If error, it should be an UnmarshalError
|
|
var unmarshalErr types.UnmarshalError
|
|
require.ErrorAs(t, err, &unmarshalErr)
|
|
}
|
|
})
|
|
|
|
t.Run("Parse local file with unknown extension", func(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
tmpFile := filepath.Join(tmpDir, "config.unknown")
|
|
|
|
yamlContent := `
|
|
method: GET
|
|
url: https://example.com`
|
|
|
|
err := os.WriteFile(tmpFile, []byte(yamlContent), 0644)
|
|
require.NoError(t, err)
|
|
|
|
configFile, _ := types.ParseConfigFile(tmpFile)
|
|
assert.Equal(t, types.ConfigFileTypeUnknown, configFile.Type())
|
|
|
|
parser := NewConfigFileParser(*configFile)
|
|
config, err := parser.Parse()
|
|
|
|
// Should still parse as YAML for unknown types
|
|
require.NoError(t, err)
|
|
require.NotNil(t, config)
|
|
assert.Equal(t, "GET", *config.Method)
|
|
})
|
|
|
|
t.Run("Parse local file with .yml extension", func(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
tmpFile := filepath.Join(tmpDir, "config.yml")
|
|
|
|
yamlContent := `
|
|
method: DELETE
|
|
url: https://api.example.com/resource`
|
|
|
|
err := os.WriteFile(tmpFile, []byte(yamlContent), 0644)
|
|
require.NoError(t, err)
|
|
|
|
configFile, _ := types.ParseConfigFile(tmpFile)
|
|
assert.Equal(t, types.ConfigFileTypeYAML, configFile.Type())
|
|
|
|
parser := NewConfigFileParser(*configFile)
|
|
config, err := parser.Parse()
|
|
|
|
require.NoError(t, err)
|
|
require.NotNil(t, config)
|
|
assert.Equal(t, "DELETE", *config.Method)
|
|
})
|
|
}
|
|
|
|
func TestConfigFileParser_Parse_RemoteFile(t *testing.T) {
|
|
t.Run("Parse remote YAML file with valid content", func(t *testing.T) {
|
|
yamlContent := `
|
|
method: PUT
|
|
url: https://api.example.com/update
|
|
timeout: 15s
|
|
dodos: 3`
|
|
|
|
// Create test HTTP server
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.WriteHeader(http.StatusOK)
|
|
w.Write([]byte(yamlContent))
|
|
}))
|
|
defer server.Close()
|
|
|
|
configFile, _ := types.ParseConfigFile(server.URL + "/config.yaml")
|
|
parser := NewConfigFileParser(*configFile)
|
|
config, err := parser.Parse()
|
|
|
|
require.NoError(t, err)
|
|
require.NotNil(t, config)
|
|
assert.Equal(t, "PUT", *config.Method)
|
|
assert.Equal(t, "https://api.example.com/update", config.URL.String())
|
|
assert.Equal(t, 15*time.Second, *config.Timeout)
|
|
assert.Equal(t, uint(3), *config.DodosCount)
|
|
})
|
|
|
|
t.Run("Parse remote file with 404 status", func(t *testing.T) {
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.WriteHeader(http.StatusNotFound)
|
|
w.Write([]byte("Not Found"))
|
|
}))
|
|
defer server.Close()
|
|
|
|
configFile, _ := types.ParseConfigFile(server.URL + "/missing.yaml")
|
|
parser := NewConfigFileParser(*configFile)
|
|
config, err := parser.Parse()
|
|
|
|
assert.Nil(t, config)
|
|
var readErr types.ConfigFileReadError
|
|
require.ErrorAs(t, err, &readErr)
|
|
assert.Contains(t, err.Error(), "404")
|
|
})
|
|
|
|
t.Run("Parse remote file with server error", func(t *testing.T) {
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
w.Write([]byte("Internal Server Error"))
|
|
}))
|
|
defer server.Close()
|
|
|
|
configFile, _ := types.ParseConfigFile(server.URL + "/error.yaml")
|
|
parser := NewConfigFileParser(*configFile)
|
|
config, err := parser.Parse()
|
|
|
|
assert.Nil(t, config)
|
|
var readErr types.ConfigFileReadError
|
|
require.ErrorAs(t, err, &readErr)
|
|
assert.Contains(t, err.Error(), "500")
|
|
})
|
|
|
|
t.Run("Parse remote file with invalid YAML", func(t *testing.T) {
|
|
invalidYAML := `
|
|
method: POST
|
|
bad indentation:
|
|
nested: improperly
|
|
url: https://example.com`
|
|
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.WriteHeader(http.StatusOK)
|
|
w.Write([]byte(invalidYAML))
|
|
}))
|
|
defer server.Close()
|
|
|
|
configFile, _ := types.ParseConfigFile(server.URL + "/invalid.yaml")
|
|
parser := NewConfigFileParser(*configFile)
|
|
config, err := parser.Parse()
|
|
|
|
// The YAML parser may handle this differently
|
|
if err == nil {
|
|
// If no error, check if the config has expected values
|
|
require.NotNil(t, config)
|
|
require.NotNil(t, config.Method)
|
|
assert.Equal(t, "POST", *config.Method)
|
|
} else {
|
|
// If error, it should be an UnmarshalError
|
|
var unmarshalErr types.UnmarshalError
|
|
require.ErrorAs(t, err, &unmarshalErr)
|
|
}
|
|
})
|
|
|
|
t.Run("Parse remote file with network error", func(t *testing.T) {
|
|
// Use an invalid URL that will cause a network error
|
|
configFile, _ := types.ParseConfigFile("http://invalid-domain-that-does-not-exist-12345.com/config.yaml")
|
|
parser := NewConfigFileParser(*configFile)
|
|
config, err := parser.Parse()
|
|
|
|
assert.Nil(t, config)
|
|
var readErr types.ConfigFileReadError
|
|
require.ErrorAs(t, err, &readErr)
|
|
})
|
|
|
|
t.Run("Parse remote file with redirect", func(t *testing.T) {
|
|
yamlContent := `
|
|
method: GET
|
|
url: https://redirected.example.com`
|
|
|
|
redirectCount := 0
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if redirectCount < 1 {
|
|
redirectCount++
|
|
http.Redirect(w, r, "/final", http.StatusMovedPermanently)
|
|
return
|
|
}
|
|
w.WriteHeader(http.StatusOK)
|
|
w.Write([]byte(yamlContent))
|
|
}))
|
|
defer server.Close()
|
|
|
|
configFile, _ := types.ParseConfigFile(server.URL + "/redirect.yaml")
|
|
parser := NewConfigFileParser(*configFile)
|
|
config, err := parser.Parse()
|
|
|
|
require.NoError(t, err)
|
|
require.NotNil(t, config)
|
|
assert.Equal(t, "GET", *config.Method)
|
|
assert.Equal(t, "https://redirected.example.com", config.URL.String())
|
|
})
|
|
}
|
|
|
|
func TestConfigFileParser_InterfaceConformance(t *testing.T) {
|
|
t.Run("ConfigFileParser implements IParser interface", func(t *testing.T) {
|
|
configFile, _ := types.ParseConfigFile("test.yaml")
|
|
parser := ConfigFileParser{configFile: *configFile}
|
|
|
|
// This test verifies that ConfigFileParser implements the IParser interface
|
|
var _ IParser = parser
|
|
|
|
// Also test that the pointer type implements the interface
|
|
var _ IParser = &parser
|
|
})
|
|
}
|
|
|
|
func TestConfigFileParser_EdgeCases(t *testing.T) {
|
|
t.Run("ParseYAML with very large numbers", func(t *testing.T) {
|
|
yamlData := `
|
|
dodos: 999999999
|
|
requests: 999999999`
|
|
|
|
configFile, _ := types.ParseConfigFile("test.yaml")
|
|
parser := NewConfigFileParser(*configFile)
|
|
config, err := parser.ParseYAML([]byte(yamlData))
|
|
|
|
require.NoError(t, err)
|
|
require.NotNil(t, config)
|
|
assert.Equal(t, uint(999999999), *config.DodosCount)
|
|
assert.Equal(t, uint(999999999), *config.RequestCount)
|
|
})
|
|
|
|
t.Run("ParseYAML with empty arrays", func(t *testing.T) {
|
|
yamlData := `
|
|
params: []
|
|
headers: []
|
|
cookies: []
|
|
body: []
|
|
proxy: []
|
|
files: []`
|
|
|
|
configFile, _ := types.ParseConfigFile("test.yaml")
|
|
parser := NewConfigFileParser(*configFile)
|
|
config, err := parser.ParseYAML([]byte(yamlData))
|
|
|
|
require.NoError(t, err)
|
|
require.NotNil(t, config)
|
|
assert.Empty(t, config.Params)
|
|
assert.Empty(t, config.Headers)
|
|
assert.Empty(t, config.Cookies)
|
|
assert.Empty(t, config.Bodies)
|
|
assert.Empty(t, config.Proxies)
|
|
assert.Empty(t, config.Files)
|
|
})
|
|
|
|
t.Run("ParseYAML with multiline body", func(t *testing.T) {
|
|
yamlData := `
|
|
body:
|
|
- |
|
|
This is a
|
|
multiline
|
|
body content
|
|
- "Single line body"`
|
|
|
|
configFile, _ := types.ParseConfigFile("test.yaml")
|
|
parser := NewConfigFileParser(*configFile)
|
|
config, err := parser.ParseYAML([]byte(yamlData))
|
|
|
|
require.NoError(t, err)
|
|
require.NotNil(t, config)
|
|
assert.Len(t, config.Bodies, 2)
|
|
assert.Contains(t, string(config.Bodies[0]), "multiline")
|
|
assert.Equal(t, types.Body("Single line body"), config.Bodies[1])
|
|
})
|
|
|
|
t.Run("ParseYAML with URL containing query parameters", func(t *testing.T) {
|
|
yamlData := `
|
|
url: "https://api.example.com/endpoint?param1=value1¶m2=value2#fragment"`
|
|
|
|
configFile, _ := types.ParseConfigFile("test.yaml")
|
|
parser := NewConfigFileParser(*configFile)
|
|
config, err := parser.ParseYAML([]byte(yamlData))
|
|
|
|
require.NoError(t, err)
|
|
require.NotNil(t, config)
|
|
require.NotNil(t, config.URL)
|
|
|
|
parsedURL, _ := url.Parse("https://api.example.com/endpoint?param1=value1¶m2=value2#fragment")
|
|
assert.Equal(t, parsedURL, config.URL)
|
|
assert.Equal(t, "param1=value1¶m2=value2", config.URL.RawQuery)
|
|
assert.Equal(t, "fragment", config.URL.Fragment)
|
|
})
|
|
|
|
t.Run("ParseYAML with headers containing colons in values", func(t *testing.T) {
|
|
yamlData := `
|
|
headers:
|
|
- "Timestamp: 2024-01-01T10:30:00Z"
|
|
- "Ratio: 16:9"`
|
|
|
|
configFile, _ := types.ParseConfigFile("test.yaml")
|
|
parser := NewConfigFileParser(*configFile)
|
|
config, err := parser.ParseYAML([]byte(yamlData))
|
|
|
|
require.NoError(t, err)
|
|
require.NotNil(t, config)
|
|
assert.Len(t, config.Headers, 2)
|
|
assert.Equal(t, "Timestamp", config.Headers[0].Key)
|
|
assert.Equal(t, []string{"2024-01-01T10:30:00Z"}, config.Headers[0].Value)
|
|
assert.Equal(t, "Ratio", config.Headers[1].Key)
|
|
assert.Equal(t, []string{"16:9"}, config.Headers[1].Value)
|
|
})
|
|
|
|
t.Run("ParseYAML with params containing equals in values", func(t *testing.T) {
|
|
yamlData := `
|
|
params:
|
|
- "equation=a=b+c"
|
|
- "formula=x==y"`
|
|
|
|
configFile, _ := types.ParseConfigFile("test.yaml")
|
|
parser := NewConfigFileParser(*configFile)
|
|
config, err := parser.ParseYAML([]byte(yamlData))
|
|
|
|
require.NoError(t, err)
|
|
require.NotNil(t, config)
|
|
assert.Len(t, config.Params, 2)
|
|
assert.Equal(t, "equation", config.Params[0].Key)
|
|
assert.Equal(t, []string{"a=b+c"}, config.Params[0].Value)
|
|
assert.Equal(t, "formula", config.Params[1].Key)
|
|
assert.Equal(t, []string{"x==y"}, config.Params[1].Value)
|
|
})
|
|
|
|
t.Run("ParseYAML with null values", func(t *testing.T) {
|
|
yamlData := `
|
|
method: null
|
|
url: null
|
|
timeout: null
|
|
dodos: null
|
|
requests: null
|
|
duration: null
|
|
yes: null
|
|
skipVerify: null`
|
|
|
|
configFile, _ := types.ParseConfigFile("test.yaml")
|
|
parser := NewConfigFileParser(*configFile)
|
|
config, err := parser.ParseYAML([]byte(yamlData))
|
|
|
|
require.NoError(t, err)
|
|
require.NotNil(t, config)
|
|
assert.Nil(t, config.Method)
|
|
assert.Nil(t, config.URL)
|
|
assert.Nil(t, config.Timeout)
|
|
assert.Nil(t, config.DodosCount)
|
|
assert.Nil(t, config.RequestCount)
|
|
assert.Nil(t, config.Duration)
|
|
assert.Nil(t, config.Yes)
|
|
assert.Nil(t, config.SkipVerify)
|
|
})
|
|
|
|
t.Run("Parse with empty file content", func(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
tmpFile := filepath.Join(tmpDir, "empty.yaml")
|
|
|
|
err := os.WriteFile(tmpFile, []byte(""), 0644)
|
|
require.NoError(t, err)
|
|
|
|
configFile, _ := types.ParseConfigFile(tmpFile)
|
|
parser := NewConfigFileParser(*configFile)
|
|
config, err := parser.Parse()
|
|
|
|
require.NoError(t, err)
|
|
require.NotNil(t, config)
|
|
// Should return empty config without error
|
|
assert.Nil(t, config.Method)
|
|
assert.Nil(t, config.URL)
|
|
})
|
|
|
|
t.Run("ParseYAML with comments", func(t *testing.T) {
|
|
yamlData := `
|
|
# This is a comment
|
|
method: POST # Inline comment
|
|
url: https://example.com
|
|
# Another comment
|
|
dodos: 5`
|
|
|
|
configFile, _ := types.ParseConfigFile("test.yaml")
|
|
parser := NewConfigFileParser(*configFile)
|
|
config, err := parser.ParseYAML([]byte(yamlData))
|
|
|
|
require.NoError(t, err)
|
|
require.NotNil(t, config)
|
|
assert.Equal(t, "POST", *config.Method)
|
|
assert.Equal(t, uint(5), *config.DodosCount)
|
|
})
|
|
}
|
|
|
|
func TestConfigFileParser_ComplexScenarios(t *testing.T) {
|
|
t.Run("Parse complete production configuration", func(t *testing.T) {
|
|
yamlData := `
|
|
# Production configuration
|
|
method: POST
|
|
url: https://api.production.com/v2/endpoint
|
|
timeout: 60s
|
|
dodos: 100
|
|
requests: 10000
|
|
duration: 1h
|
|
yes: false
|
|
skipVerify: false
|
|
|
|
# Authentication
|
|
headers:
|
|
- "Authorization: Bearer production-token-xyz123"
|
|
- "Content-Type: application/json"
|
|
- "Accept: application/json"
|
|
- "X-API-Version: 2.0"
|
|
|
|
# Request parameters
|
|
params:
|
|
- page=1
|
|
- limit=100
|
|
- filter=active
|
|
- sort=created_desc
|
|
|
|
# Session management
|
|
cookies:
|
|
- session=prod-session-abc123
|
|
- preferences=production
|
|
- tracking=disabled
|
|
|
|
# Request bodies for testing different scenarios
|
|
body:
|
|
- '{"action": "create", "resource": "user"}'
|
|
- '{"action": "update", "resource": "profile"}'
|
|
- '{"action": "delete", "resource": "session"}'
|
|
|
|
# Corporate proxy
|
|
proxy:
|
|
- http://corporate-proxy.example.com:8080
|
|
|
|
# Additional configuration files
|
|
files:
|
|
- /configs/shared/common.yaml
|
|
- https://config-server.example.com/production.yaml`
|
|
|
|
configFile, _ := types.ParseConfigFile("production.yaml")
|
|
parser := NewConfigFileParser(*configFile)
|
|
config, err := parser.ParseYAML([]byte(yamlData))
|
|
|
|
require.NoError(t, err)
|
|
require.NotNil(t, config)
|
|
|
|
// Verify all production settings
|
|
assert.Equal(t, "POST", *config.Method)
|
|
assert.Equal(t, "https://api.production.com/v2/endpoint", config.URL.String())
|
|
assert.Equal(t, 60*time.Second, *config.Timeout)
|
|
assert.Equal(t, uint(100), *config.DodosCount)
|
|
assert.Equal(t, uint(10000), *config.RequestCount)
|
|
assert.Equal(t, time.Hour, *config.Duration)
|
|
assert.False(t, *config.Yes)
|
|
assert.False(t, *config.SkipVerify)
|
|
|
|
assert.Len(t, config.Headers, 4)
|
|
assert.Len(t, config.Params, 4)
|
|
assert.Len(t, config.Cookies, 3)
|
|
assert.Len(t, config.Bodies, 3)
|
|
assert.Len(t, config.Proxies, 1)
|
|
assert.Len(t, config.Files, 2)
|
|
})
|
|
|
|
t.Run("Parse development configuration with overrides", func(t *testing.T) {
|
|
yamlData := `
|
|
# Development configuration with testing overrides
|
|
method: GET
|
|
url: http://localhost:8080/api/test
|
|
timeout: 5s
|
|
dodos: 1
|
|
requests: 10
|
|
duration: 30s
|
|
yes: true # Auto-confirm for development
|
|
skipVerify: true # Skip certificate verification for local testing
|
|
|
|
headers:
|
|
- "X-Debug: true"
|
|
- "X-Test-Mode: enabled"
|
|
|
|
params:
|
|
- debug=true
|
|
- verbose=true
|
|
|
|
body:
|
|
- "test data"`
|
|
|
|
configFile, _ := types.ParseConfigFile("dev.yaml")
|
|
parser := NewConfigFileParser(*configFile)
|
|
config, err := parser.ParseYAML([]byte(yamlData))
|
|
|
|
require.NoError(t, err)
|
|
require.NotNil(t, config)
|
|
|
|
// Verify development settings
|
|
assert.Equal(t, "GET", *config.Method)
|
|
assert.Equal(t, "http://localhost:8080/api/test", config.URL.String())
|
|
assert.Equal(t, 5*time.Second, *config.Timeout)
|
|
assert.Equal(t, uint(1), *config.DodosCount)
|
|
assert.True(t, *config.Yes)
|
|
assert.True(t, *config.SkipVerify)
|
|
})
|
|
}
|
|
|
|
func TestConfigFileParser_RealWorldFiles(t *testing.T) {
|
|
t.Run("Parse file with permission issues", func(t *testing.T) {
|
|
if os.Geteuid() == 0 {
|
|
t.Skip("Cannot test permission issues as root")
|
|
}
|
|
|
|
tmpDir := t.TempDir()
|
|
tmpFile := filepath.Join(tmpDir, "no-read.yaml")
|
|
|
|
err := os.WriteFile(tmpFile, []byte("method: GET"), 0000)
|
|
require.NoError(t, err)
|
|
|
|
configFile, _ := types.ParseConfigFile(tmpFile)
|
|
parser := NewConfigFileParser(*configFile)
|
|
config, err := parser.Parse()
|
|
|
|
assert.Nil(t, config)
|
|
var readErr types.ConfigFileReadError
|
|
require.ErrorAs(t, err, &readErr)
|
|
})
|
|
|
|
t.Run("Parse very large configuration file", func(t *testing.T) {
|
|
// Create a large YAML with many entries
|
|
yamlData := `
|
|
method: POST
|
|
url: https://example.com
|
|
headers:`
|
|
|
|
// Add 1000 headers
|
|
for i := range 1000 {
|
|
yamlData += fmt.Sprintf("\n - \"Header-%d: value-%d\"", i, i)
|
|
}
|
|
|
|
yamlData += "\nparams:"
|
|
// Add 1000 params
|
|
for i := range 1000 {
|
|
yamlData += fmt.Sprintf("\n - param%d=value%d", i, i)
|
|
}
|
|
|
|
configFile, _ := types.ParseConfigFile("large.yaml")
|
|
parser := NewConfigFileParser(*configFile)
|
|
config, err := parser.ParseYAML([]byte(yamlData))
|
|
|
|
require.NoError(t, err)
|
|
require.NotNil(t, config)
|
|
assert.Len(t, config.Headers, 1000)
|
|
assert.Len(t, config.Params, 1000)
|
|
})
|
|
}
|