backend/pkg/library/cassandra/db_test.go

546 lines
13 KiB
Go

package cassandra
import (
"errors"
"testing"
"time"
"github.com/gocql/gocql"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestIsSAISupported(t *testing.T) {
tests := []struct {
name string
version string
expected bool
}{
{
name: "version 5.0.0 should support SAI",
version: "5.0.0",
expected: true,
},
{
name: "version 5.1.0 should support SAI",
version: "5.1.0",
expected: true,
},
{
name: "version 6.0.0 should support SAI",
version: "6.0.0",
expected: true,
},
{
name: "version 4.1.0 should support SAI",
version: "4.1.0",
expected: true,
},
{
name: "version 4.2.0 should support SAI",
version: "4.2.0",
expected: true,
},
{
name: "version 4.0.9 should support SAI",
version: "4.0.9",
expected: true,
},
{
name: "version 4.0.10 should support SAI",
version: "4.0.10",
expected: true,
},
{
name: "version 4.0.8 should not support SAI",
version: "4.0.8",
expected: false,
},
{
name: "version 4.0.0 should not support SAI",
version: "4.0.0",
expected: false,
},
{
name: "version 3.11.0 should not support SAI",
version: "3.11.0",
expected: false,
},
{
name: "invalid version format should not support SAI",
version: "invalid",
expected: false,
},
{
name: "empty version should not support SAI",
version: "",
expected: false,
},
{
name: "version with only major should not support SAI",
version: "5",
expected: false,
},
{
name: "version 4.0.9 with extra parts should support SAI",
version: "4.0.9.1",
expected: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := isSAISupported(tt.version)
assert.Equal(t, tt.expected, result, "version %s should have SAI support = %v", tt.version, tt.expected)
})
}
}
func TestNew_Validation(t *testing.T) {
tests := []struct {
name string
opts []Option
wantErr bool
errMsg string
}{
{
name: "no hosts should return error",
opts: []Option{},
wantErr: true,
errMsg: "at least one host is required",
},
{
name: "empty hosts should return error",
opts: []Option{WithHosts()},
wantErr: true,
errMsg: "at least one host is required",
},
{
name: "valid hosts should not return error on validation",
opts: []Option{
WithHosts("localhost"),
},
wantErr: false,
},
{
name: "multiple hosts should not return error on validation",
opts: []Option{
WithHosts("localhost", "127.0.0.1"),
},
wantErr: false,
},
{
name: "with keyspace should not return error on validation",
opts: []Option{
WithHosts("localhost"),
WithKeyspace("test_keyspace"),
},
wantErr: false,
},
{
name: "with port should not return error on validation",
opts: []Option{
WithHosts("localhost"),
WithPort(9042),
},
wantErr: false,
},
{
name: "with auth should not return error on validation",
opts: []Option{
WithHosts("localhost"),
WithAuth("user", "pass"),
},
wantErr: false,
},
{
name: "with all options should not return error on validation",
opts: []Option{
WithHosts("localhost"),
WithKeyspace("test_keyspace"),
WithPort(9042),
WithAuth("user", "pass"),
WithConsistency(gocql.Quorum),
WithConnectTimeoutSec(10),
WithNumConns(10),
WithMaxRetries(3),
},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
db, err := New(tt.opts...)
if tt.wantErr {
require.Error(t, err)
if tt.errMsg != "" {
assert.Contains(t, err.Error(), tt.errMsg)
}
assert.Nil(t, db)
} else {
// 注意:這裡可能會因為無法連接到真實的 Cassandra 而失敗
// 但至少驗證了配置驗證邏輯
if err != nil {
// 如果錯誤不是驗證錯誤,而是連接錯誤,這是可以接受的
assert.NotContains(t, err.Error(), "at least one host is required")
}
}
})
}
}
func TestDB_GetDefaultKeyspace(t *testing.T) {
tests := []struct {
name string
keyspace string
expectedResult string
}{
{
name: "empty keyspace should return empty string",
keyspace: "",
expectedResult: "",
},
{
name: "non-empty keyspace should return keyspace",
keyspace: "test_keyspace",
expectedResult: "test_keyspace",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// 注意:這需要一個有效的 DB 實例
// 在實際測試中,可能需要 mock 或使用 testcontainers
// 這裡只是展示測試結構
_ = tt
})
}
}
func TestDB_Version(t *testing.T) {
tests := []struct {
name string
version string
expected string
}{
{
name: "version 5.0.0",
version: "5.0.0",
expected: "5.0.0",
},
{
name: "version 4.0.9",
version: "4.0.9",
expected: "4.0.9",
},
{
name: "version 3.11.0",
version: "3.11.0",
expected: "3.11.0",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// 注意:這需要一個有效的 DB 實例
// 在實際測試中,可能需要 mock 或使用 testcontainers
_ = tt
})
}
}
func TestDB_SaiSupported(t *testing.T) {
tests := []struct {
name string
version string
expected bool
}{
{
name: "version 5.0.0 should support SAI",
version: "5.0.0",
expected: true,
},
{
name: "version 4.0.9 should support SAI",
version: "4.0.9",
expected: true,
},
{
name: "version 4.0.8 should not support SAI",
version: "4.0.8",
expected: false,
},
{
name: "version 3.11.0 should not support SAI",
version: "3.11.0",
expected: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// 注意:這需要一個有效的 DB 實例
// 在實際測試中,可能需要 mock 或使用 testcontainers
// 這裡只是展示測試結構
_ = tt
})
}
}
func TestDB_GetSession(t *testing.T) {
t.Run("GetSession should return non-nil session", func(t *testing.T) {
// 注意:這需要一個有效的 DB 實例
// 在實際測試中,可能需要 mock 或使用 testcontainers
})
}
func TestDB_Close(t *testing.T) {
t.Run("Close should not panic", func(t *testing.T) {
// 注意:這需要一個有效的 DB 實例
// 在實際測試中,可能需要 mock 或使用 testcontainers
})
}
func TestDB_getVersion(t *testing.T) {
tests := []struct {
name string
version string
queryErr error
wantErr bool
expectedVer string
}{
{
name: "successful version query",
version: "5.0.0",
queryErr: nil,
wantErr: false,
expectedVer: "5.0.0",
},
{
name: "query error should return error",
version: "",
queryErr: errors.New("connection failed"),
wantErr: true,
expectedVer: "",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// 注意:這需要 mock session
// 在實際測試中,需要使用 mock 或 testcontainers
_ = tt
})
}
}
func TestDB_withContextAndTimestamp(t *testing.T) {
t.Run("withContextAndTimestamp should add context and timestamp", func(t *testing.T) {
// 注意:這需要 mock query
// 在實際測試中,需要使用 mock
})
}
func TestDefaultConfig(t *testing.T) {
t.Run("defaultConfig should return valid config", 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)
})
}
func TestOptionFunctions(t *testing.T) {
tests := []struct {
name string
opt Option
validateConfig func(*testing.T, *config)
}{
{
name: "WithHosts should set hosts",
opt: WithHosts("host1", "host2"),
validateConfig: func(t *testing.T, c *config) {
assert.Equal(t, []string{"host1", "host2"}, c.Hosts)
},
},
{
name: "WithPort should set port",
opt: WithPort(9999),
validateConfig: func(t *testing.T, c *config) {
assert.Equal(t, 9999, c.Port)
},
},
{
name: "WithKeyspace should set keyspace",
opt: WithKeyspace("test_keyspace"),
validateConfig: func(t *testing.T, c *config) {
assert.Equal(t, "test_keyspace", c.Keyspace)
},
},
{
name: "WithAuth should set auth and enable UseAuth",
opt: WithAuth("user", "pass"),
validateConfig: func(t *testing.T, c *config) {
assert.Equal(t, "user", c.Username)
assert.Equal(t, "pass", c.Password)
assert.True(t, c.UseAuth)
},
},
{
name: "WithConsistency should set consistency",
opt: WithConsistency(gocql.One),
validateConfig: func(t *testing.T, c *config) {
assert.Equal(t, gocql.One, c.Consistency)
},
},
{
name: "WithConnectTimeoutSec should set timeout",
opt: WithConnectTimeoutSec(20),
validateConfig: func(t *testing.T, c *config) {
assert.Equal(t, 20, c.ConnectTimeoutSec)
},
},
{
name: "WithConnectTimeoutSec with zero should use default",
opt: WithConnectTimeoutSec(0),
validateConfig: func(t *testing.T, c *config) {
assert.Equal(t, defaultTimeoutSec, c.ConnectTimeoutSec)
},
},
{
name: "WithNumConns should set numConns",
opt: WithNumConns(20),
validateConfig: func(t *testing.T, c *config) {
assert.Equal(t, 20, c.NumConns)
},
},
{
name: "WithNumConns with zero should use default",
opt: WithNumConns(0),
validateConfig: func(t *testing.T, c *config) {
assert.Equal(t, defaultNumConns, c.NumConns)
},
},
{
name: "WithMaxRetries should set maxRetries",
opt: WithMaxRetries(5),
validateConfig: func(t *testing.T, c *config) {
assert.Equal(t, 5, c.MaxRetries)
},
},
{
name: "WithMaxRetries with zero should use default",
opt: WithMaxRetries(0),
validateConfig: func(t *testing.T, c *config) {
assert.Equal(t, defaultMaxRetries, c.MaxRetries)
},
},
{
name: "WithRetryMinInterval should set retryMinInterval",
opt: WithRetryMinInterval(2 * time.Second),
validateConfig: func(t *testing.T, c *config) {
assert.Equal(t, 2*time.Second, c.RetryMinInterval)
},
},
{
name: "WithRetryMinInterval with zero should use default",
opt: WithRetryMinInterval(0),
validateConfig: func(t *testing.T, c *config) {
assert.Equal(t, defaultRetryMinInterval, c.RetryMinInterval)
},
},
{
name: "WithRetryMaxInterval should set retryMaxInterval",
opt: WithRetryMaxInterval(60 * time.Second),
validateConfig: func(t *testing.T, c *config) {
assert.Equal(t, 60*time.Second, c.RetryMaxInterval)
},
},
{
name: "WithRetryMaxInterval with zero should use default",
opt: WithRetryMaxInterval(0),
validateConfig: func(t *testing.T, c *config) {
assert.Equal(t, defaultRetryMaxInterval, c.RetryMaxInterval)
},
},
{
name: "WithReconnectInitialInterval should set reconnectInitialInterval",
opt: WithReconnectInitialInterval(2 * time.Second),
validateConfig: func(t *testing.T, c *config) {
assert.Equal(t, 2*time.Second, c.ReconnectInitialInterval)
},
},
{
name: "WithReconnectInitialInterval with zero should use default",
opt: WithReconnectInitialInterval(0),
validateConfig: func(t *testing.T, c *config) {
assert.Equal(t, defaultReconnectInitialInterval, c.ReconnectInitialInterval)
},
},
{
name: "WithReconnectMaxInterval should set reconnectMaxInterval",
opt: WithReconnectMaxInterval(120 * time.Second),
validateConfig: func(t *testing.T, c *config) {
assert.Equal(t, 120*time.Second, c.ReconnectMaxInterval)
},
},
{
name: "WithReconnectMaxInterval with zero should use default",
opt: WithReconnectMaxInterval(0),
validateConfig: func(t *testing.T, c *config) {
assert.Equal(t, defaultReconnectMaxInterval, c.ReconnectMaxInterval)
},
},
{
name: "WithCQLVersion should set CQLVersion",
opt: WithCQLVersion("3.1.0"),
validateConfig: func(t *testing.T, c *config) {
assert.Equal(t, "3.1.0", c.CQLVersion)
},
},
{
name: "WithCQLVersion with empty should use default",
opt: WithCQLVersion(""),
validateConfig: func(t *testing.T, c *config) {
assert.Equal(t, defaultCqlVersion, c.CQLVersion)
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
cfg := defaultConfig()
tt.opt(cfg)
tt.validateConfig(t, cfg)
})
}
}
func TestMultipleOptions(t *testing.T) {
t.Run("multiple options should be applied correctly", func(t *testing.T) {
cfg := defaultConfig()
WithHosts("host1", "host2")(cfg)
WithPort(9999)(cfg)
WithKeyspace("test")(cfg)
WithAuth("user", "pass")(cfg)
assert.Equal(t, []string{"host1", "host2"}, cfg.Hosts)
assert.Equal(t, 9999, cfg.Port)
assert.Equal(t, "test", cfg.Keyspace)
assert.Equal(t, "user", cfg.Username)
assert.Equal(t, "pass", cfg.Password)
assert.True(t, cfg.UseAuth)
})
}