Files
go-honeybee/transport/config_test.go
T

284 lines
7.3 KiB
Go

package transport
import (
"git.wisehodl.dev/jay/go-honeybee/honeybeetest"
"github.com/stretchr/testify/assert"
"net/http"
"testing"
"time"
)
// Connection Config Tests
func TestNewConnectionConfig(t *testing.T) {
_, err := NewConnectionConfig()
assert.NoError(t, err)
// errors propagate
_, err = NewConnectionConfig(WithRetryMaxRetries(-1))
assert.Error(t, err)
_, err = NewConnectionConfig(WithRetryInitialDelay(10), WithRetryMaxDelay(1))
assert.Error(t, err)
}
// Default Tests
func TestDefaultConnectionConfig(t *testing.T) {
header := http.Header{}
header.Set("User-Agent", "honeybee/0.1.0")
conf := GetDefaultConnectionConfig()
assert.Equal(t, conf, &ConnectionConfig{
CloseHandler: nil,
RequestHeader: header,
WriteTimeout: 30 * time.Second,
PingInterval: 20 * time.Second,
IncomingBufferSize: 100,
ErrorsBufferSize: 10,
Retry: RetryConfig{
MaxRetries: 0,
InitialDelay: 1 * time.Second,
MaxDelay: 60 * time.Second,
JitterFactor: 0.2,
},
})
}
// Builder Tests
func TestApplyConnectionOptions(t *testing.T) {
conf := &ConnectionConfig{}
err := applyConnectionOptions(
conf,
WithIncomingBufferSize(256),
WithErrorsBufferSize(100),
WithRetryMaxRetries(0),
WithRetryInitialDelay(3*time.Second),
WithRetryJitterFactor(0.5),
)
assert.NoError(t, err)
assert.Equal(t, 256, conf.IncomingBufferSize)
assert.Equal(t, 100, conf.ErrorsBufferSize)
assert.Equal(t, 0, conf.Retry.MaxRetries)
assert.Equal(t, 3*time.Second, conf.Retry.InitialDelay)
assert.Equal(t, 0.5, conf.Retry.JitterFactor)
// errors propagate
err = applyConnectionOptions(
conf,
WithRetryMaxRetries(-10),
)
assert.ErrorIs(t, err, InvalidRetryMaxRetries)
}
// Option Tests
func TestWithCloseHandler(t *testing.T) {
conf := &ConnectionConfig{}
opt := WithCloseHandler(func(code int, text string) error { return nil })
err := applyConnectionOptions(conf, opt)
assert.NoError(t, err)
assert.Nil(t, conf.CloseHandler(0, ""))
}
func TestWithWriteTimeout(t *testing.T) {
conf := &ConnectionConfig{}
opt := WithWriteTimeout(30)
err := applyConnectionOptions(conf, opt)
assert.NoError(t, err)
assert.Equal(t, conf.WriteTimeout, time.Duration(30))
// zero allowed
conf = &ConnectionConfig{}
opt = WithWriteTimeout(0)
err = applyConnectionOptions(conf, opt)
assert.NoError(t, err)
assert.Equal(t, conf.WriteTimeout, time.Duration(0))
// negative disallowed
conf = &ConnectionConfig{}
opt = WithWriteTimeout(-30)
err = applyConnectionOptions(conf, opt)
assert.ErrorIs(t, err, InvalidWriteTimeout)
assert.ErrorContains(t, err, "write timeout cannot be negative")
}
func TestWithRetry(t *testing.T) {
t.Run("without retry", func(t *testing.T) {
conf := GetDefaultConnectionConfig()
opt := WithRetryDisabled()
err := applyConnectionOptions(conf, opt)
assert.NoError(t, err)
assert.True(t, conf.Retry.Disabled)
})
t.Run("with attempts", func(t *testing.T) {
conf := &ConnectionConfig{}
opt := WithRetryMaxRetries(3)
err := applyConnectionOptions(conf, opt)
assert.NoError(t, err)
assert.Equal(t, 3, conf.Retry.MaxRetries)
// zero allowed
opt = WithRetryMaxRetries(0)
err = applyConnectionOptions(conf, opt)
assert.NoError(t, err)
// negative disallowed
opt = WithRetryMaxRetries(-10)
err = applyConnectionOptions(conf, opt)
assert.ErrorIs(t, err, InvalidRetryMaxRetries)
assert.ErrorContains(t, err, "max retry count cannot be negative")
})
t.Run("with initial delay", func(t *testing.T) {
conf := &ConnectionConfig{}
opt := WithRetryInitialDelay(10 * time.Second)
err := applyConnectionOptions(conf, opt)
assert.NoError(t, err)
assert.Equal(t, 10*time.Second, conf.Retry.InitialDelay)
// zero disallowed
opt = WithRetryInitialDelay(0 * time.Second)
err = applyConnectionOptions(conf, opt)
assert.ErrorIs(t, err, InvalidRetryInitialDelay)
assert.ErrorContains(t, err, "initial delay must be positive")
// negative disallowed
opt = WithRetryInitialDelay(-10 * time.Second)
err = applyConnectionOptions(conf, opt)
assert.ErrorIs(t, err, InvalidRetryInitialDelay)
})
t.Run("with max delay", func(t *testing.T) {
conf := &ConnectionConfig{}
opt := WithRetryMaxDelay(10 * time.Second)
err := applyConnectionOptions(conf, opt)
assert.NoError(t, err)
assert.Equal(t, 10*time.Second, conf.Retry.MaxDelay)
// zero disallowed
opt = WithRetryMaxDelay(0 * time.Second)
err = applyConnectionOptions(conf, opt)
assert.ErrorIs(t, err, InvalidRetryMaxDelay)
assert.ErrorContains(t, err, "max delay must be positive")
// negative disallowed
opt = WithRetryMaxDelay(-10 * time.Second)
err = applyConnectionOptions(conf, opt)
assert.ErrorIs(t, err, InvalidRetryMaxDelay)
})
t.Run("with jitter factor", func(t *testing.T) {
conf := &ConnectionConfig{}
opt := WithRetryJitterFactor(0.2)
err := applyConnectionOptions(conf, opt)
assert.NoError(t, err)
assert.Equal(t, 0.2, conf.Retry.JitterFactor)
// negative disallowed
opt = WithRetryJitterFactor(-1)
err = applyConnectionOptions(conf, opt)
assert.ErrorIs(t, err, InvalidRetryJitterFactor)
assert.ErrorContains(t, err, "jitter factor must be between 0.0 and 1.0")
// >1 disallowed
opt = WithRetryJitterFactor(1.1)
err = applyConnectionOptions(conf, opt)
assert.ErrorIs(t, err, InvalidRetryJitterFactor)
})
}
// Validation Tests
func TestValidateConnectionConfig(t *testing.T) {
cases := []struct {
name string
conf ConnectionConfig
wantErr error
wantErrText string
}{
{
name: "valid empty",
conf: ConnectionConfig{Retry: RetryConfig{Disabled: true}},
},
{
name: "valid defaults",
conf: *GetDefaultConnectionConfig(),
},
{
name: "valid complete",
conf: ConnectionConfig{
CloseHandler: (func(code int, text string) error { return nil }),
WriteTimeout: time.Duration(30),
Retry: RetryConfig{
MaxRetries: 0,
InitialDelay: 2 * time.Second,
MaxDelay: 10 * time.Second,
JitterFactor: 0.2,
},
},
},
{
name: "invalid - initial delay > max delay",
conf: ConnectionConfig{
Retry: RetryConfig{
InitialDelay: 10 * time.Second,
MaxDelay: 1 * time.Second,
},
},
wantErrText: "initial delay may not exceed maximum delay",
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
err := ValidateConnectionConfig(&tc.conf)
if tc.wantErr != nil || tc.wantErrText != "" {
if tc.wantErr != nil {
assert.ErrorIs(t, err, tc.wantErr)
}
if tc.wantErrText != "" {
assert.ErrorContains(t, err, tc.wantErrText)
}
return
}
assert.NoError(t, err)
})
}
}
func TestConnectionConfigClone(t *testing.T) {
header := http.Header{}
header.Set("X-Test", "val")
orig := ConnectionConfig{
RequestHeader: header,
WriteTimeout: 5 * time.Second,
Retry: RetryConfig{Disabled: true},
}
cloned := orig.Clone()
// values match
assert.Equal(t, orig.WriteTimeout, cloned.WriteTimeout)
assert.Equal(t, "val", cloned.RequestHeader.Get("X-Test"))
// header is a distinct copy
cloned.RequestHeader.Set("X-Test", "mutated")
assert.Equal(t, "val", orig.RequestHeader.Get("X-Test"))
}
func TestWithConnectionDialer(t *testing.T) {
mock := &honeybeetest.MockDialer{}
conf, err := NewConnectionConfig(WithConnectionDialer(mock))
assert.NoError(t, err)
assert.Equal(t, mock, conf.Dialer)
}