blockchain/internal/lib/cassandra/ez_transaction_test.go

292 lines
6.8 KiB
Go
Raw Normal View History

2025-08-05 23:41:29 +00:00
package cassandra
import (
"github.com/gocql/gocql"
"github.com/stretchr/testify/assert"
"testing"
)
type TE struct {
ID gocql.UUID `cql:"id" partition:"true"`
Name string `cql:"name"`
}
func (m *TE) TableName() string {
return "test_entity"
}
func TestNewEZTransactionInsert(t *testing.T) {
ctx, cassandraContainer, host, port := setupCassandraContainer(t)
defer cassandraContainer.Terminate(ctx)
// 連線
hosts := []string{host}
db, err := NewCassandraDB(
hosts,
WithPort(port),
WithConsistency(gocql.One),
WithNumConns(2),
)
assert.NoError(t, err)
assert.NotNil(t, db)
// 建立 keyspace + table
err = db.EnsureTable("CREATE KEYSPACE my_keyspace\nWITH replication = {\n 'class': 'SimpleStrategy',\n 'replication_factor': 1\n};\n")
assert.NoError(t, err, "should success ensure table")
err = db.EnsureTable(`
CREATE TABLE IF NOT EXISTS my_keyspace.test_entity (
id UUID PRIMARY KEY,
name TEXT
);`)
assert.NoError(t, err)
// 定義 table-driven 測試案例
tests := []struct {
name string
doc TE
}{
{
name: "insert_record_alice",
doc: TE{
ID: gocql.TimeUUID(),
Name: "Alice",
},
},
{
name: "insert_record_bob",
doc: TE{
ID: gocql.TimeUUID(),
Name: "Bob",
},
},
{
name: "insert_record_empty_name",
doc: TE{
ID: gocql.TimeUUID(),
Name: "",
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// 每個子案例都使用新的 transaction
tx := NewEZTransaction(ctx, "my_keyspace", db)
// 1. 呼叫 Insert
err := tx.Insert(ctx, &tt.doc)
assert.NoError(t, err, "Insert() 應該不會錯誤")
// 2. 呼叫 Commit真正寫入 Cassandra
err = tx.Commit()
assert.NoError(t, err, "Commit() 應該不會錯誤")
// 3. 從 Cassandra 查回資料,驗證
var got TE
got.ID = tt.doc.ID
err = db.Get(ctx, &got, "my_keyspace")
assert.NoError(t, err)
// 驗證欄位值符合
assert.Equal(t, tt.doc.ID, got.ID, "ID 應一致")
assert.Equal(t, tt.doc.Name, got.Name, "Name 應一致")
})
}
}
func TestNewEZTransactionDelete(t *testing.T) {
ctx, cassandraContainer, host, port := setupCassandraContainer(t)
defer cassandraContainer.Terminate(ctx)
// 連線
hosts := []string{host}
db, err := NewCassandraDB(
hosts,
WithPort(port),
WithConsistency(gocql.One),
WithNumConns(2),
)
assert.NoError(t, err)
assert.NotNil(t, db)
// 建立 keyspace + table
err = db.EnsureTable("CREATE KEYSPACE my_keyspace\nWITH replication = {\n 'class': 'SimpleStrategy',\n 'replication_factor': 1\n};\n")
assert.NoError(t, err, "should success ensure table")
err = db.EnsureTable(`
CREATE TABLE IF NOT EXISTS my_keyspace.test_entity (
id UUID PRIMARY KEY,
name TEXT
);`)
assert.NoError(t, err)
// 定義 table-driven 測試案例
tests := []struct {
name string
doc TE
}{
{
name: "ok",
doc: TE{
ID: gocql.TimeUUID(),
Name: "Alice",
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// 每個子案例都使用新的 transaction
tx := NewEZTransaction(ctx, "my_keyspace", db)
// 1. 呼叫 Delete
err := tx.Insert(ctx, &tt.doc)
assert.NoError(t, err, "Insert() 應該不會錯誤")
// 2. 呼叫 Delete
err = tx.Delete(ctx, &tt.doc)
assert.NoError(t, err, "Delete() 應該不會錯誤")
// 3. 呼叫 Commit真正寫入 Cassandra
err = tx.Commit()
assert.NoError(t, err, "Commit() 應該不會錯誤")
//
// 4. 從 Cassandra 查回資料,驗證
var got TE
got.ID = tt.doc.ID
err = db.Get(ctx, &got, "my_keyspace")
assert.Equal(t, err, gocql.ErrNotFound)
})
}
}
func TestNewEZTransactionUpdate(t *testing.T) {
ctx, cassandraContainer, host, port := setupCassandraContainer(t)
t.Cleanup(func() { cassandraContainer.Terminate(ctx) })
// 1. 連線並建立 keyspace + table
db, err := NewCassandraDB(
[]string{host},
WithPort(port),
WithConsistency(gocql.One),
WithNumConns(2),
)
assert.NoError(t, err)
assert.NotNil(t, db)
assert.NoError(t, db.EnsureTable(`
CREATE KEYSPACE IF NOT EXISTS my_keyspace
WITH replication = {'class':'SimpleStrategy','replication_factor':1};
`))
assert.NoError(t, db.EnsureTable(`
CREATE TABLE IF NOT EXISTS my_keyspace.test_entity (
id UUID PRIMARY KEY,
name TEXT
);
`))
// 2. 插入初始資料
id := gocql.TimeUUID()
before := TE{ID: id, Name: "Before"}
assert.NoError(t, db.Insert(ctx, &before, "my_keyspace"))
// 定義多組更新案例
tests := []struct {
name string
newName string
wantErr bool
}{
{name: "update_to_Alice", newName: "Alice"},
{name: "update_to_empty", newName: "", wantErr: true},
{name: "update_to_Bob", newName: "Bob"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// 為每個案例都重置為 Before
// 重新 insert 一次(覆蓋舊值)
assert.NoError(t, db.Insert(ctx, &before, "my_keyspace"))
// 3. 建立 transaction 並呼叫 Update
tx := NewEZTransaction(ctx, "my_keyspace", db)
updateDoc := TE{ID: id, Name: tt.newName}
err := tx.Update(ctx, &updateDoc)
if tt.wantErr {
assert.Error(t, err, "Update() 應該會出錯")
return
}
assert.NoError(t, err, "Update() 不應出錯")
// 4. Commit 實際寫入
err = tx.Commit()
assert.NoError(t, err, "Commit() 不應出錯")
// 5. 查詢並驗證
var got TE
got.ID = id
err = db.Get(ctx, &got, "my_keyspace")
assert.NoError(t, err, "db.Get() 應成功")
assert.Equal(t, id, got.ID, "ID 應一致")
assert.Equal(t, tt.newName, got.Name, "Name 應被更新為最新值")
})
}
}
func Test_Rollback(t *testing.T) {
ctx, cassandraContainer, host, port := setupCassandraContainer(t)
t.Cleanup(func() { cassandraContainer.Terminate(ctx) })
// 1. 連線並建立 keyspace + table
db, err := NewCassandraDB(
[]string{host},
WithPort(port),
WithConsistency(gocql.One),
WithNumConns(2),
)
assert.NoError(t, err)
assert.NotNil(t, db)
assert.NoError(t, db.EnsureTable(`
CREATE KEYSPACE IF NOT EXISTS my_keyspace
WITH replication = {'class':'SimpleStrategy','replication_factor':1};
`))
assert.NoError(t, db.EnsureTable(`
CREATE TABLE IF NOT EXISTS my_keyspace.test_entity (
id UUID PRIMARY KEY,
name TEXT
);
`))
// 3. 用 Transaction 插入一筆資料,並 Commit
id := gocql.TimeUUID()
doc := TE{ID: id, Name: "Alice"}
tx := NewEZTransaction(ctx, "my_keyspace", db)
err = tx.Insert(ctx, &doc)
assert.NoError(t, err)
err = tx.Commit()
assert.NoError(t, err)
// 4. Query 確認資料已存在
var got TE
got.ID = id
err = db.Get(ctx, &got, "my_keyspace")
assert.NoError(t, err)
assert.Equal(t, got.Name, doc.Name)
// 5. 呼叫 Rollback應自動刪除剛剛那筆
err = tx.Rollback()
assert.NoError(t, err)
var afterGot TE
afterGot.ID = id
err = db.Get(ctx, &afterGot, "my_keyspace")
assert.Error(t, err)
// Output:
// after commit: Alice
// after rollback: not found
}