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 }