964 lines
22 KiB
Go
964 lines
22 KiB
Go
|
|
package cassandra
|
|||
|
|
|
|||
|
|
import (
|
|||
|
|
"testing"
|
|||
|
|
"time"
|
|||
|
|
|
|||
|
|
"github.com/gocql/gocql"
|
|||
|
|
"github.com/stretchr/testify/assert"
|
|||
|
|
"github.com/stretchr/testify/require"
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
func TestOption_DefaultConfig(t *testing.T) {
|
|||
|
|
t.Run("defaultConfig should return valid config with all defaults", func(t *testing.T) {
|
|||
|
|
cfg := defaultConfig()
|
|||
|
|
require.NotNil(t, cfg)
|
|||
|
|
assert.Equal(t, defaultPort, cfg.Port)
|
|||
|
|
assert.Equal(t, defaultConsistency, cfg.Consistency)
|
|||
|
|
assert.Equal(t, defaultTimeoutSec, cfg.ConnectTimeoutSec)
|
|||
|
|
assert.Equal(t, defaultNumConns, cfg.NumConns)
|
|||
|
|
assert.Equal(t, defaultMaxRetries, cfg.MaxRetries)
|
|||
|
|
assert.Equal(t, defaultRetryMinInterval, cfg.RetryMinInterval)
|
|||
|
|
assert.Equal(t, defaultRetryMaxInterval, cfg.RetryMaxInterval)
|
|||
|
|
assert.Equal(t, defaultReconnectInitialInterval, cfg.ReconnectInitialInterval)
|
|||
|
|
assert.Equal(t, defaultReconnectMaxInterval, cfg.ReconnectMaxInterval)
|
|||
|
|
assert.Equal(t, defaultCqlVersion, cfg.CQLVersion)
|
|||
|
|
assert.Empty(t, cfg.Hosts)
|
|||
|
|
assert.Empty(t, cfg.Keyspace)
|
|||
|
|
assert.Empty(t, cfg.Username)
|
|||
|
|
assert.Empty(t, cfg.Password)
|
|||
|
|
assert.False(t, cfg.UseAuth)
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func TestWithHosts(t *testing.T) {
|
|||
|
|
tests := []struct {
|
|||
|
|
name string
|
|||
|
|
hosts []string
|
|||
|
|
expected []string
|
|||
|
|
}{
|
|||
|
|
{
|
|||
|
|
name: "single host",
|
|||
|
|
hosts: []string{"localhost"},
|
|||
|
|
expected: []string{"localhost"},
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
name: "multiple hosts",
|
|||
|
|
hosts: []string{"localhost", "127.0.0.1", "192.168.1.1"},
|
|||
|
|
expected: []string{"localhost", "127.0.0.1", "192.168.1.1"},
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
name: "empty hosts",
|
|||
|
|
hosts: []string{},
|
|||
|
|
expected: []string{},
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
name: "host with port",
|
|||
|
|
hosts: []string{"localhost:9042"},
|
|||
|
|
expected: []string{"localhost:9042"},
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
name: "host with domain",
|
|||
|
|
hosts: []string{"cassandra.example.com"},
|
|||
|
|
expected: []string{"cassandra.example.com"},
|
|||
|
|
},
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
for _, tt := range tests {
|
|||
|
|
t.Run(tt.name, func(t *testing.T) {
|
|||
|
|
cfg := defaultConfig()
|
|||
|
|
opt := WithHosts(tt.hosts...)
|
|||
|
|
opt(cfg)
|
|||
|
|
assert.Equal(t, tt.expected, cfg.Hosts)
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func TestWithPort(t *testing.T) {
|
|||
|
|
tests := []struct {
|
|||
|
|
name string
|
|||
|
|
port int
|
|||
|
|
expected int
|
|||
|
|
}{
|
|||
|
|
{
|
|||
|
|
name: "default port",
|
|||
|
|
port: 9042,
|
|||
|
|
expected: 9042,
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
name: "custom port",
|
|||
|
|
port: 9043,
|
|||
|
|
expected: 9043,
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
name: "zero port",
|
|||
|
|
port: 0,
|
|||
|
|
expected: 0,
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
name: "negative port",
|
|||
|
|
port: -1,
|
|||
|
|
expected: -1,
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
name: "high port number",
|
|||
|
|
port: 65535,
|
|||
|
|
expected: 65535,
|
|||
|
|
},
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
for _, tt := range tests {
|
|||
|
|
t.Run(tt.name, func(t *testing.T) {
|
|||
|
|
cfg := defaultConfig()
|
|||
|
|
opt := WithPort(tt.port)
|
|||
|
|
opt(cfg)
|
|||
|
|
assert.Equal(t, tt.expected, cfg.Port)
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func TestWithKeyspace(t *testing.T) {
|
|||
|
|
tests := []struct {
|
|||
|
|
name string
|
|||
|
|
keyspace string
|
|||
|
|
expected string
|
|||
|
|
}{
|
|||
|
|
{
|
|||
|
|
name: "valid keyspace",
|
|||
|
|
keyspace: "my_keyspace",
|
|||
|
|
expected: "my_keyspace",
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
name: "empty keyspace",
|
|||
|
|
keyspace: "",
|
|||
|
|
expected: "",
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
name: "keyspace with underscore",
|
|||
|
|
keyspace: "test_keyspace_1",
|
|||
|
|
expected: "test_keyspace_1",
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
name: "keyspace with numbers",
|
|||
|
|
keyspace: "keyspace123",
|
|||
|
|
expected: "keyspace123",
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
name: "long keyspace name",
|
|||
|
|
keyspace: "very_long_keyspace_name_that_might_exist",
|
|||
|
|
expected: "very_long_keyspace_name_that_might_exist",
|
|||
|
|
},
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
for _, tt := range tests {
|
|||
|
|
t.Run(tt.name, func(t *testing.T) {
|
|||
|
|
cfg := defaultConfig()
|
|||
|
|
opt := WithKeyspace(tt.keyspace)
|
|||
|
|
opt(cfg)
|
|||
|
|
assert.Equal(t, tt.expected, cfg.Keyspace)
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func TestWithAuth(t *testing.T) {
|
|||
|
|
tests := []struct {
|
|||
|
|
name string
|
|||
|
|
username string
|
|||
|
|
password string
|
|||
|
|
expectedUser string
|
|||
|
|
expectedPass string
|
|||
|
|
expectedUseAuth bool
|
|||
|
|
}{
|
|||
|
|
{
|
|||
|
|
name: "valid credentials",
|
|||
|
|
username: "admin",
|
|||
|
|
password: "password123",
|
|||
|
|
expectedUser: "admin",
|
|||
|
|
expectedPass: "password123",
|
|||
|
|
expectedUseAuth: true,
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
name: "empty username",
|
|||
|
|
username: "",
|
|||
|
|
password: "password",
|
|||
|
|
expectedUser: "",
|
|||
|
|
expectedPass: "password",
|
|||
|
|
expectedUseAuth: true,
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
name: "empty password",
|
|||
|
|
username: "admin",
|
|||
|
|
password: "",
|
|||
|
|
expectedUser: "admin",
|
|||
|
|
expectedPass: "",
|
|||
|
|
expectedUseAuth: true,
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
name: "both empty",
|
|||
|
|
username: "",
|
|||
|
|
password: "",
|
|||
|
|
expectedUser: "",
|
|||
|
|
expectedPass: "",
|
|||
|
|
expectedUseAuth: true,
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
name: "special characters in password",
|
|||
|
|
username: "user",
|
|||
|
|
password: "p@ssw0rd!#$%",
|
|||
|
|
expectedUser: "user",
|
|||
|
|
expectedPass: "p@ssw0rd!#$%",
|
|||
|
|
expectedUseAuth: true,
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
name: "long username and password",
|
|||
|
|
username: "very_long_username_that_might_exist",
|
|||
|
|
password: "very_long_password_that_might_exist",
|
|||
|
|
expectedUser: "very_long_username_that_might_exist",
|
|||
|
|
expectedPass: "very_long_password_that_might_exist",
|
|||
|
|
expectedUseAuth: true,
|
|||
|
|
},
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
for _, tt := range tests {
|
|||
|
|
t.Run(tt.name, func(t *testing.T) {
|
|||
|
|
cfg := defaultConfig()
|
|||
|
|
opt := WithAuth(tt.username, tt.password)
|
|||
|
|
opt(cfg)
|
|||
|
|
assert.Equal(t, tt.expectedUser, cfg.Username)
|
|||
|
|
assert.Equal(t, tt.expectedPass, cfg.Password)
|
|||
|
|
assert.Equal(t, tt.expectedUseAuth, cfg.UseAuth)
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func TestWithConsistency(t *testing.T) {
|
|||
|
|
tests := []struct {
|
|||
|
|
name string
|
|||
|
|
consistency gocql.Consistency
|
|||
|
|
expected gocql.Consistency
|
|||
|
|
}{
|
|||
|
|
{
|
|||
|
|
name: "Quorum consistency",
|
|||
|
|
consistency: gocql.Quorum,
|
|||
|
|
expected: gocql.Quorum,
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
name: "One consistency",
|
|||
|
|
consistency: gocql.One,
|
|||
|
|
expected: gocql.One,
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
name: "All consistency",
|
|||
|
|
consistency: gocql.All,
|
|||
|
|
expected: gocql.All,
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
name: "Any consistency",
|
|||
|
|
consistency: gocql.Any,
|
|||
|
|
expected: gocql.Any,
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
name: "LocalQuorum consistency",
|
|||
|
|
consistency: gocql.LocalQuorum,
|
|||
|
|
expected: gocql.LocalQuorum,
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
name: "EachQuorum consistency",
|
|||
|
|
consistency: gocql.EachQuorum,
|
|||
|
|
expected: gocql.EachQuorum,
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
name: "LocalOne consistency",
|
|||
|
|
consistency: gocql.LocalOne,
|
|||
|
|
expected: gocql.LocalOne,
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
name: "Two consistency",
|
|||
|
|
consistency: gocql.Two,
|
|||
|
|
expected: gocql.Two,
|
|||
|
|
},
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
for _, tt := range tests {
|
|||
|
|
t.Run(tt.name, func(t *testing.T) {
|
|||
|
|
cfg := defaultConfig()
|
|||
|
|
opt := WithConsistency(tt.consistency)
|
|||
|
|
opt(cfg)
|
|||
|
|
assert.Equal(t, tt.expected, cfg.Consistency)
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func TestWithConnectTimeoutSec(t *testing.T) {
|
|||
|
|
tests := []struct {
|
|||
|
|
name string
|
|||
|
|
timeout int
|
|||
|
|
expected int
|
|||
|
|
}{
|
|||
|
|
{
|
|||
|
|
name: "valid timeout",
|
|||
|
|
timeout: 10,
|
|||
|
|
expected: 10,
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
name: "zero timeout should use default",
|
|||
|
|
timeout: 0,
|
|||
|
|
expected: defaultTimeoutSec,
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
name: "negative timeout should use default",
|
|||
|
|
timeout: -1,
|
|||
|
|
expected: defaultTimeoutSec,
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
name: "large timeout",
|
|||
|
|
timeout: 300,
|
|||
|
|
expected: 300,
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
name: "small timeout",
|
|||
|
|
timeout: 1,
|
|||
|
|
expected: 1,
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
name: "very large timeout",
|
|||
|
|
timeout: 3600,
|
|||
|
|
expected: 3600,
|
|||
|
|
},
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
for _, tt := range tests {
|
|||
|
|
t.Run(tt.name, func(t *testing.T) {
|
|||
|
|
cfg := defaultConfig()
|
|||
|
|
opt := WithConnectTimeoutSec(tt.timeout)
|
|||
|
|
opt(cfg)
|
|||
|
|
assert.Equal(t, tt.expected, cfg.ConnectTimeoutSec)
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func TestWithNumConns(t *testing.T) {
|
|||
|
|
tests := []struct {
|
|||
|
|
name string
|
|||
|
|
numConns int
|
|||
|
|
expected int
|
|||
|
|
}{
|
|||
|
|
{
|
|||
|
|
name: "valid numConns",
|
|||
|
|
numConns: 10,
|
|||
|
|
expected: 10,
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
name: "zero numConns should use default",
|
|||
|
|
numConns: 0,
|
|||
|
|
expected: defaultNumConns,
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
name: "negative numConns should use default",
|
|||
|
|
numConns: -1,
|
|||
|
|
expected: defaultNumConns,
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
name: "large numConns",
|
|||
|
|
numConns: 100,
|
|||
|
|
expected: 100,
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
name: "small numConns",
|
|||
|
|
numConns: 1,
|
|||
|
|
expected: 1,
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
name: "very large numConns",
|
|||
|
|
numConns: 1000,
|
|||
|
|
expected: 1000,
|
|||
|
|
},
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
for _, tt := range tests {
|
|||
|
|
t.Run(tt.name, func(t *testing.T) {
|
|||
|
|
cfg := defaultConfig()
|
|||
|
|
opt := WithNumConns(tt.numConns)
|
|||
|
|
opt(cfg)
|
|||
|
|
assert.Equal(t, tt.expected, cfg.NumConns)
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func TestWithMaxRetries(t *testing.T) {
|
|||
|
|
tests := []struct {
|
|||
|
|
name string
|
|||
|
|
maxRetries int
|
|||
|
|
expected int
|
|||
|
|
}{
|
|||
|
|
{
|
|||
|
|
name: "valid maxRetries",
|
|||
|
|
maxRetries: 3,
|
|||
|
|
expected: 3,
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
name: "zero maxRetries should use default",
|
|||
|
|
maxRetries: 0,
|
|||
|
|
expected: defaultMaxRetries,
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
name: "negative maxRetries should use default",
|
|||
|
|
maxRetries: -1,
|
|||
|
|
expected: defaultMaxRetries,
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
name: "large maxRetries",
|
|||
|
|
maxRetries: 10,
|
|||
|
|
expected: 10,
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
name: "small maxRetries",
|
|||
|
|
maxRetries: 1,
|
|||
|
|
expected: 1,
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
name: "very large maxRetries",
|
|||
|
|
maxRetries: 100,
|
|||
|
|
expected: 100,
|
|||
|
|
},
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
for _, tt := range tests {
|
|||
|
|
t.Run(tt.name, func(t *testing.T) {
|
|||
|
|
cfg := defaultConfig()
|
|||
|
|
opt := WithMaxRetries(tt.maxRetries)
|
|||
|
|
opt(cfg)
|
|||
|
|
assert.Equal(t, tt.expected, cfg.MaxRetries)
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func TestWithRetryMinInterval(t *testing.T) {
|
|||
|
|
tests := []struct {
|
|||
|
|
name string
|
|||
|
|
duration time.Duration
|
|||
|
|
expected time.Duration
|
|||
|
|
}{
|
|||
|
|
{
|
|||
|
|
name: "valid duration",
|
|||
|
|
duration: 1 * time.Second,
|
|||
|
|
expected: 1 * time.Second,
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
name: "zero duration should use default",
|
|||
|
|
duration: 0,
|
|||
|
|
expected: defaultRetryMinInterval,
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
name: "negative duration should use default",
|
|||
|
|
duration: -1 * time.Second,
|
|||
|
|
expected: defaultRetryMinInterval,
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
name: "milliseconds",
|
|||
|
|
duration: 500 * time.Millisecond,
|
|||
|
|
expected: 500 * time.Millisecond,
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
name: "minutes",
|
|||
|
|
duration: 5 * time.Minute,
|
|||
|
|
expected: 5 * time.Minute,
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
name: "hours",
|
|||
|
|
duration: 1 * time.Hour,
|
|||
|
|
expected: 1 * time.Hour,
|
|||
|
|
},
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
for _, tt := range tests {
|
|||
|
|
t.Run(tt.name, func(t *testing.T) {
|
|||
|
|
cfg := defaultConfig()
|
|||
|
|
opt := WithRetryMinInterval(tt.duration)
|
|||
|
|
opt(cfg)
|
|||
|
|
assert.Equal(t, tt.expected, cfg.RetryMinInterval)
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func TestWithRetryMaxInterval(t *testing.T) {
|
|||
|
|
tests := []struct {
|
|||
|
|
name string
|
|||
|
|
duration time.Duration
|
|||
|
|
expected time.Duration
|
|||
|
|
}{
|
|||
|
|
{
|
|||
|
|
name: "valid duration",
|
|||
|
|
duration: 30 * time.Second,
|
|||
|
|
expected: 30 * time.Second,
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
name: "zero duration should use default",
|
|||
|
|
duration: 0,
|
|||
|
|
expected: defaultRetryMaxInterval,
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
name: "negative duration should use default",
|
|||
|
|
duration: -1 * time.Second,
|
|||
|
|
expected: defaultRetryMaxInterval,
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
name: "milliseconds",
|
|||
|
|
duration: 1000 * time.Millisecond,
|
|||
|
|
expected: 1000 * time.Millisecond,
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
name: "minutes",
|
|||
|
|
duration: 10 * time.Minute,
|
|||
|
|
expected: 10 * time.Minute,
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
name: "hours",
|
|||
|
|
duration: 2 * time.Hour,
|
|||
|
|
expected: 2 * time.Hour,
|
|||
|
|
},
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
for _, tt := range tests {
|
|||
|
|
t.Run(tt.name, func(t *testing.T) {
|
|||
|
|
cfg := defaultConfig()
|
|||
|
|
opt := WithRetryMaxInterval(tt.duration)
|
|||
|
|
opt(cfg)
|
|||
|
|
assert.Equal(t, tt.expected, cfg.RetryMaxInterval)
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func TestWithReconnectInitialInterval(t *testing.T) {
|
|||
|
|
tests := []struct {
|
|||
|
|
name string
|
|||
|
|
duration time.Duration
|
|||
|
|
expected time.Duration
|
|||
|
|
}{
|
|||
|
|
{
|
|||
|
|
name: "valid duration",
|
|||
|
|
duration: 1 * time.Second,
|
|||
|
|
expected: 1 * time.Second,
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
name: "zero duration should use default",
|
|||
|
|
duration: 0,
|
|||
|
|
expected: defaultReconnectInitialInterval,
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
name: "negative duration should use default",
|
|||
|
|
duration: -1 * time.Second,
|
|||
|
|
expected: defaultReconnectInitialInterval,
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
name: "milliseconds",
|
|||
|
|
duration: 500 * time.Millisecond,
|
|||
|
|
expected: 500 * time.Millisecond,
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
name: "minutes",
|
|||
|
|
duration: 2 * time.Minute,
|
|||
|
|
expected: 2 * time.Minute,
|
|||
|
|
},
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
for _, tt := range tests {
|
|||
|
|
t.Run(tt.name, func(t *testing.T) {
|
|||
|
|
cfg := defaultConfig()
|
|||
|
|
opt := WithReconnectInitialInterval(tt.duration)
|
|||
|
|
opt(cfg)
|
|||
|
|
assert.Equal(t, tt.expected, cfg.ReconnectInitialInterval)
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func TestWithReconnectMaxInterval(t *testing.T) {
|
|||
|
|
tests := []struct {
|
|||
|
|
name string
|
|||
|
|
duration time.Duration
|
|||
|
|
expected time.Duration
|
|||
|
|
}{
|
|||
|
|
{
|
|||
|
|
name: "valid duration",
|
|||
|
|
duration: 60 * time.Second,
|
|||
|
|
expected: 60 * time.Second,
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
name: "zero duration should use default",
|
|||
|
|
duration: 0,
|
|||
|
|
expected: defaultReconnectMaxInterval,
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
name: "negative duration should use default",
|
|||
|
|
duration: -1 * time.Second,
|
|||
|
|
expected: defaultReconnectMaxInterval,
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
name: "milliseconds",
|
|||
|
|
duration: 5000 * time.Millisecond,
|
|||
|
|
expected: 5000 * time.Millisecond,
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
name: "minutes",
|
|||
|
|
duration: 5 * time.Minute,
|
|||
|
|
expected: 5 * time.Minute,
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
name: "hours",
|
|||
|
|
duration: 1 * time.Hour,
|
|||
|
|
expected: 1 * time.Hour,
|
|||
|
|
},
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
for _, tt := range tests {
|
|||
|
|
t.Run(tt.name, func(t *testing.T) {
|
|||
|
|
cfg := defaultConfig()
|
|||
|
|
opt := WithReconnectMaxInterval(tt.duration)
|
|||
|
|
opt(cfg)
|
|||
|
|
assert.Equal(t, tt.expected, cfg.ReconnectMaxInterval)
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func TestWithCQLVersion(t *testing.T) {
|
|||
|
|
tests := []struct {
|
|||
|
|
name string
|
|||
|
|
version string
|
|||
|
|
expected string
|
|||
|
|
}{
|
|||
|
|
{
|
|||
|
|
name: "valid version",
|
|||
|
|
version: "3.0.0",
|
|||
|
|
expected: "3.0.0",
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
name: "empty version should use default",
|
|||
|
|
version: "",
|
|||
|
|
expected: defaultCqlVersion,
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
name: "version 3.1.0",
|
|||
|
|
version: "3.1.0",
|
|||
|
|
expected: "3.1.0",
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
name: "version 3.4.0",
|
|||
|
|
version: "3.4.0",
|
|||
|
|
expected: "3.4.0",
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
name: "version 4.0.0",
|
|||
|
|
version: "4.0.0",
|
|||
|
|
expected: "4.0.0",
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
name: "version with build",
|
|||
|
|
version: "3.0.0-beta",
|
|||
|
|
expected: "3.0.0-beta",
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
name: "version with snapshot",
|
|||
|
|
version: "3.0.0-SNAPSHOT",
|
|||
|
|
expected: "3.0.0-SNAPSHOT",
|
|||
|
|
},
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
for _, tt := range tests {
|
|||
|
|
t.Run(tt.name, func(t *testing.T) {
|
|||
|
|
cfg := defaultConfig()
|
|||
|
|
opt := WithCQLVersion(tt.version)
|
|||
|
|
opt(cfg)
|
|||
|
|
assert.Equal(t, tt.expected, cfg.CQLVersion)
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func TestOption_Combination(t *testing.T) {
|
|||
|
|
tests := []struct {
|
|||
|
|
name string
|
|||
|
|
opts []Option
|
|||
|
|
validate func(*testing.T, *config)
|
|||
|
|
}{
|
|||
|
|
{
|
|||
|
|
name: "all options",
|
|||
|
|
opts: []Option{
|
|||
|
|
WithHosts("localhost", "127.0.0.1"),
|
|||
|
|
WithPort(9042),
|
|||
|
|
WithKeyspace("test_keyspace"),
|
|||
|
|
WithAuth("user", "pass"),
|
|||
|
|
WithConsistency(gocql.Quorum),
|
|||
|
|
WithConnectTimeoutSec(10),
|
|||
|
|
WithNumConns(10),
|
|||
|
|
WithMaxRetries(3),
|
|||
|
|
WithRetryMinInterval(1 * time.Second),
|
|||
|
|
WithRetryMaxInterval(30 * time.Second),
|
|||
|
|
WithReconnectInitialInterval(1 * time.Second),
|
|||
|
|
WithReconnectMaxInterval(60 * time.Second),
|
|||
|
|
WithCQLVersion("3.0.0"),
|
|||
|
|
},
|
|||
|
|
validate: func(t *testing.T, c *config) {
|
|||
|
|
assert.Equal(t, []string{"localhost", "127.0.0.1"}, c.Hosts)
|
|||
|
|
assert.Equal(t, 9042, c.Port)
|
|||
|
|
assert.Equal(t, "test_keyspace", c.Keyspace)
|
|||
|
|
assert.Equal(t, "user", c.Username)
|
|||
|
|
assert.Equal(t, "pass", c.Password)
|
|||
|
|
assert.True(t, c.UseAuth)
|
|||
|
|
assert.Equal(t, gocql.Quorum, c.Consistency)
|
|||
|
|
assert.Equal(t, 10, c.ConnectTimeoutSec)
|
|||
|
|
assert.Equal(t, 10, c.NumConns)
|
|||
|
|
assert.Equal(t, 3, c.MaxRetries)
|
|||
|
|
assert.Equal(t, 1*time.Second, c.RetryMinInterval)
|
|||
|
|
assert.Equal(t, 30*time.Second, c.RetryMaxInterval)
|
|||
|
|
assert.Equal(t, 1*time.Second, c.ReconnectInitialInterval)
|
|||
|
|
assert.Equal(t, 60*time.Second, c.ReconnectMaxInterval)
|
|||
|
|
assert.Equal(t, "3.0.0", c.CQLVersion)
|
|||
|
|
},
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
name: "minimal options",
|
|||
|
|
opts: []Option{
|
|||
|
|
WithHosts("localhost"),
|
|||
|
|
},
|
|||
|
|
validate: func(t *testing.T, c *config) {
|
|||
|
|
assert.Equal(t, []string{"localhost"}, c.Hosts)
|
|||
|
|
// 其他應該使用預設值
|
|||
|
|
assert.Equal(t, defaultPort, c.Port)
|
|||
|
|
assert.Equal(t, defaultConsistency, c.Consistency)
|
|||
|
|
},
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
name: "options with zero values should use defaults",
|
|||
|
|
opts: []Option{
|
|||
|
|
WithHosts("localhost"),
|
|||
|
|
WithConnectTimeoutSec(0),
|
|||
|
|
WithNumConns(0),
|
|||
|
|
WithMaxRetries(0),
|
|||
|
|
WithRetryMinInterval(0),
|
|||
|
|
WithRetryMaxInterval(0),
|
|||
|
|
WithReconnectInitialInterval(0),
|
|||
|
|
WithReconnectMaxInterval(0),
|
|||
|
|
WithCQLVersion(""),
|
|||
|
|
},
|
|||
|
|
validate: func(t *testing.T, c *config) {
|
|||
|
|
assert.Equal(t, []string{"localhost"}, c.Hosts)
|
|||
|
|
assert.Equal(t, defaultTimeoutSec, c.ConnectTimeoutSec)
|
|||
|
|
assert.Equal(t, defaultNumConns, c.NumConns)
|
|||
|
|
assert.Equal(t, defaultMaxRetries, c.MaxRetries)
|
|||
|
|
assert.Equal(t, defaultRetryMinInterval, c.RetryMinInterval)
|
|||
|
|
assert.Equal(t, defaultRetryMaxInterval, c.RetryMaxInterval)
|
|||
|
|
assert.Equal(t, defaultReconnectInitialInterval, c.ReconnectInitialInterval)
|
|||
|
|
assert.Equal(t, defaultReconnectMaxInterval, c.ReconnectMaxInterval)
|
|||
|
|
assert.Equal(t, defaultCqlVersion, c.CQLVersion)
|
|||
|
|
},
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
name: "options with negative values should use defaults",
|
|||
|
|
opts: []Option{
|
|||
|
|
WithHosts("localhost"),
|
|||
|
|
WithConnectTimeoutSec(-1),
|
|||
|
|
WithNumConns(-1),
|
|||
|
|
WithMaxRetries(-1),
|
|||
|
|
WithRetryMinInterval(-1 * time.Second),
|
|||
|
|
WithRetryMaxInterval(-1 * time.Second),
|
|||
|
|
WithReconnectInitialInterval(-1 * time.Second),
|
|||
|
|
WithReconnectMaxInterval(-1 * time.Second),
|
|||
|
|
},
|
|||
|
|
validate: func(t *testing.T, c *config) {
|
|||
|
|
assert.Equal(t, []string{"localhost"}, c.Hosts)
|
|||
|
|
assert.Equal(t, defaultTimeoutSec, c.ConnectTimeoutSec)
|
|||
|
|
assert.Equal(t, defaultNumConns, c.NumConns)
|
|||
|
|
assert.Equal(t, defaultMaxRetries, c.MaxRetries)
|
|||
|
|
assert.Equal(t, defaultRetryMinInterval, c.RetryMinInterval)
|
|||
|
|
assert.Equal(t, defaultRetryMaxInterval, c.RetryMaxInterval)
|
|||
|
|
assert.Equal(t, defaultReconnectInitialInterval, c.ReconnectInitialInterval)
|
|||
|
|
assert.Equal(t, defaultReconnectMaxInterval, c.ReconnectMaxInterval)
|
|||
|
|
},
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
name: "multiple options applied in sequence",
|
|||
|
|
opts: []Option{
|
|||
|
|
WithHosts("host1"),
|
|||
|
|
WithHosts("host2", "host3"), // 應該覆蓋
|
|||
|
|
WithPort(9042),
|
|||
|
|
WithPort(9043), // 應該覆蓋
|
|||
|
|
},
|
|||
|
|
validate: func(t *testing.T, c *config) {
|
|||
|
|
assert.Equal(t, []string{"host2", "host3"}, c.Hosts)
|
|||
|
|
assert.Equal(t, 9043, c.Port)
|
|||
|
|
},
|
|||
|
|
},
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
for _, tt := range tests {
|
|||
|
|
t.Run(tt.name, func(t *testing.T) {
|
|||
|
|
cfg := defaultConfig()
|
|||
|
|
for _, opt := range tt.opts {
|
|||
|
|
opt(cfg)
|
|||
|
|
}
|
|||
|
|
tt.validate(t, cfg)
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func TestOption_Type(t *testing.T) {
|
|||
|
|
t.Run("all options should return Option type", func(t *testing.T) {
|
|||
|
|
var opt Option
|
|||
|
|
|
|||
|
|
opt = WithHosts("localhost")
|
|||
|
|
assert.NotNil(t, opt)
|
|||
|
|
|
|||
|
|
opt = WithPort(9042)
|
|||
|
|
assert.NotNil(t, opt)
|
|||
|
|
|
|||
|
|
opt = WithKeyspace("test")
|
|||
|
|
assert.NotNil(t, opt)
|
|||
|
|
|
|||
|
|
opt = WithAuth("user", "pass")
|
|||
|
|
assert.NotNil(t, opt)
|
|||
|
|
|
|||
|
|
opt = WithConsistency(gocql.Quorum)
|
|||
|
|
assert.NotNil(t, opt)
|
|||
|
|
|
|||
|
|
opt = WithConnectTimeoutSec(10)
|
|||
|
|
assert.NotNil(t, opt)
|
|||
|
|
|
|||
|
|
opt = WithNumConns(10)
|
|||
|
|
assert.NotNil(t, opt)
|
|||
|
|
|
|||
|
|
opt = WithMaxRetries(3)
|
|||
|
|
assert.NotNil(t, opt)
|
|||
|
|
|
|||
|
|
opt = WithRetryMinInterval(1 * time.Second)
|
|||
|
|
assert.NotNil(t, opt)
|
|||
|
|
|
|||
|
|
opt = WithRetryMaxInterval(30 * time.Second)
|
|||
|
|
assert.NotNil(t, opt)
|
|||
|
|
|
|||
|
|
opt = WithReconnectInitialInterval(1 * time.Second)
|
|||
|
|
assert.NotNil(t, opt)
|
|||
|
|
|
|||
|
|
opt = WithReconnectMaxInterval(60 * time.Second)
|
|||
|
|
assert.NotNil(t, opt)
|
|||
|
|
|
|||
|
|
opt = WithCQLVersion("3.0.0")
|
|||
|
|
assert.NotNil(t, opt)
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func TestOption_EdgeCases(t *testing.T) {
|
|||
|
|
t.Run("empty option slice", func(t *testing.T) {
|
|||
|
|
cfg := defaultConfig()
|
|||
|
|
opts := []Option{}
|
|||
|
|
for _, opt := range opts {
|
|||
|
|
opt(cfg)
|
|||
|
|
}
|
|||
|
|
// 應該保持預設值
|
|||
|
|
assert.Equal(t, defaultPort, cfg.Port)
|
|||
|
|
assert.Equal(t, defaultConsistency, cfg.Consistency)
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
t.Run("zero value option function", func(t *testing.T) {
|
|||
|
|
cfg := defaultConfig()
|
|||
|
|
var opt Option
|
|||
|
|
// 零值的 Option 是 nil,調用會 panic,所以不應該調用
|
|||
|
|
// 這裡只是驗證零值不會影響配置
|
|||
|
|
_ = opt
|
|||
|
|
// 應該保持預設值
|
|||
|
|
assert.Equal(t, defaultPort, cfg.Port)
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
t.Run("very long strings", func(t *testing.T) {
|
|||
|
|
cfg := defaultConfig()
|
|||
|
|
longString := string(make([]byte, 10000))
|
|||
|
|
WithKeyspace(longString)(cfg)
|
|||
|
|
assert.Equal(t, longString, cfg.Keyspace)
|
|||
|
|
|
|||
|
|
WithAuth(longString, longString)(cfg)
|
|||
|
|
assert.Equal(t, longString, cfg.Username)
|
|||
|
|
assert.Equal(t, longString, cfg.Password)
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
t.Run("special characters in strings", func(t *testing.T) {
|
|||
|
|
cfg := defaultConfig()
|
|||
|
|
specialChars := "!@#$%^&*()_+-=[]{}|;:,.<>?"
|
|||
|
|
WithKeyspace(specialChars)(cfg)
|
|||
|
|
assert.Equal(t, specialChars, cfg.Keyspace)
|
|||
|
|
|
|||
|
|
WithAuth(specialChars, specialChars)(cfg)
|
|||
|
|
assert.Equal(t, specialChars, cfg.Username)
|
|||
|
|
assert.Equal(t, specialChars, cfg.Password)
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func TestOption_RealWorldScenarios(t *testing.T) {
|
|||
|
|
tests := []struct {
|
|||
|
|
name string
|
|||
|
|
scenario string
|
|||
|
|
opts []Option
|
|||
|
|
validate func(*testing.T, *config)
|
|||
|
|
}{
|
|||
|
|
{
|
|||
|
|
name: "production-like configuration",
|
|||
|
|
scenario: "typical production setup",
|
|||
|
|
opts: []Option{
|
|||
|
|
WithHosts("cassandra1.example.com", "cassandra2.example.com", "cassandra3.example.com"),
|
|||
|
|
WithPort(9042),
|
|||
|
|
WithKeyspace("production_keyspace"),
|
|||
|
|
WithAuth("prod_user", "secure_password"),
|
|||
|
|
WithConsistency(gocql.Quorum),
|
|||
|
|
WithConnectTimeoutSec(30),
|
|||
|
|
WithNumConns(50),
|
|||
|
|
WithMaxRetries(5),
|
|||
|
|
},
|
|||
|
|
validate: func(t *testing.T, c *config) {
|
|||
|
|
assert.Len(t, c.Hosts, 3)
|
|||
|
|
assert.Equal(t, 9042, c.Port)
|
|||
|
|
assert.Equal(t, "production_keyspace", c.Keyspace)
|
|||
|
|
assert.True(t, c.UseAuth)
|
|||
|
|
assert.Equal(t, gocql.Quorum, c.Consistency)
|
|||
|
|
assert.Equal(t, 30, c.ConnectTimeoutSec)
|
|||
|
|
assert.Equal(t, 50, c.NumConns)
|
|||
|
|
assert.Equal(t, 5, c.MaxRetries)
|
|||
|
|
},
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
name: "development configuration",
|
|||
|
|
scenario: "local development setup",
|
|||
|
|
opts: []Option{
|
|||
|
|
WithHosts("localhost"),
|
|||
|
|
WithKeyspace("dev_keyspace"),
|
|||
|
|
},
|
|||
|
|
validate: func(t *testing.T, c *config) {
|
|||
|
|
assert.Equal(t, []string{"localhost"}, c.Hosts)
|
|||
|
|
assert.Equal(t, "dev_keyspace", c.Keyspace)
|
|||
|
|
assert.False(t, c.UseAuth)
|
|||
|
|
},
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
name: "high availability configuration",
|
|||
|
|
scenario: "HA setup with multiple hosts",
|
|||
|
|
opts: []Option{
|
|||
|
|
WithHosts("node1", "node2", "node3", "node4", "node5"),
|
|||
|
|
WithConsistency(gocql.All),
|
|||
|
|
WithMaxRetries(10),
|
|||
|
|
},
|
|||
|
|
validate: func(t *testing.T, c *config) {
|
|||
|
|
assert.Len(t, c.Hosts, 5)
|
|||
|
|
assert.Equal(t, gocql.All, c.Consistency)
|
|||
|
|
assert.Equal(t, 10, c.MaxRetries)
|
|||
|
|
},
|
|||
|
|
},
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
for _, tt := range tests {
|
|||
|
|
t.Run(tt.name, func(t *testing.T) {
|
|||
|
|
cfg := defaultConfig()
|
|||
|
|
for _, opt := range tt.opts {
|
|||
|
|
opt(cfg)
|
|||
|
|
}
|
|||
|
|
tt.validate(t, cfg)
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|