blockchain/internal/lib/cassandra/crud_test.go

369 lines
7.9 KiB
Go

package cassandra
import (
"context"
"fmt"
"testing"
"time"
"github.com/gocql/gocql"
"github.com/scylladb/gocqlx/v3/qb"
"github.com/stretchr/testify/assert"
)
func TestInsert(t *testing.T) {
container, db := setupForTest(t)
defer func() {
_ = container.Container.Terminate(container.Ctx)
fmt.Println("[TEST] Container terminated")
}()
now := time.Now()
// 測試案例(可擴充)
tests := []struct {
name string
input MonkeyEntity
}{
{
name: "insert George",
input: MonkeyEntity{
ID: gocql.TimeUUID(),
Name: "George",
UpdateAt: now,
CreateAt: now,
},
},
{
name: "insert Bob",
input: MonkeyEntity{
ID: gocql.TimeUUID(),
Name: "Bob",
UpdateAt: now,
CreateAt: now,
},
},
{
name: "insert Alice",
input: MonkeyEntity{
ID: gocql.TimeUUID(),
Name: "Alice",
UpdateAt: now,
CreateAt: now,
},
},
}
// 執行測試
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
err := db.Insert(container.Ctx, &tc.input, "my_keyspace")
assert.NoError(t, err)
// 驗證寫入
var name string
q := db.GetSession().Query("SELECT name FROM my_keyspace.monkey_entity WHERE id = ?", []string{"name"})
err = q.Bind(tc.input.ID).GetRelease(&name)
assert.NoError(t, err)
assert.Equal(t, tc.input.Name, name)
})
}
}
func TestGet(t *testing.T) {
container, db := setupForTest(t)
defer func() {
_ = container.Container.Terminate(container.Ctx)
fmt.Println("[TEST] Container terminated")
}()
now := time.Now()
monkey := MonkeyEntity{
ID: gocql.TimeUUID(),
Name: "George",
UpdateAt: now,
CreateAt: now,
}
// 插入一筆資料
err := db.Insert(container.Ctx, &monkey, "my_keyspace")
assert.NoError(t, err)
tests := []struct {
name string
filter MonkeyEntity
expect string
}{
{
name: "Get existing monkey",
filter: MonkeyEntity{ID: monkey.ID, Name: monkey.Name},
expect: "George",
},
{
name: "Get non-existent monkey",
filter: MonkeyEntity{ID: gocql.TimeUUID(), Name: "GG"},
expect: "",
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
result := tc.filter // 預設填入主鍵
err := db.Get(container.Ctx, &result, "my_keyspace")
if tc.expect == "" {
assert.Error(t, err, "expected error for missing record")
} else {
assert.NoError(t, err)
assert.Equal(t, tc.expect, result.Name)
}
})
}
}
func TestDelete(t *testing.T) {
container, db := setupForTest(t)
defer func() {
_ = container.Container.Terminate(container.Ctx)
fmt.Println("[TEST] Container terminated")
}()
now := time.Now()
monkey := MonkeyEntity{
ID: gocql.TimeUUID(),
Name: "DeleteMe",
UpdateAt: now,
CreateAt: now,
}
// 插入資料
err := db.Insert(container.Ctx, &monkey, "my_keyspace")
assert.NoError(t, err)
// 先確認有插入成功
verify := MonkeyEntity{ID: monkey.ID, Name: monkey.Name}
err = db.Get(container.Ctx, &verify, "my_keyspace")
assert.NoError(t, err)
assert.Equal(t, "DeleteMe", verify.Name)
// 執行刪除
err = db.Delete(container.Ctx, &monkey, "my_keyspace")
assert.NoError(t, err)
// 再查,應該查不到
result := MonkeyEntity{ID: monkey.ID, Name: monkey.Name}
err = db.Get(container.Ctx, &result, "my_keyspace")
assert.Error(t, err, "expected error because record should be deleted")
}
func TestUpdate(t *testing.T) {
container, db := setupForTest(t)
defer func() {
_ = container.Container.Terminate(container.Ctx)
fmt.Println("[TEST] Container terminated")
}()
now := time.Now()
id := gocql.TimeUUID()
// Step 1: 插入初始資料
monkey := MonkeyEntity{
ID: id,
Name: "OldName",
UpdateAt: now,
CreateAt: now,
}
err := db.Insert(container.Ctx, &monkey, "my_keyspace")
assert.NoError(t, err)
// Step 2: 更新 UpdateAt 欄位(模擬只更新一欄)
updatedTime := now.Add(10 * time.Minute)
updateDoc := MonkeyEntity{
ID: id,
Name: "OldName", // 主鍵
UpdateAt: updatedTime,
// CreateAt 是零值,不會被更新
}
err = db.Update(container.Ctx, &updateDoc, "my_keyspace")
assert.NoError(t, err)
// Step 3: 查詢回來驗證更新
result := MonkeyEntity{
ID: id,
Name: "OldName",
}
err = db.Get(container.Ctx, &result, "my_keyspace")
assert.NoError(t, err)
assert.WithinDuration(t, updatedTime, result.UpdateAt, time.Second)
assert.WithinDuration(t, now, result.CreateAt, time.Second) // 未被更新
}
func insertSampleConsistency(t *testing.T, db *CassandraDB, ctx context.Context, keyspace string) *Consistency {
err := db.EnsureTable(`
CREATE TABLE IF NOT EXISTS my_keyspace.consistency (
id UUID,
consistency_name TEXT,
last_task_id TEXT,
target TEXT,
status TEXT,
consistency_type TEXT,
consistency_map TEXT,
create_at BIGINT,
update_at BIGINT,
PRIMARY KEY ((id))
);`)
assert.NoError(t, err)
c := &Consistency{
ID: gocql.TimeUUID(),
ConsistencyName: "query-test",
LastTaskID: "task-1",
Target: "test.csv",
Status: "Running",
ConsistencyType: "simple",
ConsistencyMap: `{"example": "value"}`,
CreateAT: time.Now().UnixNano(),
UpdateAT: time.Now().UnixNano(),
}
err = db.Insert(ctx, c, keyspace)
assert.NoError(t, err)
return c
}
func TestQueryBuilder_WithWhere(t *testing.T) {
container, db := setupForTest(t)
defer func() {
_ = container.Container.Terminate(container.Ctx)
fmt.Println("[TEST] Container terminated")
}()
saved := insertSampleConsistency(t, db, container.Ctx, "my_keyspace")
t.Run("query by id", func(t *testing.T) {
var results []*Consistency
e := &Consistency{}
field := GetCqlTag(e, &e.ID)
err := db.QueryBuilder(
container.Ctx,
&Consistency{},
&results,
"my_keyspace",
WithWhere(
[]qb.Cmp{qb.Eq(field)},
map[string]any{field: saved.ID.String()},
),
)
assert.NoError(t, err)
assert.NotEmpty(t, results)
found := false
for _, r := range results {
if r.ID == saved.ID {
found = true
break
}
}
assert.True(t, found, "should find inserted consistency")
})
t.Run("query with unmatched id", func(t *testing.T) {
var results []*Consistency
e := &Consistency{}
field := GetCqlTag(e, &e.ID)
err := db.QueryBuilder(
container.Ctx,
&Consistency{},
&results,
"my_keyspace",
WithWhere(
[]qb.Cmp{qb.Eq(field)},
map[string]any{field: "NonExist"},
),
)
assert.Error(t, err)
assert.Empty(t, results)
})
t.Run("query by in", func(t *testing.T) {
var results []*Consistency
e := &Consistency{}
field := GetCqlTag(e, &e.ID)
err := db.QueryBuilder(
container.Ctx,
&Consistency{},
&results,
"my_keyspace",
WithWhere(
[]qb.Cmp{qb.In(field)},
map[string]any{field: []gocql.UUID{saved.ID}},
),
)
assert.NoError(t, err)
assert.NotEmpty(t, results)
found := false
for _, r := range results {
if r.ID == saved.ID {
found = true
break
}
}
assert.True(t, found, "should find inserted consistency")
})
t.Run("query by one is not in", func(t *testing.T) {
var results []*Consistency
e := &Consistency{}
field := GetCqlTag(e, &e.ID)
err := db.QueryBuilder(
container.Ctx,
&Consistency{},
&results,
"my_keyspace",
WithWhere(
[]qb.Cmp{qb.In(field)},
map[string]any{field: []gocql.UUID{saved.ID, gocql.TimeUUID()}},
),
)
assert.NoError(t, err)
assert.NotEmpty(t, results)
found := false
for _, r := range results {
if r.ID == saved.ID {
found = true
break
}
}
assert.True(t, found, "should find inserted consistency")
})
t.Run("query get all", func(t *testing.T) {
var results []*Consistency
e := &Consistency{}
err := db.QueryBuilder(
container.Ctx,
e,
&results,
"my_keyspace",
)
assert.NoError(t, err)
assert.NotEmpty(t, results)
found := false
for _, r := range results {
if r.ID == saved.ID {
found = true
break
}
}
assert.True(t, found, "should find inserted consistency")
})
}