548 lines
12 KiB
Go
548 lines
12 KiB
Go
package cassandra
|
||
|
||
import (
|
||
"reflect"
|
||
"testing"
|
||
|
||
"github.com/stretchr/testify/assert"
|
||
"github.com/stretchr/testify/require"
|
||
)
|
||
|
||
func TestContains(t *testing.T) {
|
||
tests := []struct {
|
||
name string
|
||
list []string
|
||
target string
|
||
want bool
|
||
}{
|
||
{
|
||
name: "target exists in list",
|
||
list: []string{"a", "b", "c"},
|
||
target: "b",
|
||
want: true,
|
||
},
|
||
{
|
||
name: "target at beginning",
|
||
list: []string{"a", "b", "c"},
|
||
target: "a",
|
||
want: true,
|
||
},
|
||
{
|
||
name: "target at end",
|
||
list: []string{"a", "b", "c"},
|
||
target: "c",
|
||
want: true,
|
||
},
|
||
{
|
||
name: "target not in list",
|
||
list: []string{"a", "b", "c"},
|
||
target: "d",
|
||
want: false,
|
||
},
|
||
{
|
||
name: "empty list",
|
||
list: []string{},
|
||
target: "a",
|
||
want: false,
|
||
},
|
||
{
|
||
name: "empty target",
|
||
list: []string{"a", "b", "c"},
|
||
target: "",
|
||
want: false,
|
||
},
|
||
{
|
||
name: "target in single element list",
|
||
list: []string{"a"},
|
||
target: "a",
|
||
want: true,
|
||
},
|
||
{
|
||
name: "case sensitive",
|
||
list: []string{"A", "B", "C"},
|
||
target: "a",
|
||
want: false,
|
||
},
|
||
{
|
||
name: "duplicate values",
|
||
list: []string{"a", "b", "a", "c"},
|
||
target: "a",
|
||
want: true,
|
||
},
|
||
{
|
||
name: "long list",
|
||
list: []string{"a", "b", "c", "d", "e", "f", "g", "h", "i", "j"},
|
||
target: "j",
|
||
want: true,
|
||
},
|
||
}
|
||
|
||
for _, tt := range tests {
|
||
t.Run(tt.name, func(t *testing.T) {
|
||
result := contains(tt.list, tt.target)
|
||
assert.Equal(t, tt.want, result)
|
||
})
|
||
}
|
||
}
|
||
|
||
func TestIsZero(t *testing.T) {
|
||
tests := []struct {
|
||
name string
|
||
value any
|
||
expected bool
|
||
skip bool
|
||
}{
|
||
{
|
||
name: "nil pointer",
|
||
value: (*string)(nil),
|
||
expected: true,
|
||
skip: false,
|
||
},
|
||
{
|
||
name: "non-nil pointer",
|
||
value: stringPtr("test"),
|
||
expected: false,
|
||
skip: false,
|
||
},
|
||
{
|
||
name: "nil slice",
|
||
value: []string(nil),
|
||
expected: true,
|
||
skip: false,
|
||
},
|
||
{
|
||
name: "empty slice",
|
||
value: []string{},
|
||
expected: false, // 空 slice 不是 nil
|
||
skip: false,
|
||
},
|
||
{
|
||
name: "nil map",
|
||
value: map[string]int(nil),
|
||
expected: true,
|
||
skip: false,
|
||
},
|
||
{
|
||
name: "empty map",
|
||
value: map[string]int{},
|
||
expected: false, // 空 map 不是 nil
|
||
skip: false,
|
||
},
|
||
{
|
||
name: "zero int",
|
||
value: 0,
|
||
expected: true,
|
||
skip: false,
|
||
},
|
||
{
|
||
name: "non-zero int",
|
||
value: 42,
|
||
expected: false,
|
||
skip: false,
|
||
},
|
||
{
|
||
name: "zero int64",
|
||
value: int64(0),
|
||
expected: true,
|
||
skip: false,
|
||
},
|
||
{
|
||
name: "non-zero int64",
|
||
value: int64(42),
|
||
expected: false,
|
||
skip: false,
|
||
},
|
||
{
|
||
name: "zero float64",
|
||
value: 0.0,
|
||
expected: true,
|
||
skip: false,
|
||
},
|
||
{
|
||
name: "non-zero float64",
|
||
value: 3.14,
|
||
expected: false,
|
||
skip: false,
|
||
},
|
||
{
|
||
name: "empty string",
|
||
value: "",
|
||
expected: true,
|
||
skip: false,
|
||
},
|
||
{
|
||
name: "non-empty string",
|
||
value: "test",
|
||
expected: false,
|
||
skip: false,
|
||
},
|
||
{
|
||
name: "false bool",
|
||
value: false,
|
||
expected: true,
|
||
skip: false,
|
||
},
|
||
{
|
||
name: "true bool",
|
||
value: true,
|
||
expected: false,
|
||
skip: false,
|
||
},
|
||
{
|
||
name: "struct with zero values",
|
||
value: testUser{},
|
||
expected: true, // 所有欄位都是零值,應該返回 true
|
||
skip: false,
|
||
},
|
||
}
|
||
|
||
for _, tt := range tests {
|
||
t.Run(tt.name, func(t *testing.T) {
|
||
if tt.skip {
|
||
t.Skip("Skipping test")
|
||
return
|
||
}
|
||
// 使用 reflect.ValueOf 來獲取 reflect.Value
|
||
v := reflect.ValueOf(tt.value)
|
||
// 檢查是否為零值(nil interface 會導致 zero Value)
|
||
if !v.IsValid() {
|
||
// 對於 nil interface,直接返回 true
|
||
assert.True(t, tt.expected)
|
||
return
|
||
}
|
||
result := isZero(v)
|
||
assert.Equal(t, tt.expected, result)
|
||
})
|
||
}
|
||
}
|
||
|
||
func TestNewRepository(t *testing.T) {
|
||
tests := []struct {
|
||
name string
|
||
keyspace string
|
||
wantErr bool
|
||
validate func(*testing.T, Repository[testUser], *DB)
|
||
}{
|
||
{
|
||
name: "valid keyspace",
|
||
keyspace: "test_keyspace",
|
||
wantErr: false,
|
||
validate: func(t *testing.T, repo Repository[testUser], db *DB) {
|
||
assert.NotNil(t, repo)
|
||
},
|
||
},
|
||
{
|
||
name: "empty keyspace uses default",
|
||
keyspace: "",
|
||
wantErr: false,
|
||
validate: func(t *testing.T, repo Repository[testUser], db *DB) {
|
||
assert.NotNil(t, repo)
|
||
},
|
||
},
|
||
}
|
||
|
||
for _, tt := range tests {
|
||
t.Run(tt.name, func(t *testing.T) {
|
||
// 注意:這需要一個有效的 DB 實例
|
||
// 在實際測試中,需要使用 mock 或 testcontainers
|
||
_ = tt
|
||
})
|
||
}
|
||
}
|
||
|
||
func TestRepository_Insert(t *testing.T) {
|
||
tests := []struct {
|
||
name string
|
||
description string
|
||
}{
|
||
{
|
||
name: "successful insert",
|
||
description: "should insert document successfully",
|
||
},
|
||
{
|
||
name: "duplicate key",
|
||
description: "should return error on duplicate key",
|
||
},
|
||
{
|
||
name: "invalid document",
|
||
description: "should return error for invalid document",
|
||
},
|
||
}
|
||
|
||
for _, tt := range tests {
|
||
t.Run(tt.name, func(t *testing.T) {
|
||
// 注意:這需要 mock session 或實際的資料庫連接
|
||
_ = tt
|
||
})
|
||
}
|
||
}
|
||
|
||
func TestRepository_Get(t *testing.T) {
|
||
tests := []struct {
|
||
name string
|
||
pk any
|
||
description string
|
||
wantErr bool
|
||
}{
|
||
{
|
||
name: "found with string key",
|
||
pk: "test-id",
|
||
description: "should return document when found",
|
||
wantErr: false,
|
||
},
|
||
{
|
||
name: "not found",
|
||
pk: "non-existent",
|
||
description: "should return ErrNotFound when not found",
|
||
wantErr: true,
|
||
},
|
||
{
|
||
name: "invalid primary key structure",
|
||
pk: "single-key",
|
||
description: "should return error for invalid key structure",
|
||
wantErr: true,
|
||
},
|
||
{
|
||
name: "struct primary key",
|
||
pk: testUser{ID: "test-id"},
|
||
description: "should work with struct primary key",
|
||
wantErr: false,
|
||
},
|
||
}
|
||
|
||
for _, tt := range tests {
|
||
t.Run(tt.name, func(t *testing.T) {
|
||
// 注意:這需要 mock session 或實際的資料庫連接
|
||
_ = tt
|
||
})
|
||
}
|
||
}
|
||
|
||
func TestRepository_Update(t *testing.T) {
|
||
tests := []struct {
|
||
name string
|
||
description string
|
||
wantErr bool
|
||
}{
|
||
{
|
||
name: "successful update",
|
||
description: "should update document successfully",
|
||
wantErr: false,
|
||
},
|
||
{
|
||
name: "not found",
|
||
description: "should return error when document not found",
|
||
wantErr: true,
|
||
},
|
||
{
|
||
name: "no fields to update",
|
||
description: "should return error when no fields to update",
|
||
wantErr: true,
|
||
},
|
||
}
|
||
|
||
for _, tt := range tests {
|
||
t.Run(tt.name, func(t *testing.T) {
|
||
// 注意:這需要 mock session 或實際的資料庫連接
|
||
_ = tt
|
||
})
|
||
}
|
||
}
|
||
|
||
func TestRepository_Delete(t *testing.T) {
|
||
tests := []struct {
|
||
name string
|
||
pk any
|
||
description string
|
||
wantErr bool
|
||
}{
|
||
{
|
||
name: "successful delete",
|
||
pk: "test-id",
|
||
description: "should delete document successfully",
|
||
wantErr: false,
|
||
},
|
||
{
|
||
name: "not found",
|
||
pk: "non-existent",
|
||
description: "should not return error when not found (idempotent)",
|
||
wantErr: false,
|
||
},
|
||
}
|
||
|
||
for _, tt := range tests {
|
||
t.Run(tt.name, func(t *testing.T) {
|
||
// 注意:這需要 mock session 或實際的資料庫連接
|
||
_ = tt
|
||
})
|
||
}
|
||
}
|
||
|
||
func TestRepository_InsertMany(t *testing.T) {
|
||
tests := []struct {
|
||
name string
|
||
docs []testUser
|
||
description string
|
||
wantErr bool
|
||
}{
|
||
{
|
||
name: "empty slice",
|
||
docs: []testUser{},
|
||
description: "should return nil for empty slice",
|
||
wantErr: false,
|
||
},
|
||
{
|
||
name: "single document",
|
||
docs: []testUser{{ID: "1", Name: "Alice"}},
|
||
description: "should insert single document",
|
||
wantErr: false,
|
||
},
|
||
{
|
||
name: "multiple documents",
|
||
docs: []testUser{{ID: "1", Name: "Alice"}, {ID: "2", Name: "Bob"}},
|
||
description: "should insert multiple documents",
|
||
wantErr: false,
|
||
},
|
||
{
|
||
name: "large batch",
|
||
docs: make([]testUser, 100),
|
||
description: "should handle large batch",
|
||
wantErr: false,
|
||
},
|
||
}
|
||
|
||
for _, tt := range tests {
|
||
t.Run(tt.name, func(t *testing.T) {
|
||
// 注意:這需要 mock session 或實際的資料庫連接
|
||
_ = tt
|
||
})
|
||
}
|
||
}
|
||
|
||
func TestRepository_Query(t *testing.T) {
|
||
t.Run("should return QueryBuilder", func(t *testing.T) {
|
||
// 注意:這需要一個有效的 repository
|
||
// 實際的執行需要資料庫連接
|
||
})
|
||
}
|
||
|
||
func TestBuildUpdateStatement(t *testing.T) {
|
||
tests := []struct {
|
||
name string
|
||
setCols []string
|
||
whereCols []string
|
||
table string
|
||
validate func(*testing.T, string, []string)
|
||
}{
|
||
{
|
||
name: "single set column, single where column",
|
||
setCols: []string{"name"},
|
||
whereCols: []string{"id"},
|
||
table: "users",
|
||
validate: func(t *testing.T, stmt string, names []string) {
|
||
assert.Contains(t, stmt, "UPDATE")
|
||
assert.Contains(t, stmt, "users")
|
||
assert.Contains(t, stmt, "SET")
|
||
assert.Contains(t, stmt, "WHERE")
|
||
assert.Len(t, names, 2) // name, id
|
||
},
|
||
},
|
||
{
|
||
name: "multiple set columns, single where column",
|
||
setCols: []string{"name", "email", "age"},
|
||
whereCols: []string{"id"},
|
||
table: "users",
|
||
validate: func(t *testing.T, stmt string, names []string) {
|
||
assert.Contains(t, stmt, "UPDATE")
|
||
assert.Contains(t, stmt, "users")
|
||
assert.Len(t, names, 4) // name, email, age, id
|
||
},
|
||
},
|
||
{
|
||
name: "single set column, multiple where columns",
|
||
setCols: []string{"status"},
|
||
whereCols: []string{"user_id", "account_id"},
|
||
table: "accounts",
|
||
validate: func(t *testing.T, stmt string, names []string) {
|
||
assert.Contains(t, stmt, "UPDATE")
|
||
assert.Contains(t, stmt, "accounts")
|
||
assert.Len(t, names, 3) // status, user_id, account_id
|
||
},
|
||
},
|
||
{
|
||
name: "multiple set and where columns",
|
||
setCols: []string{"name", "email"},
|
||
whereCols: []string{"id", "version"},
|
||
table: "users",
|
||
validate: func(t *testing.T, stmt string, names []string) {
|
||
assert.Contains(t, stmt, "UPDATE")
|
||
assert.Len(t, names, 4) // name, email, id, version
|
||
},
|
||
},
|
||
}
|
||
|
||
for _, tt := range tests {
|
||
t.Run(tt.name, func(t *testing.T) {
|
||
// 創建一個臨時的 repository 來測試 buildUpdateStatement
|
||
// 注意:這需要一個有效的 metadata
|
||
// 使用 testUser 的 metadata
|
||
var zero testUser
|
||
metadata, err := generateMetadata(zero, "test_keyspace")
|
||
require.NoError(t, err)
|
||
|
||
repo := &repository[testUser]{
|
||
table: tt.table,
|
||
metadata: metadata,
|
||
}
|
||
stmt, names := repo.buildUpdateStatement(tt.setCols, tt.whereCols)
|
||
tt.validate(t, stmt, names)
|
||
})
|
||
}
|
||
}
|
||
|
||
func TestBuildUpdateFields(t *testing.T) {
|
||
tests := []struct {
|
||
name string
|
||
doc testUser
|
||
includeZero bool
|
||
wantErr bool
|
||
validate func(*testing.T, *updateFields)
|
||
}{
|
||
{
|
||
name: "update with includeZero false",
|
||
doc: testUser{ID: "1", Name: "Alice", Email: "alice@example.com"},
|
||
includeZero: false,
|
||
wantErr: false,
|
||
validate: func(t *testing.T, fields *updateFields) {
|
||
assert.NotEmpty(t, fields.setCols)
|
||
assert.Contains(t, fields.whereCols, "id")
|
||
},
|
||
},
|
||
{
|
||
name: "update with includeZero true",
|
||
doc: testUser{ID: "1", Name: "", Email: ""},
|
||
includeZero: true,
|
||
wantErr: false,
|
||
validate: func(t *testing.T, fields *updateFields) {
|
||
assert.NotEmpty(t, fields.setCols)
|
||
},
|
||
},
|
||
{
|
||
name: "no fields to update",
|
||
doc: testUser{ID: "1"},
|
||
includeZero: false,
|
||
wantErr: true,
|
||
validate: nil,
|
||
},
|
||
}
|
||
|
||
for _, tt := range tests {
|
||
t.Run(tt.name, func(t *testing.T) {
|
||
// 注意:這需要一個有效的 repository 和 metadata
|
||
// 在實際測試中,需要使用 mock 或 testcontainers
|
||
_ = tt
|
||
})
|
||
}
|
||
}
|
||
|