This is an automated email from the ASF dual-hosted git repository.
jimin pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-seata-go.git
The following commit(s) were added to refs/heads/master by this push:
new faa32f07 test: add some test (#979)
faa32f07 is described below
commit faa32f07441b67b82c0b10d2022bd2bdd44ba467
Author: Eric Wang <[email protected]>
AuthorDate: Sat Nov 8 05:33:01 2025 +0300
test: add some test (#979)
---
changes/dev.md | 3 +
.../tcc_fence_config_test.go} | 29 +-
pkg/rm/tcc/fence/fence_driver_conn_test.go | 562 +++++++++++
pkg/rm/tcc/fence/fence_test.go | 792 +++++++++++++++
.../handler/tcc_fence_wrapper_handler_test.go | 1027 ++++++++++++++++++++
.../fence/store/db/sql/tcc_fence_store_sql_test.go | 409 ++++++++
6 files changed, 2816 insertions(+), 6 deletions(-)
diff --git a/changes/dev.md b/changes/dev.md
index 46eb7b1c..0c8e5acc 100755
--- a/changes/dev.md
+++ b/changes/dev.md
@@ -37,6 +37,9 @@
### test:
+ - [[#958](https://github.com/apache/incubator-seata-go/issues/958)] improve
test coverage for pkg/rm/tcc/fence/store/db/sql (100%)
+ - [[#957](https://github.com/apache/incubator-seata-go/issues/957)] improve
test coverage for pkg/rm/tcc/fence/handler (84.5%)
+ - [[#955](https://github.com/apache/incubator-seata-go/issues/955)] improve
test coverage for pkg/rm/tcc/fence (95.1%)
- [[#947](https://github.com/apache/incubator-seata-go/issues/947)] improve
test coverage for pkg/discovery/mock
- [[#948](https://github.com/apache/incubator-seata-go/issues/948)] improve
test coverage for pkg/integration
diff --git a/pkg/rm/tcc/fence/fence_driver_conn_test.go
b/pkg/rm/tcc/fence/config/tcc_fence_config_test.go
similarity index 53%
copy from pkg/rm/tcc/fence/fence_driver_conn_test.go
copy to pkg/rm/tcc/fence/config/tcc_fence_config_test.go
index 336da282..fa174147 100644
--- a/pkg/rm/tcc/fence/fence_driver_conn_test.go
+++ b/pkg/rm/tcc/fence/config/tcc_fence_config_test.go
@@ -15,16 +15,33 @@
* limitations under the License.
*/
-package fence
+package config
import (
"testing"
- "github.com/stretchr/testify/assert"
+ "seata.apache.org/seata-go/pkg/util/log"
)
-func TestBegin(t *testing.T) {
- tx, err := (&FenceConn{}).Begin()
- assert.NotNil(t, err)
- assert.Nil(t, tx)
+func TestInitFence(t *testing.T) {
+ log.Init()
+
+ // InitFence is currently empty, just call it to ensure no panic
+ InitFence()
+}
+
+func TestInitCleanTask(t *testing.T) {
+ log.Init()
+
+ // Test with an invalid DSN - should log warning but not panic
+ dsn := "invalid-dsn"
+
+ // Call InitCleanTask which starts a goroutine
+ // Even with invalid DSN, it should handle the error gracefully
+ InitCleanTask(dsn)
+
+ // Note: We don't call Destroy() here because there's a known issue
+ // where Destroy() panics if the logQueue was not initialized
+ // (which happens when the DSN is invalid and sql.Open fails).
+ // This is a limitation of the current handler implementation.
}
diff --git a/pkg/rm/tcc/fence/fence_driver_conn_test.go
b/pkg/rm/tcc/fence/fence_driver_conn_test.go
index 336da282..bfae1da1 100644
--- a/pkg/rm/tcc/fence/fence_driver_conn_test.go
+++ b/pkg/rm/tcc/fence/fence_driver_conn_test.go
@@ -18,13 +18,575 @@
package fence
import (
+ "context"
+ "database/sql"
+ "database/sql/driver"
+ "errors"
"testing"
+ sqlmock "github.com/DATA-DOG/go-sqlmock"
"github.com/stretchr/testify/assert"
+
+ "seata.apache.org/seata-go/pkg/rm/tcc/fence/enum"
+ "seata.apache.org/seata-go/pkg/tm"
+ "seata.apache.org/seata-go/pkg/util/log"
)
+// Mock connection without BeginTx support
+type mockConnNoBeginTx struct {
+ driver.Conn
+}
+
+func (m *mockConnNoBeginTx) Prepare(query string) (driver.Stmt, error) {
+ return nil, errors.New("not used")
+}
+
+func (m *mockConnNoBeginTx) Close() error {
+ return nil
+}
+
+// mockConnWithBeginTx implements driver.ConnBeginTx
+type mockConnWithBeginTx struct {
+ *mockConn
+ beginTxFunc func(ctx context.Context, opts driver.TxOptions)
(driver.Tx, error)
+}
+
+func (m *mockConnWithBeginTx) BeginTx(ctx context.Context, opts
driver.TxOptions) (driver.Tx, error) {
+ if m.beginTxFunc != nil {
+ return m.beginTxFunc(ctx, opts)
+ }
+ return nil, errors.New("not implemented")
+}
+
func TestBegin(t *testing.T) {
tx, err := (&FenceConn{}).Begin()
assert.NotNil(t, err)
assert.Nil(t, tx)
}
+
+func TestFenceConn_Prepare(t *testing.T) {
+ mockConn := &mockConn{
+ prepareFunc: func(query string) (driver.Stmt, error) {
+ return nil, nil
+ },
+ }
+
+ fenceConn := &FenceConn{TargetConn: mockConn}
+ stmt, err := fenceConn.Prepare("SELECT 1")
+ assert.NoError(t, err)
+ assert.Nil(t, stmt)
+}
+
+func TestFenceConn_PrepareContext(t *testing.T) {
+ mockConn := &mockConn{
+ prepareFunc: func(query string) (driver.Stmt, error) {
+ return nil, nil
+ },
+ }
+
+ fenceConn := &FenceConn{TargetConn: mockConn}
+ stmt, err := fenceConn.PrepareContext(context.Background(), "SELECT 1")
+ assert.NoError(t, err)
+ assert.Nil(t, stmt)
+}
+
+func TestFenceConn_Exec(t *testing.T) {
+ tests := []struct {
+ name string
+ conn driver.Conn
+ wantErr bool
+ }{
+ {
+ name: "exec supported",
+ conn: &mockConnWithExec{
+ mockConn: &mockConn{},
+ execFunc: func(query string, args
[]driver.Value) (driver.Result, error) {
+ return &mockResult{}, nil
+ },
+ },
+ wantErr: false,
+ },
+ {
+ name: "exec not supported",
+ conn: &mockConn{},
+ wantErr: true,
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ fenceConn := &FenceConn{TargetConn: tt.conn}
+ result, err := fenceConn.Exec("INSERT INTO test VALUES
(?)", []driver.Value{1})
+
+ if tt.wantErr {
+ assert.Error(t, err)
+ assert.Equal(t, driver.ErrSkip, err)
+ } else {
+ assert.NoError(t, err)
+ assert.NotNil(t, result)
+ }
+ })
+ }
+}
+
+func TestFenceConn_ExecContext(t *testing.T) {
+ tests := []struct {
+ name string
+ conn driver.Conn
+ wantErr bool
+ errType error
+ }{
+ {
+ name: "exec context supported",
+ conn: &mockConnWithExecContext{
+ mockConn: &mockConn{},
+ execContextFunc: func(ctx context.Context,
query string, args []driver.NamedValue) (driver.Result, error) {
+ return &mockResult{}, nil
+ },
+ },
+ wantErr: false,
+ },
+ {
+ name: "fallback to exec - exec supported",
+ conn: &mockConnWithExec{
+ mockConn: &mockConn{},
+ execFunc: func(query string, args
[]driver.Value) (driver.Result, error) {
+ return &mockResult{}, nil
+ },
+ },
+ wantErr: false,
+ },
+ {
+ name: "fallback to exec - exec not supported",
+ conn: &mockConn{},
+ wantErr: true,
+ errType: driver.ErrSkip,
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ fenceConn := &FenceConn{TargetConn: tt.conn}
+ args := []driver.NamedValue{{Ordinal: 1, Value: 1}}
+ result, err :=
fenceConn.ExecContext(context.Background(), "INSERT INTO test VALUES (?)", args)
+
+ if tt.wantErr {
+ assert.Error(t, err)
+ if tt.errType != nil {
+ assert.Equal(t, tt.errType, err)
+ }
+ } else {
+ assert.NoError(t, err)
+ assert.NotNil(t, result)
+ }
+ })
+ }
+}
+
+func TestFenceConn_Query(t *testing.T) {
+ tests := []struct {
+ name string
+ conn driver.Conn
+ wantErr bool
+ }{
+ {
+ name: "query supported",
+ conn: &mockConnWithQuery{
+ mockConn: &mockConn{},
+ queryFunc: func(query string, args
[]driver.Value) (driver.Rows, error) {
+ return nil, nil
+ },
+ },
+ wantErr: false,
+ },
+ {
+ name: "query not supported",
+ conn: &mockConn{},
+ wantErr: true,
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ fenceConn := &FenceConn{TargetConn: tt.conn}
+ rows, err := fenceConn.Query("SELECT * FROM test",
[]driver.Value{})
+
+ if tt.wantErr {
+ assert.Error(t, err)
+ assert.Equal(t, driver.ErrSkip, err)
+ } else {
+ assert.NoError(t, err)
+ assert.Nil(t, rows)
+ }
+ })
+ }
+}
+
+func TestFenceConn_QueryContext(t *testing.T) {
+ tests := []struct {
+ name string
+ conn driver.Conn
+ wantErr bool
+ errType error
+ }{
+ {
+ name: "query context supported",
+ conn: &mockConnWithQueryContext{
+ mockConn: &mockConn{},
+ queryContextFunc: func(ctx context.Context,
query string, args []driver.NamedValue) (driver.Rows, error) {
+ return nil, nil
+ },
+ },
+ wantErr: false,
+ },
+ {
+ name: "fallback to query - query supported",
+ conn: &mockConnWithQuery{
+ mockConn: &mockConn{},
+ queryFunc: func(query string, args
[]driver.Value) (driver.Rows, error) {
+ return nil, nil
+ },
+ },
+ wantErr: false,
+ },
+ {
+ name: "fallback to query - query not supported",
+ conn: &mockConn{},
+ wantErr: true,
+ errType: driver.ErrSkip,
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ fenceConn := &FenceConn{TargetConn: tt.conn}
+ args := []driver.NamedValue{{Ordinal: 1, Value: 1}}
+ rows, err :=
fenceConn.QueryContext(context.Background(), "SELECT * FROM test WHERE id = ?",
args)
+
+ if tt.wantErr {
+ assert.Error(t, err)
+ if tt.errType != nil {
+ assert.Equal(t, tt.errType, err)
+ }
+ } else {
+ assert.NoError(t, err)
+ assert.Nil(t, rows)
+ }
+ })
+ }
+}
+
+func TestFenceConn_ResetSession(t *testing.T) {
+ tests := []struct {
+ name string
+ conn driver.Conn
+ wantErr bool
+ errType error
+ }{
+ {
+ name: "reset session supported",
+ conn: &mockConnWithResetSession{
+ mockConn: &mockConn{},
+ resetFunc: func(ctx context.Context) error {
+ return nil
+ },
+ },
+ wantErr: false,
+ },
+ {
+ name: "reset session not supported",
+ conn: &mockConn{},
+ wantErr: true,
+ errType: driver.ErrSkip,
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ fenceConn := &FenceConn{TargetConn: tt.conn}
+ err := fenceConn.ResetSession(context.Background())
+
+ if tt.wantErr {
+ assert.Error(t, err)
+ if tt.errType != nil {
+ assert.Equal(t, tt.errType, err)
+ }
+ } else {
+ assert.NoError(t, err)
+ }
+ })
+ }
+}
+
+func TestFenceConn_Close(t *testing.T) {
+ tests := []struct {
+ name string
+ conn *mockConn
+ wantErr bool
+ }{
+ {
+ name: "close success",
+ conn: &mockConn{
+ closeFunc: func() error {
+ return nil
+ },
+ },
+ wantErr: false,
+ },
+ {
+ name: "close with error",
+ conn: &mockConn{
+ closeFunc: func() error {
+ return errors.New("close error")
+ },
+ },
+ wantErr: true,
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ fenceConn := &FenceConn{TargetConn: tt.conn}
+ err := fenceConn.Close()
+
+ if tt.wantErr {
+ assert.Error(t, err)
+ } else {
+ assert.NoError(t, err)
+ }
+ })
+ }
+}
+
+func TestFenceConn_BeginTx_NotSupported(t *testing.T) {
+ log.Init()
+
+ mockConn := &mockConnNoBeginTx{}
+ fenceConn := &FenceConn{TargetConn: mockConn}
+
+ tx, err := fenceConn.BeginTx(context.Background(), driver.TxOptions{})
+ assert.Error(t, err)
+ assert.Contains(t, err.Error(), "operation unsupported")
+ assert.Nil(t, tx)
+}
+
+func TestFenceConn_BeginTx_NoSeataContext(t *testing.T) {
+ log.Init()
+
+ mockConnWithBegin := &mockConnWithBeginTx{
+ mockConn: &mockConn{},
+ beginTxFunc: func(ctx context.Context, opts driver.TxOptions)
(driver.Tx, error) {
+ return &mockTx{}, nil
+ },
+ }
+
+ fenceConn := &FenceConn{
+ TargetConn: mockConnWithBegin,
+ TargetDB: &sql.DB{},
+ }
+
+ tx, err := fenceConn.BeginTx(context.Background(), driver.TxOptions{})
+ assert.Error(t, err)
+ assert.Contains(t, err.Error(), "there is not seata context")
+ assert.Nil(t, tx)
+}
+
+func TestFenceConn_BeginTx_BeginTxError(t *testing.T) {
+ log.Init()
+
+ expectedErr := errors.New("begin tx failed")
+ mockConnWithBegin := &mockConnWithBeginTx{
+ mockConn: &mockConn{},
+ beginTxFunc: func(ctx context.Context, opts driver.TxOptions)
(driver.Tx, error) {
+ return nil, expectedErr
+ },
+ }
+
+ fenceConn := &FenceConn{
+ TargetConn: mockConnWithBegin,
+ }
+
+ tx, err := fenceConn.BeginTx(context.Background(), driver.TxOptions{})
+ assert.Error(t, err)
+ assert.Equal(t, expectedErr, err)
+ assert.Nil(t, tx)
+}
+
+func TestFenceConn_BeginTx_FenceTxAlreadyBegun(t *testing.T) {
+ log.Init()
+
+ mockTx := &mockTx{}
+ mockConnWithBegin := &mockConnWithBeginTx{
+ mockConn: &mockConn{},
+ beginTxFunc: func(ctx context.Context, opts driver.TxOptions)
(driver.Tx, error) {
+ return mockTx, nil
+ },
+ }
+
+ fenceConn := &FenceConn{
+ TargetConn: mockConnWithBegin,
+ }
+
+ ctx := context.Background()
+ ctx = tm.InitSeataContext(ctx)
+ tm.SetXID(ctx, "test-xid")
+ tm.SetFenceTxBeginedFlag(ctx, true) // Mark fence tx as already begun
+
+ tx, err := fenceConn.BeginTx(ctx, driver.TxOptions{})
+ assert.NoError(t, err)
+ assert.Equal(t, mockTx, tx) // Should return the original tx directly
+}
+
+func TestFenceConn_BeginTx_TargetDBBeginTxError(t *testing.T) {
+ log.Init()
+
+ mockTx := &mockTx{
+ rollbackFunc: func() error {
+ return nil
+ },
+ }
+ mockConnWithBegin := &mockConnWithBeginTx{
+ mockConn: &mockConn{},
+ beginTxFunc: func(ctx context.Context, opts driver.TxOptions)
(driver.Tx, error) {
+ return mockTx, nil
+ },
+ }
+
+ // Create a closed database to trigger BeginTx error
+ db, _, err := sqlmock.New()
+ assert.NoError(t, err)
+ db.Close() // Close it to cause BeginTx to fail
+
+ fenceConn := &FenceConn{
+ TargetConn: mockConnWithBegin,
+ TargetDB: db,
+ }
+
+ ctx := context.Background()
+ ctx = tm.InitSeataContext(ctx)
+ tm.SetXID(ctx, "test-xid")
+ tm.SetFenceTxBeginedFlag(ctx, false)
+
+ tx, err := fenceConn.BeginTx(ctx, driver.TxOptions{})
+ assert.Error(t, err)
+ assert.Nil(t, tx)
+}
+
+func TestFenceConn_BeginTx_WithFenceError(t *testing.T) {
+ log.Init()
+
+ db, mock, err := sqlmock.New()
+ assert.NoError(t, err)
+ defer db.Close()
+
+ mockTx := &mockTx{
+ rollbackFunc: func() error {
+ return nil
+ },
+ }
+ mockConnWithBegin := &mockConnWithBeginTx{
+ mockConn: &mockConn{},
+ beginTxFunc: func(ctx context.Context, opts driver.TxOptions)
(driver.Tx, error) {
+ return mockTx, nil
+ },
+ }
+
+ // Expect both transactions to begin
+ mock.ExpectBegin()
+
+ fenceConn := &FenceConn{
+ TargetConn: mockConnWithBegin,
+ TargetDB: db,
+ }
+
+ ctx := context.Background()
+ ctx = tm.InitSeataContext(ctx)
+ tm.SetXID(ctx, "test-xid")
+ tm.SetTxName(ctx, "test-tx")
+ tm.SetFencePhase(ctx, enum.FencePhaseNotExist) // This will cause
WithFence to fail
+ tm.SetFenceTxBeginedFlag(ctx, false)
+
+ tx, err := fenceConn.BeginTx(ctx, driver.TxOptions{})
+ assert.Error(t, err)
+ assert.Contains(t, err.Error(), "fence phase not exist")
+ assert.Nil(t, tx)
+}
+
+func TestFenceConn_BeginTx_Success(t *testing.T) {
+ log.Init()
+
+ db, mock, err :=
sqlmock.New(sqlmock.QueryMatcherOption(sqlmock.QueryMatcherEqual))
+ assert.NoError(t, err)
+ defer db.Close()
+
+ mockTx := &mockTx{
+ rollbackFunc: func() error {
+ return nil
+ },
+ }
+ mockConnWithBegin := &mockConnWithBeginTx{
+ mockConn: &mockConn{},
+ beginTxFunc: func(ctx context.Context, opts driver.TxOptions)
(driver.Tx, error) {
+ return mockTx, nil
+ },
+ }
+
+ // Expect fence transaction operations
+ mock.ExpectBegin()
+ mock.ExpectPrepare("insert into tcc_fence_log (xid, branch_id,
action_name, status, gmt_create, gmt_modified) values ( ?,?,?,?,?,?)").
+ ExpectExec().
+ WithArgs(sqlmock.AnyArg(), sqlmock.AnyArg(), sqlmock.AnyArg(),
sqlmock.AnyArg(), sqlmock.AnyArg(), sqlmock.AnyArg()).
+ WillReturnResult(sqlmock.NewResult(1, 1))
+ // Note: No ExpectCommit here - the transaction is kept open in FenceTx
+
+ fenceConn := &FenceConn{
+ TargetConn: mockConnWithBegin,
+ TargetDB: db,
+ }
+
+ ctx := context.Background()
+ ctx = tm.InitSeataContext(ctx)
+ tm.SetXID(ctx, "test-xid")
+ tm.SetTxName(ctx, "test-tx")
+ tm.SetFencePhase(ctx, enum.FencePhasePrepare)
+ tm.SetFenceTxBeginedFlag(ctx, false)
+
+ bac := &tm.BusinessActionContext{
+ Xid: "test-xid",
+ BranchId: 1001,
+ ActionName: "test-action",
+ ActionContext: make(map[string]interface{}),
+ }
+ tm.SetBusinessActionContext(ctx, bac)
+
+ tx, err := fenceConn.BeginTx(ctx, driver.TxOptions{})
+ assert.NoError(t, err)
+ assert.NotNil(t, tx)
+
+ fenceTx, ok := tx.(*FenceTx)
+ assert.True(t, ok)
+ assert.Equal(t, mockTx, fenceTx.TargetTx)
+ assert.NotNil(t, fenceTx.TargetFenceTx)
+ assert.Equal(t, ctx, fenceTx.Ctx)
+
+ // Clean up - the fence tx needs to be rolled back or committed
+ // For this test, we just verify the expectations were met so far
+ // In real usage, the FenceTx would be committed/rolled back by the
caller
+}
+
+func TestFenceConn_ResetSession_Error(t *testing.T) {
+ log.Init()
+
+ expectedErr := errors.New("reset session failed")
+ mockConnWithReset := &mockConnWithResetSession{
+ mockConn: &mockConn{},
+ resetFunc: func(ctx context.Context) error {
+ return expectedErr
+ },
+ }
+
+ fenceConn := &FenceConn{TargetConn: mockConnWithReset}
+ err := fenceConn.ResetSession(context.Background())
+ assert.Error(t, err)
+ assert.Equal(t, expectedErr, err)
+}
diff --git a/pkg/rm/tcc/fence/fence_test.go b/pkg/rm/tcc/fence/fence_test.go
new file mode 100644
index 00000000..aed0ebbd
--- /dev/null
+++ b/pkg/rm/tcc/fence/fence_test.go
@@ -0,0 +1,792 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package fence
+
+import (
+ "context"
+ "database/sql"
+ "database/sql/driver"
+ "errors"
+ "flag"
+ "testing"
+ "time"
+
+ sqlmock "github.com/DATA-DOG/go-sqlmock"
+ "github.com/stretchr/testify/assert"
+
+ "seata.apache.org/seata-go/pkg/rm/tcc/fence/enum"
+ "seata.apache.org/seata-go/pkg/tm"
+ "seata.apache.org/seata-go/pkg/util/log"
+)
+
+func TestInitFenceConfig(t *testing.T) {
+ log.Init()
+
+ tests := []struct {
+ name string
+ config Config
+ }{
+ {
+ name: "config with enable=false",
+ config: Config{
+ Enable: false,
+ Url: "test-url",
+ LogTableName: "test_table",
+ CleanPeriod: 10 * time.Minute,
+ },
+ },
+ {
+ name: "config with enable=true",
+ config: Config{
+ Enable: true,
+ Url: "",
+ LogTableName: "tcc_fence_log",
+ CleanPeriod: 5 * time.Minute,
+ },
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ InitFenceConfig(tt.config)
+ assert.Equal(t, tt.config, FenceConfig)
+ })
+ }
+}
+
+func TestConfig_RegisterFlagsWithPrefix(t *testing.T) {
+ tests := []struct {
+ name string
+ prefix string
+ }{
+ {
+ name: "register with prefix 'tcc'",
+ prefix: "tcc",
+ },
+ {
+ name: "register with prefix 'fence'",
+ prefix: "fence",
+ },
+ {
+ name: "register with empty prefix",
+ prefix: "",
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ cfg := &Config{}
+ fs := flag.NewFlagSet("test", flag.ContinueOnError)
+
+ cfg.RegisterFlagsWithPrefix(tt.prefix, fs)
+
+ // Verify flags are registered
+ assert.NotNil(t, fs.Lookup(tt.prefix+".enable"))
+ assert.NotNil(t, fs.Lookup(tt.prefix+".url"))
+ assert.NotNil(t, fs.Lookup(tt.prefix+".log-table-name"))
+ assert.NotNil(t, fs.Lookup(tt.prefix+".clean-period"))
+ })
+ }
+}
+
+func TestDoFence_AllPhases(t *testing.T) {
+ log.Init()
+
+ tests := []struct {
+ name string
+ fencePhase enum.FencePhase
+ xid string
+ txName string
+ wantErr bool
+ errMsg string
+ }{
+ {
+ name: "fence phase not exist",
+ fencePhase: enum.FencePhaseNotExist,
+ xid: "test-xid-001",
+ txName: "test-tx",
+ wantErr: true,
+ errMsg: "xid test-xid-001, tx name test-tx, fence
phase not exist",
+ },
+ {
+ name: "illegal fence phase",
+ fencePhase: enum.FencePhase(99),
+ xid: "test-xid-002",
+ txName: "test-tx",
+ wantErr: true,
+ errMsg: "fence phase: 99 illegal",
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ db, mock, err := sqlmock.New()
+ assert.NoError(t, err)
+ defer db.Close()
+
+ mock.ExpectBegin()
+ tx, err := db.Begin()
+ assert.NoError(t, err)
+
+ ctx := context.Background()
+ ctx = tm.InitSeataContext(ctx)
+ tm.SetXID(ctx, tt.xid)
+ tm.SetTxName(ctx, tt.txName)
+ tm.SetFencePhase(ctx, tt.fencePhase)
+
+ err = DoFence(ctx, tx)
+
+ if tt.wantErr {
+ assert.Error(t, err)
+ assert.Contains(t, err.Error(), tt.errMsg)
+ } else {
+ assert.NoError(t, err)
+ }
+ })
+ }
+}
+
+func TestWithFence_CallbackError(t *testing.T) {
+ log.Init()
+
+ db, mock, err := sqlmock.New()
+ assert.NoError(t, err)
+ defer db.Close()
+
+ mock.ExpectBegin()
+ tx, err := db.Begin()
+ assert.NoError(t, err)
+
+ ctx := context.Background()
+ ctx = tm.InitSeataContext(ctx)
+ tm.SetXID(ctx, "test-xid")
+ tm.SetTxName(ctx, "test-tx")
+ tm.SetFencePhase(ctx, enum.FencePhaseNotExist)
+
+ callbackErr := errors.New("business logic error")
+ callback := func() error {
+ return callbackErr
+ }
+
+ err = WithFence(ctx, tx, callback)
+ assert.Error(t, err)
+}
+
+// Mock implementations for driver interfaces
+type mockConn struct {
+ driver.Conn
+ prepareFunc func(query string) (driver.Stmt, error)
+ beginTxFunc func(ctx context.Context, opts driver.TxOptions)
(driver.Tx, error)
+ closeFunc func() error
+ resetSessionFunc func(ctx context.Context) error
+}
+
+func (m *mockConn) Prepare(query string) (driver.Stmt, error) {
+ if m.prepareFunc != nil {
+ return m.prepareFunc(query)
+ }
+ return nil, nil
+}
+
+func (m *mockConn) BeginTx(ctx context.Context, opts driver.TxOptions)
(driver.Tx, error) {
+ if m.beginTxFunc != nil {
+ return m.beginTxFunc(ctx, opts)
+ }
+ return nil, errors.New("not implemented")
+}
+
+func (m *mockConn) Close() error {
+ if m.closeFunc != nil {
+ return m.closeFunc()
+ }
+ return nil
+}
+
+func (m *mockConn) ResetSession(ctx context.Context) error {
+ if m.resetSessionFunc != nil {
+ return m.resetSessionFunc(ctx)
+ }
+ return driver.ErrSkip
+}
+
+// mockConnWithExec implements driver.Execer
+type mockConnWithExec struct {
+ *mockConn
+ execFunc func(query string, args []driver.Value) (driver.Result, error)
+}
+
+func (m *mockConnWithExec) Exec(query string, args []driver.Value)
(driver.Result, error) {
+ if m.execFunc != nil {
+ return m.execFunc(query, args)
+ }
+ return nil, driver.ErrSkip
+}
+
+// mockConnWithExecContext implements driver.ExecerContext
+type mockConnWithExecContext struct {
+ *mockConn
+ execContextFunc func(ctx context.Context, query string, args
[]driver.NamedValue) (driver.Result, error)
+}
+
+func (m *mockConnWithExecContext) ExecContext(ctx context.Context, query
string, args []driver.NamedValue) (driver.Result, error) {
+ if m.execContextFunc != nil {
+ return m.execContextFunc(ctx, query, args)
+ }
+ return nil, driver.ErrSkip
+}
+
+// mockConnWithQuery implements driver.Queryer
+type mockConnWithQuery struct {
+ *mockConn
+ queryFunc func(query string, args []driver.Value) (driver.Rows, error)
+}
+
+func (m *mockConnWithQuery) Query(query string, args []driver.Value)
(driver.Rows, error) {
+ if m.queryFunc != nil {
+ return m.queryFunc(query, args)
+ }
+ return nil, driver.ErrSkip
+}
+
+// mockConnWithQueryContext implements driver.QueryerContext
+type mockConnWithQueryContext struct {
+ *mockConn
+ queryContextFunc func(ctx context.Context, query string, args
[]driver.NamedValue) (driver.Rows, error)
+}
+
+func (m *mockConnWithQueryContext) QueryContext(ctx context.Context, query
string, args []driver.NamedValue) (driver.Rows, error) {
+ if m.queryContextFunc != nil {
+ return m.queryContextFunc(ctx, query, args)
+ }
+ return nil, driver.ErrSkip
+}
+
+// mockConnWithResetSession implements driver.SessionResetter
+type mockConnWithResetSession struct {
+ *mockConn
+ resetFunc func(ctx context.Context) error
+}
+
+func (m *mockConnWithResetSession) ResetSession(ctx context.Context) error {
+ if m.resetFunc != nil {
+ return m.resetFunc(ctx)
+ }
+ return nil
+}
+
+type mockTx struct {
+ driver.Tx
+ commitFunc func() error
+ rollbackFunc func() error
+}
+
+func (m *mockTx) Commit() error {
+ if m.commitFunc != nil {
+ return m.commitFunc()
+ }
+ return nil
+}
+
+func (m *mockTx) Rollback() error {
+ if m.rollbackFunc != nil {
+ return m.rollbackFunc()
+ }
+ return nil
+}
+
+type mockResult struct {
+ driver.Result
+}
+
+func (m *mockResult) LastInsertId() (int64, error) {
+ return 0, nil
+}
+
+func (m *mockResult) RowsAffected() (int64, error) {
+ return 1, nil
+}
+
+func TestFenceTx_Commit(t *testing.T) {
+ log.Init()
+
+ tests := []struct {
+ name string
+ targetTx *mockTx
+ wantErr bool
+ setupContext func() context.Context
+ }{
+ {
+ name: "commit success",
+ targetTx: &mockTx{
+ commitFunc: func() error {
+ return nil
+ },
+ },
+ wantErr: false,
+ setupContext: func() context.Context {
+ ctx := context.Background()
+ ctx = tm.InitSeataContext(ctx)
+ tm.SetFenceTxBeginedFlag(ctx, true)
+ return ctx
+ },
+ },
+ {
+ name: "commit with target tx error",
+ targetTx: &mockTx{
+ commitFunc: func() error {
+ return errors.New("commit failed")
+ },
+ },
+ wantErr: true,
+ setupContext: func() context.Context {
+ ctx := context.Background()
+ ctx = tm.InitSeataContext(ctx)
+ return ctx
+ },
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ db, mock, err := sqlmock.New()
+ assert.NoError(t, err)
+ defer db.Close()
+
+ mock.ExpectBegin()
+ fenceTx, err := db.Begin()
+ assert.NoError(t, err)
+
+ if !tt.wantErr {
+ mock.ExpectCommit()
+ }
+
+ ctx := tt.setupContext()
+ tx := &FenceTx{
+ Ctx: ctx,
+ TargetTx: tt.targetTx,
+ TargetFenceTx: fenceTx,
+ }
+
+ err = tx.Commit()
+
+ if tt.wantErr {
+ assert.Error(t, err)
+ } else {
+ assert.NoError(t, err)
+ // Verify fence tx flag is cleared
+ assert.False(t, tm.IsFenceTxBegin(ctx))
+ }
+ })
+ }
+}
+
+func TestFenceTx_Rollback(t *testing.T) {
+ log.Init()
+
+ tests := []struct {
+ name string
+ targetTx *mockTx
+ wantErr bool
+ setupContext func() context.Context
+ }{
+ {
+ name: "rollback success",
+ targetTx: &mockTx{
+ rollbackFunc: func() error {
+ return nil
+ },
+ },
+ wantErr: false,
+ setupContext: func() context.Context {
+ ctx := context.Background()
+ ctx = tm.InitSeataContext(ctx)
+ tm.SetFenceTxBeginedFlag(ctx, true)
+ return ctx
+ },
+ },
+ {
+ name: "rollback with target tx error",
+ targetTx: &mockTx{
+ rollbackFunc: func() error {
+ return errors.New("rollback failed")
+ },
+ },
+ wantErr: true,
+ setupContext: func() context.Context {
+ ctx := context.Background()
+ ctx = tm.InitSeataContext(ctx)
+ return ctx
+ },
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ db, mock, err := sqlmock.New()
+ assert.NoError(t, err)
+ defer db.Close()
+
+ mock.ExpectBegin()
+ fenceTx, err := db.Begin()
+ assert.NoError(t, err)
+
+ if !tt.wantErr {
+ mock.ExpectRollback()
+ }
+
+ ctx := tt.setupContext()
+ tx := &FenceTx{
+ Ctx: ctx,
+ TargetTx: tt.targetTx,
+ TargetFenceTx: fenceTx,
+ }
+
+ err = tx.Rollback()
+
+ if tt.wantErr {
+ assert.Error(t, err)
+ } else {
+ assert.NoError(t, err)
+ // Verify fence tx flag is cleared
+ assert.False(t, tm.IsFenceTxBegin(ctx))
+ }
+ })
+ }
+}
+
+func TestSeataFenceConnector_Connect(t *testing.T) {
+ mockConn := &mockConn{}
+ mockConnector := &mockConnector{
+ connectFunc: func(ctx context.Context) (driver.Conn, error) {
+ return mockConn, nil
+ },
+ }
+
+ connector := &SeataFenceConnector{
+ TargetConnector: mockConnector,
+ TargetDB: &sql.DB{},
+ }
+
+ conn, err := connector.Connect(context.Background())
+ assert.NoError(t, err)
+ assert.NotNil(t, conn)
+
+ fenceConn, ok := conn.(*FenceConn)
+ assert.True(t, ok)
+ assert.Equal(t, mockConn, fenceConn.TargetConn)
+}
+
+func TestSeataFenceConnector_Driver(t *testing.T) {
+ mockDriver := &FenceDriver{}
+ mockConnector := &mockConnector{
+ driverFunc: func() driver.Driver {
+ return mockDriver
+ },
+ }
+
+ connector := &SeataFenceConnector{
+ TargetConnector: mockConnector,
+ }
+
+ driver := connector.Driver()
+ assert.Equal(t, mockDriver, driver)
+}
+
+type mockConnector struct {
+ connectFunc func(ctx context.Context) (driver.Conn, error)
+ driverFunc func() driver.Driver
+}
+
+func (m *mockConnector) Connect(ctx context.Context) (driver.Conn, error) {
+ if m.connectFunc != nil {
+ return m.connectFunc(ctx)
+ }
+ return nil, nil
+}
+
+func (m *mockConnector) Driver() driver.Driver {
+ if m.driverFunc != nil {
+ return m.driverFunc()
+ }
+ return nil
+}
+
+func TestDoFence_PreparePhase(t *testing.T) {
+ log.Init()
+
+ db, mock, err :=
sqlmock.New(sqlmock.QueryMatcherOption(sqlmock.QueryMatcherEqual))
+ assert.NoError(t, err)
+ defer db.Close()
+
+ mock.ExpectBegin()
+ mock.ExpectPrepare("insert into tcc_fence_log (xid, branch_id,
action_name, status, gmt_create, gmt_modified) values ( ?,?,?,?,?,?)").
+ ExpectExec().
+ WithArgs(sqlmock.AnyArg(), sqlmock.AnyArg(), sqlmock.AnyArg(),
sqlmock.AnyArg(), sqlmock.AnyArg(), sqlmock.AnyArg()).
+ WillReturnResult(sqlmock.NewResult(1, 1))
+ mock.ExpectCommit()
+
+ tx, err := db.BeginTx(context.Background(), &sql.TxOptions{})
+ assert.NoError(t, err)
+
+ ctx := context.Background()
+ ctx = tm.InitSeataContext(ctx)
+ tm.SetXID(ctx, "test-xid-prepare")
+ tm.SetTxName(ctx, "test-tx")
+ tm.SetFencePhase(ctx, enum.FencePhasePrepare)
+ bac := &tm.BusinessActionContext{
+ Xid: "test-xid-prepare",
+ BranchId: 123,
+ ActionName: "test-action",
+ }
+ tm.SetBusinessActionContext(ctx, bac)
+
+ err = DoFence(ctx, tx)
+ assert.NoError(t, err)
+
+ tx.Commit()
+ assert.NoError(t, mock.ExpectationsWereMet())
+}
+
+func TestDoFence_CommitPhase(t *testing.T) {
+ log.Init()
+
+ db, mock, err :=
sqlmock.New(sqlmock.QueryMatcherOption(sqlmock.QueryMatcherEqual))
+ assert.NoError(t, err)
+ defer db.Close()
+
+ now := time.Now()
+ mock.ExpectBegin()
+ mock.ExpectPrepare("select xid, branch_id, action_name, status,
gmt_create, gmt_modified from tcc_fence_log where xid = ? and branch_id = ?
for update").
+ ExpectQuery().
+ WithArgs("test-xid-commit", int64(456)).
+ WillReturnRows(sqlmock.NewRows([]string{"xid", "branch_id",
"action_name", "status", "gmt_create", "gmt_modified"}).
+ AddRow("test-xid-commit", int64(456), "test-action",
enum.StatusTried, now, now))
+ mock.ExpectPrepare("update tcc_fence_log set status = ?, gmt_modified
= ? where xid = ? and branch_id = ? and status = ? ").
+ ExpectExec().
+ WithArgs(sqlmock.AnyArg(), sqlmock.AnyArg(), sqlmock.AnyArg(),
sqlmock.AnyArg(), sqlmock.AnyArg()).
+ WillReturnResult(sqlmock.NewResult(1, 1))
+ mock.ExpectCommit()
+
+ tx, err := db.BeginTx(context.Background(), &sql.TxOptions{})
+ assert.NoError(t, err)
+
+ ctx := context.Background()
+ ctx = tm.InitSeataContext(ctx)
+ tm.SetXID(ctx, "test-xid-commit")
+ tm.SetTxName(ctx, "test-tx")
+ tm.SetFencePhase(ctx, enum.FencePhaseCommit)
+ bac := &tm.BusinessActionContext{
+ Xid: "test-xid-commit",
+ BranchId: 456,
+ ActionName: "test-action",
+ }
+ tm.SetBusinessActionContext(ctx, bac)
+
+ err = DoFence(ctx, tx)
+ assert.NoError(t, err)
+
+ tx.Commit()
+ assert.NoError(t, mock.ExpectationsWereMet())
+}
+
+func TestDoFence_RollbackPhase(t *testing.T) {
+ log.Init()
+
+ db, mock, err :=
sqlmock.New(sqlmock.QueryMatcherOption(sqlmock.QueryMatcherEqual))
+ assert.NoError(t, err)
+ defer db.Close()
+
+ now := time.Now()
+ mock.ExpectBegin()
+ mock.ExpectPrepare("select xid, branch_id, action_name, status,
gmt_create, gmt_modified from tcc_fence_log where xid = ? and branch_id = ?
for update").
+ ExpectQuery().
+ WithArgs("test-xid-rollback", int64(789)).
+ WillReturnRows(sqlmock.NewRows([]string{"xid", "branch_id",
"action_name", "status", "gmt_create", "gmt_modified"}).
+ AddRow("test-xid-rollback", int64(789), "test-action",
enum.StatusTried, now, now))
+ mock.ExpectPrepare("update tcc_fence_log set status = ?, gmt_modified
= ? where xid = ? and branch_id = ? and status = ? ").
+ ExpectExec().
+ WithArgs(sqlmock.AnyArg(), sqlmock.AnyArg(), sqlmock.AnyArg(),
sqlmock.AnyArg(), sqlmock.AnyArg()).
+ WillReturnResult(sqlmock.NewResult(1, 1))
+ mock.ExpectCommit()
+
+ tx, err := db.BeginTx(context.Background(), &sql.TxOptions{})
+ assert.NoError(t, err)
+
+ ctx := context.Background()
+ ctx = tm.InitSeataContext(ctx)
+ tm.SetXID(ctx, "test-xid-rollback")
+ tm.SetTxName(ctx, "test-tx")
+ tm.SetFencePhase(ctx, enum.FencePhaseRollback)
+ bac := &tm.BusinessActionContext{
+ Xid: "test-xid-rollback",
+ BranchId: 789,
+ ActionName: "test-action",
+ }
+ tm.SetBusinessActionContext(ctx, bac)
+
+ err = DoFence(ctx, tx)
+ assert.NoError(t, err)
+
+ tx.Commit()
+ assert.NoError(t, mock.ExpectationsWereMet())
+}
+
+func TestWithFence_CallbackSuccess(t *testing.T) {
+ log.Init()
+
+ db, mock, err :=
sqlmock.New(sqlmock.QueryMatcherOption(sqlmock.QueryMatcherEqual))
+ assert.NoError(t, err)
+ defer db.Close()
+
+ mock.ExpectBegin()
+ mock.ExpectPrepare("insert into tcc_fence_log (xid, branch_id,
action_name, status, gmt_create, gmt_modified) values ( ?,?,?,?,?,?)").
+ ExpectExec().
+ WithArgs(sqlmock.AnyArg(), sqlmock.AnyArg(), sqlmock.AnyArg(),
sqlmock.AnyArg(), sqlmock.AnyArg(), sqlmock.AnyArg()).
+ WillReturnResult(sqlmock.NewResult(1, 1))
+ mock.ExpectCommit()
+
+ tx, err := db.BeginTx(context.Background(), &sql.TxOptions{})
+ assert.NoError(t, err)
+
+ ctx := context.Background()
+ ctx = tm.InitSeataContext(ctx)
+ tm.SetXID(ctx, "test-xid-success")
+ tm.SetTxName(ctx, "test-tx")
+ tm.SetFencePhase(ctx, enum.FencePhasePrepare)
+ bac := &tm.BusinessActionContext{
+ Xid: "test-xid-success",
+ BranchId: 999,
+ ActionName: "test-action",
+ }
+ tm.SetBusinessActionContext(ctx, bac)
+
+ callbackExecuted := false
+ callback := func() error {
+ callbackExecuted = true
+ return nil
+ }
+
+ err = WithFence(ctx, tx, callback)
+ assert.NoError(t, err)
+ assert.True(t, callbackExecuted)
+
+ tx.Commit()
+ assert.NoError(t, mock.ExpectationsWereMet())
+}
+
+func TestWithFence_CallbackErrorFormatting(t *testing.T) {
+ log.Init()
+
+ db, mock, err :=
sqlmock.New(sqlmock.QueryMatcherOption(sqlmock.QueryMatcherEqual))
+ assert.NoError(t, err)
+ defer db.Close()
+
+ mock.ExpectBegin()
+ mock.ExpectPrepare("insert into tcc_fence_log (xid, branch_id,
action_name, status, gmt_create, gmt_modified) values ( ?,?,?,?,?,?)").
+ ExpectExec().
+ WithArgs(sqlmock.AnyArg(), sqlmock.AnyArg(), sqlmock.AnyArg(),
sqlmock.AnyArg(), sqlmock.AnyArg(), sqlmock.AnyArg()).
+ WillReturnResult(sqlmock.NewResult(1, 1))
+ mock.ExpectCommit()
+
+ tx, err := db.BeginTx(context.Background(), &sql.TxOptions{})
+ assert.NoError(t, err)
+
+ ctx := context.Background()
+ ctx = tm.InitSeataContext(ctx)
+ tm.SetXID(ctx, "test-xid")
+ tm.SetTxName(ctx, "test-tx")
+ tm.SetFencePhase(ctx, enum.FencePhasePrepare)
+
+ bac := &tm.BusinessActionContext{
+ Xid: "test-xid",
+ BranchId: 1001,
+ ActionName: "test-action",
+ ActionContext: make(map[string]interface{}),
+ }
+ tm.SetBusinessActionContext(ctx, bac)
+
+ expectedCallbackErr := errors.New("specific business error")
+ callback := func() error {
+ return expectedCallbackErr
+ }
+
+ err = WithFence(ctx, tx, callback)
+ assert.Error(t, err)
+ assert.Contains(t, err.Error(), "the business method error msg of:")
+ assert.ErrorIs(t, err, expectedCallbackErr)
+}
+
+// Test OpenConnector with DriverContext.OpenConnector error
+func TestFenceDriver_OpenConnector_DriverContextError(t *testing.T) {
+ log.Init()
+
+ expectedErr := errors.New("open connector failed")
+ mockDriverCtx := &mockDriverContext{
+ openConnectorFunc: func(name string) (driver.Connector, error) {
+ return nil, expectedErr
+ },
+ }
+
+ fd := &FenceDriver{
+ TargetDriver: mockDriverCtx,
+ }
+
+ connector, err := fd.OpenConnector("test-dsn")
+ assert.Error(t, err)
+ assert.Equal(t, expectedErr, err)
+ assert.Nil(t, connector)
+}
+
+// Test SeataFenceConnector.Connect with error
+func TestSeataFenceConnector_Connect_Error(t *testing.T) {
+ log.Init()
+
+ expectedErr := errors.New("connect failed")
+ mockConnector := &mockConnector{
+ connectFunc: func(ctx context.Context) (driver.Conn, error) {
+ return nil, expectedErr
+ },
+ }
+
+ connector := &SeataFenceConnector{
+ TargetConnector: mockConnector,
+ TargetDB: &sql.DB{},
+ }
+
+ conn, err := connector.Connect(context.Background())
+ assert.Error(t, err)
+ assert.Equal(t, expectedErr, err)
+ assert.Nil(t, conn)
+}
+
+// Mock DriverContext for testing
+type mockDriverContext struct {
+ driver.Driver
+ openConnectorFunc func(name string) (driver.Connector, error)
+}
+
+func (m *mockDriverContext) OpenConnector(name string) (driver.Connector,
error) {
+ if m.openConnectorFunc != nil {
+ return m.openConnectorFunc(name)
+ }
+ return nil, errors.New("not implemented")
+}
+
+func (m *mockDriverContext) Open(name string) (driver.Conn, error) {
+ return nil, errors.New("not used")
+}
diff --git a/pkg/rm/tcc/fence/handler/tcc_fence_wrapper_handler_test.go
b/pkg/rm/tcc/fence/handler/tcc_fence_wrapper_handler_test.go
new file mode 100644
index 00000000..c7c397a9
--- /dev/null
+++ b/pkg/rm/tcc/fence/handler/tcc_fence_wrapper_handler_test.go
@@ -0,0 +1,1027 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package handler
+
+import (
+ "context"
+ "database/sql"
+ "errors"
+ "sync"
+ "testing"
+ "time"
+
+ sqlmock "github.com/DATA-DOG/go-sqlmock"
+ "github.com/go-sql-driver/mysql"
+ "github.com/stretchr/testify/assert"
+
+ "seata.apache.org/seata-go/pkg/rm/tcc/fence/enum"
+ "seata.apache.org/seata-go/pkg/rm/tcc/fence/store/db/model"
+ "seata.apache.org/seata-go/pkg/tm"
+ "seata.apache.org/seata-go/pkg/util/log"
+)
+
+// mockTCCFenceStore is a mock implementation of TCCFenceStore for testing
+type mockTCCFenceStore struct {
+ mu sync.RWMutex
+ insertFunc func(tx *sql.Tx, tccFenceDo
*model.TCCFenceDO) error
+ queryFunc func(tx *sql.Tx, xid string, branchId
int64) (*model.TCCFenceDO, error)
+ updateFunc func(tx *sql.Tx, xid string, branchId
int64, oldStatus enum.FenceStatus, newStatus enum.FenceStatus) error
+ deleteFunc func(tx *sql.Tx, xid string, branchId
int64) error
+ deleteMultipleFunc func(tx *sql.Tx, identity
[]model.FenceLogIdentity) error
+ deleteTCCFenceDOByMdfDateFunc func(tx *sql.Tx, datetime time.Time,
limit int32) (int64, error)
+ queryTCCFenceLogIdentityByMdDate func(tx *sql.Tx, datetime time.Time)
([]model.FenceLogIdentity, error)
+}
+
+func (m *mockTCCFenceStore) InsertTCCFenceDO(tx *sql.Tx, tccFenceDo
*model.TCCFenceDO) error {
+ m.mu.RLock()
+ defer m.mu.RUnlock()
+ if m.insertFunc != nil {
+ return m.insertFunc(tx, tccFenceDo)
+ }
+ return nil
+}
+
+func (m *mockTCCFenceStore) QueryTCCFenceDO(tx *sql.Tx, xid string, branchId
int64) (*model.TCCFenceDO, error) {
+ m.mu.RLock()
+ defer m.mu.RUnlock()
+ if m.queryFunc != nil {
+ return m.queryFunc(tx, xid, branchId)
+ }
+ return nil, nil
+}
+
+func (m *mockTCCFenceStore) UpdateTCCFenceDO(tx *sql.Tx, xid string, branchId
int64, oldStatus enum.FenceStatus, newStatus enum.FenceStatus) error {
+ m.mu.RLock()
+ defer m.mu.RUnlock()
+ if m.updateFunc != nil {
+ return m.updateFunc(tx, xid, branchId, oldStatus, newStatus)
+ }
+ return nil
+}
+
+func (m *mockTCCFenceStore) DeleteTCCFenceDO(tx *sql.Tx, xid string, branchId
int64) error {
+ m.mu.RLock()
+ defer m.mu.RUnlock()
+ if m.deleteFunc != nil {
+ return m.deleteFunc(tx, xid, branchId)
+ }
+ return nil
+}
+
+func (m *mockTCCFenceStore) DeleteMultipleTCCFenceLogIdentity(tx *sql.Tx,
identity []model.FenceLogIdentity) error {
+ m.mu.RLock()
+ defer m.mu.RUnlock()
+ if m.deleteMultipleFunc != nil {
+ return m.deleteMultipleFunc(tx, identity)
+ }
+ return nil
+}
+
+func (m *mockTCCFenceStore) DeleteTCCFenceDOByMdfDate(tx *sql.Tx, datetime
time.Time, limit int32) (int64, error) {
+ m.mu.RLock()
+ defer m.mu.RUnlock()
+ if m.deleteTCCFenceDOByMdfDateFunc != nil {
+ return m.deleteTCCFenceDOByMdfDateFunc(tx, datetime, limit)
+ }
+ return 0, nil
+}
+
+func (m *mockTCCFenceStore) QueryTCCFenceLogIdentityByMdDate(tx *sql.Tx,
datetime time.Time) ([]model.FenceLogIdentity, error) {
+ m.mu.RLock()
+ defer m.mu.RUnlock()
+ if m.queryTCCFenceLogIdentityByMdDate != nil {
+ return m.queryTCCFenceLogIdentityByMdDate(tx, datetime)
+ }
+ return nil, nil
+}
+
+func (m *mockTCCFenceStore) SetLogTableName(logTable string) {
+ // No-op for mock
+}
+
+func createTestContext(xid string, branchId int64, actionName string)
context.Context {
+ ctx := context.Background()
+ ctx = tm.InitSeataContext(ctx)
+ bac := &tm.BusinessActionContext{
+ Xid: xid,
+ BranchId: branchId,
+ ActionName: actionName,
+ }
+ tm.SetBusinessActionContext(ctx, bac)
+ return ctx
+}
+
+func TestGetFenceHandler(t *testing.T) {
+ log.Init()
+
+ handler1 := GetFenceHandler()
+ assert.NotNil(t, handler1)
+
+ handler2 := GetFenceHandler()
+ assert.Equal(t, handler1, handler2, "GetFenceHandler should return the
same instance")
+}
+
+func TestInitCleanPeriod(t *testing.T) {
+ log.Init()
+
+ handler := GetFenceHandler()
+ testDuration := 10 * time.Minute
+
+ handler.InitCleanPeriod(testDuration)
+ assert.Equal(t, testDuration, cleanInterval)
+
+ // Reset to default for other tests
+ cleanInterval = 5 * time.Minute
+}
+
+func TestPrepareFence_Success(t *testing.T) {
+ log.Init()
+
+ db, mock, err := sqlmock.New()
+ assert.NoError(t, err)
+ defer db.Close()
+
+ mock.ExpectBegin()
+ tx, err := db.Begin()
+ assert.NoError(t, err)
+
+ mockDao := &mockTCCFenceStore{
+ insertFunc: func(tx *sql.Tx, tccFenceDo *model.TCCFenceDO)
error {
+ assert.Equal(t, "test-xid", tccFenceDo.Xid)
+ assert.Equal(t, int64(123), tccFenceDo.BranchId)
+ assert.Equal(t, "test-action", tccFenceDo.ActionName)
+ assert.Equal(t, enum.StatusTried, tccFenceDo.Status)
+ return nil
+ },
+ }
+
+ handler := &tccFenceWrapperHandler{
+ tccFenceDao: mockDao,
+ }
+
+ ctx := createTestContext("test-xid", 123, "test-action")
+ err = handler.PrepareFence(ctx, tx)
+ assert.NoError(t, err)
+}
+
+func TestPrepareFence_DuplicateEntry(t *testing.T) {
+ log.Init()
+
+ db, mock, err := sqlmock.New()
+ assert.NoError(t, err)
+ defer db.Close()
+
+ mock.ExpectBegin()
+ tx, err := db.Begin()
+ assert.NoError(t, err)
+
+ mysqlErr := &mysql.MySQLError{Number: 1062, Message: "Duplicate entry"}
+ mockDao := &mockTCCFenceStore{
+ insertFunc: func(tx *sql.Tx, tccFenceDo *model.TCCFenceDO)
error {
+ return mysqlErr
+ },
+ }
+
+ handler := &tccFenceWrapperHandler{
+ tccFenceDao: mockDao,
+ logQueue: make(chan *model.FenceLogIdentity, maxQueueSize),
+ }
+
+ ctx := createTestContext("test-xid", 123, "test-action")
+ err = handler.PrepareFence(ctx, tx)
+ assert.Error(t, err)
+ assert.Contains(t, err.Error(), "insert tcc fence record errors")
+}
+
+func TestPrepareFence_InsertError(t *testing.T) {
+ log.Init()
+
+ db, mock, err := sqlmock.New()
+ assert.NoError(t, err)
+ defer db.Close()
+
+ mock.ExpectBegin()
+ tx, err := db.Begin()
+ assert.NoError(t, err)
+
+ mockDao := &mockTCCFenceStore{
+ insertFunc: func(tx *sql.Tx, tccFenceDo *model.TCCFenceDO)
error {
+ return errors.New("database error")
+ },
+ }
+
+ handler := &tccFenceWrapperHandler{
+ tccFenceDao: mockDao,
+ }
+
+ ctx := createTestContext("test-xid", 123, "test-action")
+ err = handler.PrepareFence(ctx, tx)
+ assert.Error(t, err)
+ assert.Contains(t, err.Error(), "insert tcc fence record errors")
+}
+
+func TestCommitFence_Success(t *testing.T) {
+ log.Init()
+
+ db, mock, err := sqlmock.New()
+ assert.NoError(t, err)
+ defer db.Close()
+
+ mock.ExpectBegin()
+ tx, err := db.Begin()
+ assert.NoError(t, err)
+
+ mockDao := &mockTCCFenceStore{
+ queryFunc: func(tx *sql.Tx, xid string, branchId int64)
(*model.TCCFenceDO, error) {
+ return &model.TCCFenceDO{
+ Xid: xid,
+ BranchId: branchId,
+ Status: enum.StatusTried,
+ }, nil
+ },
+ updateFunc: func(tx *sql.Tx, xid string, branchId int64,
oldStatus enum.FenceStatus, newStatus enum.FenceStatus) error {
+ assert.Equal(t, enum.StatusTried, oldStatus)
+ assert.Equal(t, enum.StatusCommitted, newStatus)
+ return nil
+ },
+ }
+
+ handler := &tccFenceWrapperHandler{
+ tccFenceDao: mockDao,
+ }
+
+ ctx := createTestContext("test-xid", 123, "test-action")
+ err = handler.CommitFence(ctx, tx)
+ assert.NoError(t, err)
+}
+
+func TestCommitFence_QueryError(t *testing.T) {
+ log.Init()
+
+ db, mock, err := sqlmock.New()
+ assert.NoError(t, err)
+ defer db.Close()
+
+ mock.ExpectBegin()
+ tx, err := db.Begin()
+ assert.NoError(t, err)
+
+ mockDao := &mockTCCFenceStore{
+ queryFunc: func(tx *sql.Tx, xid string, branchId int64)
(*model.TCCFenceDO, error) {
+ return nil, errors.New("query error")
+ },
+ }
+
+ handler := &tccFenceWrapperHandler{
+ tccFenceDao: mockDao,
+ }
+
+ ctx := createTestContext("test-xid", 123, "test-action")
+ err = handler.CommitFence(ctx, tx)
+ assert.Error(t, err)
+ assert.Contains(t, err.Error(), "commit fence method failed")
+}
+
+func TestCommitFence_RecordNotExists(t *testing.T) {
+ log.Init()
+
+ db, mock, err := sqlmock.New()
+ assert.NoError(t, err)
+ defer db.Close()
+
+ mock.ExpectBegin()
+ tx, err := db.Begin()
+ assert.NoError(t, err)
+
+ mockDao := &mockTCCFenceStore{
+ queryFunc: func(tx *sql.Tx, xid string, branchId int64)
(*model.TCCFenceDO, error) {
+ return nil, nil
+ },
+ }
+
+ handler := &tccFenceWrapperHandler{
+ tccFenceDao: mockDao,
+ }
+
+ ctx := createTestContext("test-xid", 123, "test-action")
+ err = handler.CommitFence(ctx, tx)
+ assert.Error(t, err)
+ assert.Contains(t, err.Error(), "tcc fence record not exists")
+}
+
+func TestCommitFence_AlreadyCommitted(t *testing.T) {
+ log.Init()
+
+ db, mock, err := sqlmock.New()
+ assert.NoError(t, err)
+ defer db.Close()
+
+ mock.ExpectBegin()
+ tx, err := db.Begin()
+ assert.NoError(t, err)
+
+ mockDao := &mockTCCFenceStore{
+ queryFunc: func(tx *sql.Tx, xid string, branchId int64)
(*model.TCCFenceDO, error) {
+ return &model.TCCFenceDO{
+ Xid: xid,
+ BranchId: branchId,
+ Status: enum.StatusCommitted,
+ }, nil
+ },
+ }
+
+ handler := &tccFenceWrapperHandler{
+ tccFenceDao: mockDao,
+ }
+
+ ctx := createTestContext("test-xid", 123, "test-action")
+ err = handler.CommitFence(ctx, tx)
+ assert.NoError(t, err)
+}
+
+func TestCommitFence_UnexpectedStatus_Rollbacked(t *testing.T) {
+ log.Init()
+
+ db, mock, err := sqlmock.New()
+ assert.NoError(t, err)
+ defer db.Close()
+
+ mock.ExpectBegin()
+ tx, err := db.Begin()
+ assert.NoError(t, err)
+
+ mockDao := &mockTCCFenceStore{
+ queryFunc: func(tx *sql.Tx, xid string, branchId int64)
(*model.TCCFenceDO, error) {
+ return &model.TCCFenceDO{
+ Xid: xid,
+ BranchId: branchId,
+ Status: enum.StatusRollbacked,
+ }, nil
+ },
+ }
+
+ handler := &tccFenceWrapperHandler{
+ tccFenceDao: mockDao,
+ }
+
+ ctx := createTestContext("test-xid", 123, "test-action")
+ err = handler.CommitFence(ctx, tx)
+ assert.Error(t, err)
+ assert.Contains(t, err.Error(), "branch transaction status is
unexpected")
+}
+
+func TestCommitFence_UnexpectedStatus_Suspended(t *testing.T) {
+ log.Init()
+
+ db, mock, err := sqlmock.New()
+ assert.NoError(t, err)
+ defer db.Close()
+
+ mock.ExpectBegin()
+ tx, err := db.Begin()
+ assert.NoError(t, err)
+
+ mockDao := &mockTCCFenceStore{
+ queryFunc: func(tx *sql.Tx, xid string, branchId int64)
(*model.TCCFenceDO, error) {
+ return &model.TCCFenceDO{
+ Xid: xid,
+ BranchId: branchId,
+ Status: enum.StatusSuspended,
+ }, nil
+ },
+ }
+
+ handler := &tccFenceWrapperHandler{
+ tccFenceDao: mockDao,
+ }
+
+ ctx := createTestContext("test-xid", 123, "test-action")
+ err = handler.CommitFence(ctx, tx)
+ assert.Error(t, err)
+ assert.Contains(t, err.Error(), "branch transaction status is
unexpected")
+}
+
+func TestRollbackFence_Success(t *testing.T) {
+ log.Init()
+
+ db, mock, err := sqlmock.New()
+ assert.NoError(t, err)
+ defer db.Close()
+
+ mock.ExpectBegin()
+ tx, err := db.Begin()
+ assert.NoError(t, err)
+
+ mockDao := &mockTCCFenceStore{
+ queryFunc: func(tx *sql.Tx, xid string, branchId int64)
(*model.TCCFenceDO, error) {
+ return &model.TCCFenceDO{
+ Xid: xid,
+ BranchId: branchId,
+ Status: enum.StatusTried,
+ }, nil
+ },
+ updateFunc: func(tx *sql.Tx, xid string, branchId int64,
oldStatus enum.FenceStatus, newStatus enum.FenceStatus) error {
+ assert.Equal(t, enum.StatusTried, oldStatus)
+ assert.Equal(t, enum.StatusRollbacked, newStatus)
+ return nil
+ },
+ }
+
+ handler := &tccFenceWrapperHandler{
+ tccFenceDao: mockDao,
+ }
+
+ ctx := createTestContext("test-xid", 123, "test-action")
+ err = handler.RollbackFence(ctx, tx)
+ assert.NoError(t, err)
+}
+
+func TestRollbackFence_QueryError(t *testing.T) {
+ log.Init()
+
+ db, mock, err := sqlmock.New()
+ assert.NoError(t, err)
+ defer db.Close()
+
+ mock.ExpectBegin()
+ tx, err := db.Begin()
+ assert.NoError(t, err)
+
+ mockDao := &mockTCCFenceStore{
+ queryFunc: func(tx *sql.Tx, xid string, branchId int64)
(*model.TCCFenceDO, error) {
+ return nil, errors.New("query error")
+ },
+ }
+
+ handler := &tccFenceWrapperHandler{
+ tccFenceDao: mockDao,
+ }
+
+ ctx := createTestContext("test-xid", 123, "test-action")
+ err = handler.RollbackFence(ctx, tx)
+ assert.Error(t, err)
+ assert.Contains(t, err.Error(), "rollback fence method failed")
+}
+
+func TestRollbackFence_RecordNotExists_InsertSuspended(t *testing.T) {
+ log.Init()
+
+ db, mock, err := sqlmock.New()
+ assert.NoError(t, err)
+ defer db.Close()
+
+ mock.ExpectBegin()
+ tx, err := db.Begin()
+ assert.NoError(t, err)
+
+ mockDao := &mockTCCFenceStore{
+ queryFunc: func(tx *sql.Tx, xid string, branchId int64)
(*model.TCCFenceDO, error) {
+ return nil, nil
+ },
+ insertFunc: func(tx *sql.Tx, tccFenceDo *model.TCCFenceDO)
error {
+ assert.Equal(t, enum.StatusSuspended, tccFenceDo.Status)
+ return nil
+ },
+ }
+
+ handler := &tccFenceWrapperHandler{
+ tccFenceDao: mockDao,
+ }
+
+ ctx := createTestContext("test-xid", 123, "test-action")
+ err = handler.RollbackFence(ctx, tx)
+ assert.NoError(t, err)
+}
+
+func TestRollbackFence_RecordNotExists_InsertError(t *testing.T) {
+ log.Init()
+
+ db, mock, err := sqlmock.New()
+ assert.NoError(t, err)
+ defer db.Close()
+
+ mock.ExpectBegin()
+ tx, err := db.Begin()
+ assert.NoError(t, err)
+
+ mockDao := &mockTCCFenceStore{
+ queryFunc: func(tx *sql.Tx, xid string, branchId int64)
(*model.TCCFenceDO, error) {
+ return nil, nil
+ },
+ insertFunc: func(tx *sql.Tx, tccFenceDo *model.TCCFenceDO)
error {
+ return errors.New("insert error")
+ },
+ }
+
+ handler := &tccFenceWrapperHandler{
+ tccFenceDao: mockDao,
+ }
+
+ ctx := createTestContext("test-xid", 123, "test-action")
+ err = handler.RollbackFence(ctx, tx)
+ assert.Error(t, err)
+ assert.Contains(t, err.Error(), "insert tcc fence record errors")
+}
+
+func TestRollbackFence_AlreadyRollbacked(t *testing.T) {
+ log.Init()
+
+ db, mock, err := sqlmock.New()
+ assert.NoError(t, err)
+ defer db.Close()
+
+ mock.ExpectBegin()
+ tx, err := db.Begin()
+ assert.NoError(t, err)
+
+ mockDao := &mockTCCFenceStore{
+ queryFunc: func(tx *sql.Tx, xid string, branchId int64)
(*model.TCCFenceDO, error) {
+ return &model.TCCFenceDO{
+ Xid: xid,
+ BranchId: branchId,
+ Status: enum.StatusRollbacked,
+ }, nil
+ },
+ }
+
+ handler := &tccFenceWrapperHandler{
+ tccFenceDao: mockDao,
+ }
+
+ ctx := createTestContext("test-xid", 123, "test-action")
+ err = handler.RollbackFence(ctx, tx)
+ assert.NoError(t, err)
+}
+
+func TestRollbackFence_AlreadySuspended(t *testing.T) {
+ log.Init()
+
+ db, mock, err := sqlmock.New()
+ assert.NoError(t, err)
+ defer db.Close()
+
+ mock.ExpectBegin()
+ tx, err := db.Begin()
+ assert.NoError(t, err)
+
+ mockDao := &mockTCCFenceStore{
+ queryFunc: func(tx *sql.Tx, xid string, branchId int64)
(*model.TCCFenceDO, error) {
+ return &model.TCCFenceDO{
+ Xid: xid,
+ BranchId: branchId,
+ Status: enum.StatusSuspended,
+ }, nil
+ },
+ }
+
+ handler := &tccFenceWrapperHandler{
+ tccFenceDao: mockDao,
+ }
+
+ ctx := createTestContext("test-xid", 123, "test-action")
+ err = handler.RollbackFence(ctx, tx)
+ assert.NoError(t, err)
+}
+
+func TestRollbackFence_UnexpectedStatus_Committed(t *testing.T) {
+ log.Init()
+
+ db, mock, err := sqlmock.New()
+ assert.NoError(t, err)
+ defer db.Close()
+
+ mock.ExpectBegin()
+ tx, err := db.Begin()
+ assert.NoError(t, err)
+
+ mockDao := &mockTCCFenceStore{
+ queryFunc: func(tx *sql.Tx, xid string, branchId int64)
(*model.TCCFenceDO, error) {
+ return &model.TCCFenceDO{
+ Xid: xid,
+ BranchId: branchId,
+ Status: enum.StatusCommitted,
+ }, nil
+ },
+ }
+
+ handler := &tccFenceWrapperHandler{
+ tccFenceDao: mockDao,
+ }
+
+ ctx := createTestContext("test-xid", 123, "test-action")
+ err = handler.RollbackFence(ctx, tx)
+ assert.Error(t, err)
+ assert.Contains(t, err.Error(), "branch transaction status is
unexpected")
+}
+
+func TestInsertTCCFenceLog(t *testing.T) {
+ log.Init()
+
+ db, mock, err := sqlmock.New()
+ assert.NoError(t, err)
+ defer db.Close()
+
+ mock.ExpectBegin()
+ tx, err := db.Begin()
+ assert.NoError(t, err)
+
+ mockDao := &mockTCCFenceStore{
+ insertFunc: func(tx *sql.Tx, tccFenceDo *model.TCCFenceDO)
error {
+ assert.Equal(t, "test-xid", tccFenceDo.Xid)
+ assert.Equal(t, int64(456), tccFenceDo.BranchId)
+ assert.Equal(t, "test-action", tccFenceDo.ActionName)
+ assert.Equal(t, enum.StatusTried, tccFenceDo.Status)
+ return nil
+ },
+ }
+
+ handler := &tccFenceWrapperHandler{
+ tccFenceDao: mockDao,
+ }
+
+ err = handler.insertTCCFenceLog(tx, "test-xid", 456, "test-action",
enum.StatusTried)
+ assert.NoError(t, err)
+}
+
+func TestUpdateFenceStatus(t *testing.T) {
+ log.Init()
+
+ db, mock, err := sqlmock.New()
+ assert.NoError(t, err)
+ defer db.Close()
+
+ mock.ExpectBegin()
+ tx, err := db.Begin()
+ assert.NoError(t, err)
+
+ mockDao := &mockTCCFenceStore{
+ updateFunc: func(tx *sql.Tx, xid string, branchId int64,
oldStatus enum.FenceStatus, newStatus enum.FenceStatus) error {
+ assert.Equal(t, "test-xid", xid)
+ assert.Equal(t, int64(789), branchId)
+ assert.Equal(t, enum.StatusTried, oldStatus)
+ assert.Equal(t, enum.StatusCommitted, newStatus)
+ return nil
+ },
+ }
+
+ handler := &tccFenceWrapperHandler{
+ tccFenceDao: mockDao,
+ }
+
+ err = handler.updateFenceStatus(tx, "test-xid", 789,
enum.StatusCommitted)
+ assert.NoError(t, err)
+}
+
+func TestDeleteBatchFence_Success(t *testing.T) {
+ log.Init()
+
+ db, mock, err := sqlmock.New()
+ assert.NoError(t, err)
+ defer db.Close()
+
+ mock.ExpectBegin()
+ tx, err := db.Begin()
+ assert.NoError(t, err)
+
+ batch := []model.FenceLogIdentity{
+ {Xid: "xid1", BranchId: 1},
+ {Xid: "xid2", BranchId: 2},
+ }
+
+ mockDao := &mockTCCFenceStore{
+ deleteMultipleFunc: func(tx *sql.Tx, identity
[]model.FenceLogIdentity) error {
+ assert.Equal(t, batch, identity)
+ return nil
+ },
+ }
+
+ handler := &tccFenceWrapperHandler{
+ tccFenceDao: mockDao,
+ }
+
+ err = handler.deleteBatchFence(tx, batch)
+ assert.NoError(t, err)
+}
+
+func TestDeleteBatchFence_Error(t *testing.T) {
+ log.Init()
+
+ db, mock, err := sqlmock.New()
+ assert.NoError(t, err)
+ defer db.Close()
+
+ mock.ExpectBegin()
+ tx, err := db.Begin()
+ assert.NoError(t, err)
+
+ batch := []model.FenceLogIdentity{
+ {Xid: "xid1", BranchId: 1},
+ }
+
+ mockDao := &mockTCCFenceStore{
+ deleteMultipleFunc: func(tx *sql.Tx, identity
[]model.FenceLogIdentity) error {
+ return errors.New("delete error")
+ },
+ }
+
+ handler := &tccFenceWrapperHandler{
+ tccFenceDao: mockDao,
+ }
+
+ err = handler.deleteBatchFence(tx, batch)
+ assert.Error(t, err)
+ assert.Contains(t, err.Error(), "delete batch fence log failed")
+}
+
+func TestPushCleanChannel(t *testing.T) {
+ log.Init()
+
+ handler := &tccFenceWrapperHandler{
+ logQueue: make(chan *model.FenceLogIdentity, maxQueueSize),
+ }
+
+ handler.pushCleanChannel("test-xid", 123)
+
+ select {
+ case fli := <-handler.logQueue:
+ assert.Equal(t, "test-xid", fli.Xid)
+ assert.Equal(t, int64(123), fli.BranchId)
+ case <-time.After(1 * time.Second):
+ t.Fatal("Expected to receive from logQueue")
+ }
+}
+
+func TestPushCleanChannel_FullQueue(t *testing.T) {
+ log.Init()
+
+ handler := &tccFenceWrapperHandler{
+ logQueue: make(chan *model.FenceLogIdentity, 1),
+ }
+
+ // Fill the queue
+ handler.pushCleanChannel("xid1", 1)
+
+ // This should add to cache instead
+ handler.pushCleanChannel("xid2", 2)
+
+ // Verify queue has first item
+ select {
+ case fli := <-handler.logQueue:
+ assert.Equal(t, "xid1", fli.Xid)
+ default:
+ t.Fatal("Expected queue to have item")
+ }
+
+ // Verify cache has second item
+ assert.Equal(t, 1, handler.logCache.Len())
+}
+
+func TestDestroyLogCleanChannel(t *testing.T) {
+ log.Init()
+
+ db, mock, err := sqlmock.New()
+ assert.NoError(t, err)
+
+ mock.ExpectClose()
+
+ handler := &tccFenceWrapperHandler{
+ logQueue: make(chan *model.FenceLogIdentity, maxQueueSize),
+ db: db,
+ }
+
+ handler.DestroyLogCleanChannel()
+
+ // Verify channel is closed
+ _, ok := <-handler.logQueue
+ assert.False(t, ok, "logQueue should be closed")
+
+ // Verify db is nil
+ assert.Nil(t, handler.db)
+}
+
+func TestDestroyLogCleanChannel_OnlyOnce(t *testing.T) {
+ log.Init()
+
+ handler := &tccFenceWrapperHandler{
+ logQueue: make(chan *model.FenceLogIdentity, maxQueueSize),
+ }
+
+ // Call multiple times
+ handler.DestroyLogCleanChannel()
+ handler.DestroyLogCleanChannel()
+ handler.DestroyLogCleanChannel()
+
+ // Should only close once without panic
+ assert.Nil(t, handler.db)
+}
+
+func TestTraversalCleanChannel(t *testing.T) {
+ log.Init()
+
+ db, mock, err := sqlmock.New()
+ assert.NoError(t, err)
+ defer db.Close()
+
+ // Expect Begin and Commit for batch delete
+ mock.ExpectBegin()
+ mock.ExpectCommit()
+
+ mockDao := &mockTCCFenceStore{
+ deleteMultipleFunc: func(tx *sql.Tx, identity
[]model.FenceLogIdentity) error {
+ // Verify we're deleting the expected batch
+ assert.Equal(t, channelDelete, len(identity))
+ return nil
+ },
+ }
+
+ handler := &tccFenceWrapperHandler{
+ tccFenceDao: mockDao,
+ logQueue: make(chan *model.FenceLogIdentity, maxQueueSize),
+ }
+
+ // Start the goroutine
+ go handler.traversalCleanChannel(db)
+
+ // Push exactly channelDelete items to trigger batch delete
+ for i := 0; i < channelDelete; i++ {
+ handler.logQueue <- &model.FenceLogIdentity{
+ Xid: "test-xid",
+ BranchId: int64(i),
+ }
+ }
+
+ // Give it time to process
+ time.Sleep(100 * time.Millisecond)
+
+ // Close the channel to stop the goroutine
+ close(handler.logQueue)
+
+ // Give it time to finish
+ time.Sleep(100 * time.Millisecond)
+
+ // Verify expectations
+ assert.NoError(t, mock.ExpectationsWereMet())
+}
+
+func TestTraversalCleanChannel_RemainingBatch(t *testing.T) {
+ log.Init()
+
+ db, mock, err := sqlmock.New()
+ assert.NoError(t, err)
+ defer db.Close()
+
+ // Expect Begin and Commit for final batch
+ mock.ExpectBegin()
+ mock.ExpectCommit()
+
+ mockDao := &mockTCCFenceStore{
+ deleteMultipleFunc: func(tx *sql.Tx, identity
[]model.FenceLogIdentity) error {
+ // Verify we're deleting the remaining items
+ assert.Equal(t, 3, len(identity))
+ return nil
+ },
+ }
+
+ handler := &tccFenceWrapperHandler{
+ tccFenceDao: mockDao,
+ logQueue: make(chan *model.FenceLogIdentity, maxQueueSize),
+ }
+
+ // Start the goroutine
+ go handler.traversalCleanChannel(db)
+
+ // Push less than channelDelete items
+ for i := 0; i < 3; i++ {
+ handler.logQueue <- &model.FenceLogIdentity{
+ Xid: "test-xid",
+ BranchId: int64(i),
+ }
+ }
+
+ // Give it time to process
+ time.Sleep(100 * time.Millisecond)
+
+ // Close the channel to stop the goroutine and trigger final batch
+ close(handler.logQueue)
+
+ // Give it time to finish
+ time.Sleep(100 * time.Millisecond)
+
+ // Verify expectations
+ assert.NoError(t, mock.ExpectationsWereMet())
+}
+
+func TestTraversalCleanChannel_DeleteError(t *testing.T) {
+ log.Init()
+
+ db, mock, err := sqlmock.New()
+ assert.NoError(t, err)
+ defer db.Close()
+
+ // Expect Begin but delete will fail
+ mock.ExpectBegin()
+
+ mockDao := &mockTCCFenceStore{
+ deleteMultipleFunc: func(tx *sql.Tx, identity
[]model.FenceLogIdentity) error {
+ return errors.New("delete failed")
+ },
+ }
+
+ handler := &tccFenceWrapperHandler{
+ tccFenceDao: mockDao,
+ logQueue: make(chan *model.FenceLogIdentity, maxQueueSize),
+ }
+
+ // Start the goroutine
+ go handler.traversalCleanChannel(db)
+
+ // Push channelDelete items to trigger batch delete
+ for i := 0; i < channelDelete; i++ {
+ handler.logQueue <- &model.FenceLogIdentity{
+ Xid: "test-xid",
+ BranchId: int64(i),
+ }
+ }
+
+ // Give it time to process
+ time.Sleep(100 * time.Millisecond)
+
+ // Close the channel
+ close(handler.logQueue)
+
+ // Give it time to finish
+ time.Sleep(100 * time.Millisecond)
+}
+
+func TestInitLogCleanChannel(t *testing.T) {
+ log.Init()
+
+ // Create a test DSN for sqlmock
+ db, mock, err := sqlmock.New(sqlmock.MonitorPingsOption(true))
+ assert.NoError(t, err)
+ defer db.Close()
+
+ // We can't easily test the actual goroutines, but we can verify
+ // the function doesn't panic and initializes the handler
+ handler := &tccFenceWrapperHandler{
+ tccFenceDao: &mockTCCFenceStore{},
+ }
+
+ // Test with invalid DSN (won't connect but won't panic)
+ handler.InitLogCleanChannel("invalid-dsn")
+
+ // Verify db was attempted to be set
+ handler.dbMutex.RLock()
+ dbSet := handler.db != nil
+ handler.dbMutex.RUnlock()
+
+ // With invalid DSN, db should still be nil
+ assert.False(t, dbSet)
+
+ // Clean up
+ if err := mock.ExpectationsWereMet(); err != nil {
+ // It's okay if expectations aren't met with invalid DSN
+ t.Logf("Expected behavior with invalid DSN: %v", err)
+ }
+}
+
+func TestInitLogCleanChannel_EmptyDSN(t *testing.T) {
+ log.Init()
+
+ handler := &tccFenceWrapperHandler{
+ tccFenceDao: &mockTCCFenceStore{},
+ }
+
+ // Test with empty DSN - sql.Open still returns a non-nil DB even with
empty DSN
+ // but it may not be usable
+ handler.InitLogCleanChannel("")
+
+ // Verify db was attempted to be set (sql.Open returns non-nil DB even
with empty DSN)
+ handler.dbMutex.RLock()
+ defer handler.dbMutex.RUnlock()
+ // DB object is created but may not be functional
+ assert.NotNil(t, handler.db)
+}
+
+func TestConstants(t *testing.T) {
+ // Verify constants are set as expected
+ assert.Equal(t, 500, maxQueueSize)
+ assert.Equal(t, 5, channelDelete)
+ assert.Equal(t, 24*time.Hour, cleanExpired)
+ assert.Equal(t, 5*time.Minute, cleanInterval)
+}
diff --git a/pkg/rm/tcc/fence/store/db/sql/tcc_fence_store_sql_test.go
b/pkg/rm/tcc/fence/store/db/sql/tcc_fence_store_sql_test.go
new file mode 100644
index 00000000..8fe0d2cc
--- /dev/null
+++ b/pkg/rm/tcc/fence/store/db/sql/tcc_fence_store_sql_test.go
@@ -0,0 +1,409 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package sql
+
+import (
+ "strconv"
+ "strings"
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+
+ "seata.apache.org/seata-go/pkg/rm/tcc/fence/enum"
+)
+
+func TestGetInsertLocalTCCLogSQL(t *testing.T) {
+ tests := []struct {
+ name string
+ localTccTable string
+ expectedResult string
+ }{
+ {
+ name: "standard table name",
+ localTccTable: "tcc_fence_log",
+ expectedResult: "insert into tcc_fence_log (xid,
branch_id, action_name, status, gmt_create, gmt_modified) values (
?,?,?,?,?,?)",
+ },
+ {
+ name: "custom table name",
+ localTccTable: "my_custom_tcc_log",
+ expectedResult: "insert into my_custom_tcc_log (xid,
branch_id, action_name, status, gmt_create, gmt_modified) values (
?,?,?,?,?,?)",
+ },
+ {
+ name: "table name with schema",
+ localTccTable: "schema.tcc_log",
+ expectedResult: "insert into schema.tcc_log (xid,
branch_id, action_name, status, gmt_create, gmt_modified) values (
?,?,?,?,?,?)",
+ },
+ {
+ name: "empty table name",
+ localTccTable: "",
+ expectedResult: "insert into (xid, branch_id,
action_name, status, gmt_create, gmt_modified) values ( ?,?,?,?,?,?)",
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ result := GetInsertLocalTCCLogSQL(tt.localTccTable)
+ assert.Equal(t, tt.expectedResult, result)
+ // Verify the SQL contains the required placeholders
+ assert.Contains(t, result, "insert into")
+ assert.Contains(t, result, "xid, branch_id,
action_name, status, gmt_create, gmt_modified")
+ assert.Contains(t, result, "?,?,?,?,?,?")
+ })
+ }
+}
+
+func TestGetQuerySQLByBranchIdAndXid(t *testing.T) {
+ tests := []struct {
+ name string
+ localTccTable string
+ expectedResult string
+ }{
+ {
+ name: "standard table name",
+ localTccTable: "tcc_fence_log",
+ expectedResult: "select xid, branch_id, action_name,
status, gmt_create, gmt_modified from tcc_fence_log where xid = ? and
branch_id = ? for update",
+ },
+ {
+ name: "custom table name",
+ localTccTable: "my_tcc_table",
+ expectedResult: "select xid, branch_id, action_name,
status, gmt_create, gmt_modified from my_tcc_table where xid = ? and
branch_id = ? for update",
+ },
+ {
+ name: "empty table name",
+ localTccTable: "",
+ expectedResult: "select xid, branch_id, action_name,
status, gmt_create, gmt_modified from where xid = ? and branch_id = ? for
update",
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ result := GetQuerySQLByBranchIdAndXid(tt.localTccTable)
+ assert.Equal(t, tt.expectedResult, result)
+ // Verify the SQL structure
+ assert.Contains(t, result, "select")
+ assert.Contains(t, result, "where xid = ? and branch_id
= ?")
+ assert.Contains(t, result, "for update")
+ })
+ }
+}
+
+func TestGetUpdateStatusSQLByBranchIdAndXid(t *testing.T) {
+ tests := []struct {
+ name string
+ localTccTable string
+ expectedResult string
+ }{
+ {
+ name: "standard table name",
+ localTccTable: "tcc_fence_log",
+ expectedResult: "update tcc_fence_log set status = ?,
gmt_modified = ? where xid = ? and branch_id = ? and status = ? ",
+ },
+ {
+ name: "custom table name",
+ localTccTable: "custom_fence",
+ expectedResult: "update custom_fence set status = ?,
gmt_modified = ? where xid = ? and branch_id = ? and status = ? ",
+ },
+ {
+ name: "empty table name",
+ localTccTable: "",
+ expectedResult: "update set status = ?, gmt_modified
= ? where xid = ? and branch_id = ? and status = ? ",
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ result :=
GetUpdateStatusSQLByBranchIdAndXid(tt.localTccTable)
+ assert.Equal(t, tt.expectedResult, result)
+ // Verify the SQL structure
+ assert.Contains(t, result, "update")
+ assert.Contains(t, result, "set status = ?,
gmt_modified = ?")
+ assert.Contains(t, result, "where xid = ? and
branch_id = ? and status = ?")
+ // Verify the correct number of placeholders (5 total)
+ assert.Equal(t, 5, strings.Count(result, "?"))
+ })
+ }
+}
+
+func TestGetDeleteSQLByBranchIdAndXid(t *testing.T) {
+ tests := []struct {
+ name string
+ localTccTable string
+ expectedResult string
+ }{
+ {
+ name: "standard table name",
+ localTccTable: "tcc_fence_log",
+ expectedResult: "delete from tcc_fence_log where xid
= ? and branch_id = ? ",
+ },
+ {
+ name: "custom table name",
+ localTccTable: "my_table",
+ expectedResult: "delete from my_table where xid = ?
and branch_id = ? ",
+ },
+ {
+ name: "empty table name",
+ localTccTable: "",
+ expectedResult: "delete from where xid = ? and
branch_id = ? ",
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ result := GetDeleteSQLByBranchIdAndXid(tt.localTccTable)
+ assert.Equal(t, tt.expectedResult, result)
+ // Verify the SQL structure
+ assert.Contains(t, result, "delete from")
+ assert.Contains(t, result, "where xid = ? and
branch_id = ?")
+ // Verify the correct number of placeholders (2 total)
+ assert.Equal(t, 2, strings.Count(result, "?"))
+ })
+ }
+}
+
+func TestGertDeleteSQLByBranchIdsAndXids(t *testing.T) {
+ tests := []struct {
+ name string
+ localTccTable string
+ paramsPlaceHolder string
+ expectedResult string
+ }{
+ {
+ name: "single pair of parameters",
+ localTccTable: "tcc_fence_log",
+ paramsPlaceHolder: "(?,?)",
+ expectedResult: "delete from tcc_fence_log where
(xid,branch_id) in ((?,?) )",
+ },
+ {
+ name: "multiple pairs of parameters",
+ localTccTable: "tcc_fence_log",
+ paramsPlaceHolder: "(?,?),(?,?),(?,?)",
+ expectedResult: "delete from tcc_fence_log where
(xid,branch_id) in ((?,?),(?,?),(?,?) )",
+ },
+ {
+ name: "custom table with multiple
parameters",
+ localTccTable: "custom_log",
+ paramsPlaceHolder: "(?,?),(?,?)",
+ expectedResult: "delete from custom_log where
(xid,branch_id) in ((?,?),(?,?) )",
+ },
+ {
+ name: "empty table name",
+ localTccTable: "",
+ paramsPlaceHolder: "(?,?)",
+ expectedResult: "delete from where
(xid,branch_id) in ((?,?) )",
+ },
+ {
+ name: "empty placeholder",
+ localTccTable: "tcc_fence_log",
+ paramsPlaceHolder: "",
+ expectedResult: "delete from tcc_fence_log where
(xid,branch_id) in ( )",
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ result :=
GertDeleteSQLByBranchIdsAndXids(tt.localTccTable, tt.paramsPlaceHolder)
+ assert.Equal(t, tt.expectedResult, result)
+ // Verify the SQL structure
+ assert.Contains(t, result, "delete from")
+ assert.Contains(t, result, "where (xid,branch_id) in")
+ })
+ }
+}
+
+func TestGetDeleteSQLByMdfDateAndStatus(t *testing.T) {
+ // Pre-calculate the expected status values
+ statusCommitted := strconv.Itoa(int(enum.StatusCommitted))
+ statusRollbacked := strconv.Itoa(int(enum.StatusRollbacked))
+ statusSuspended := strconv.Itoa(int(enum.StatusSuspended))
+
+ tests := []struct {
+ name string
+ localTccTable string
+ expectedResult string
+ }{
+ {
+ name: "standard table name",
+ localTccTable: "tcc_fence_log",
+ expectedResult: "delete from tcc_fence_log where
gmt_modified < ? and status in (" + statusCommitted + " , " + statusRollbacked
+ " , " + statusSuspended + ") limit ?",
+ },
+ {
+ name: "custom table name",
+ localTccTable: "my_custom_table",
+ expectedResult: "delete from my_custom_table where
gmt_modified < ? and status in (" + statusCommitted + " , " + statusRollbacked
+ " , " + statusSuspended + ") limit ?",
+ },
+ {
+ name: "empty table name",
+ localTccTable: "",
+ expectedResult: "delete from where gmt_modified < ?
and status in (" + statusCommitted + " , " + statusRollbacked + " , " +
statusSuspended + ") limit ?",
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ result :=
GetDeleteSQLByMdfDateAndStatus(tt.localTccTable)
+ assert.Equal(t, tt.expectedResult, result)
+ // Verify the SQL structure
+ assert.Contains(t, result, "delete from")
+ assert.Contains(t, result, "where gmt_modified < ?")
+ assert.Contains(t, result, "status in")
+ assert.Contains(t, result, "limit ?")
+ // Verify it contains the status values
+ assert.Contains(t, result, statusCommitted)
+ assert.Contains(t, result, statusRollbacked)
+ assert.Contains(t, result, statusSuspended)
+ // Verify the correct number of placeholders (2 total:
date and limit)
+ assert.Equal(t, 2, strings.Count(result, "?"))
+ })
+ }
+}
+
+func TestGetQuerySQLByMdDate(t *testing.T) {
+ // Pre-calculate the expected status values
+ statusCommitted := strconv.Itoa(int(enum.StatusCommitted))
+ statusRollbacked := strconv.Itoa(int(enum.StatusRollbacked))
+ statusSuspended := strconv.Itoa(int(enum.StatusSuspended))
+
+ tests := []struct {
+ name string
+ localTccTable string
+ expectedResult string
+ }{
+ {
+ name: "standard table name",
+ localTccTable: "tcc_fence_log",
+ expectedResult: "select xid, branch_id from
tcc_fence_log where gmt_modified < ? and status in (" + statusCommitted + " ,
" + statusRollbacked + " , " + statusSuspended + ")",
+ },
+ {
+ name: "custom table name",
+ localTccTable: "my_log_table",
+ expectedResult: "select xid, branch_id from
my_log_table where gmt_modified < ? and status in (" + statusCommitted + " ,
" + statusRollbacked + " , " + statusSuspended + ")",
+ },
+ {
+ name: "empty table name",
+ localTccTable: "",
+ expectedResult: "select xid, branch_id from where
gmt_modified < ? and status in (" + statusCommitted + " , " + statusRollbacked
+ " , " + statusSuspended + ")",
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ result := GetQuerySQLByMdDate(tt.localTccTable)
+ assert.Equal(t, tt.expectedResult, result)
+ // Verify the SQL structure
+ assert.Contains(t, result, "select xid, branch_id from")
+ assert.Contains(t, result, "where gmt_modified < ?")
+ assert.Contains(t, result, "status in")
+ // Verify it contains the status values
+ assert.Contains(t, result, statusCommitted)
+ assert.Contains(t, result, statusRollbacked)
+ assert.Contains(t, result, statusSuspended)
+ // Verify the correct number of placeholders (1 total:
date)
+ assert.Equal(t, 1, strings.Count(result, "?"))
+ })
+ }
+}
+
+func TestSQLTemplateConsistency(t *testing.T) {
+ // Test that SQL templates properly handle table name substitution
+ tableName := "test_table"
+
+ t.Run("all functions return non-empty strings", func(t *testing.T) {
+ assert.NotEmpty(t, GetInsertLocalTCCLogSQL(tableName))
+ assert.NotEmpty(t, GetQuerySQLByBranchIdAndXid(tableName))
+ assert.NotEmpty(t,
GetUpdateStatusSQLByBranchIdAndXid(tableName))
+ assert.NotEmpty(t, GetDeleteSQLByBranchIdAndXid(tableName))
+ assert.NotEmpty(t, GertDeleteSQLByBranchIdsAndXids(tableName,
"(?,?)"))
+ assert.NotEmpty(t, GetDeleteSQLByMdfDateAndStatus(tableName))
+ assert.NotEmpty(t, GetQuerySQLByMdDate(tableName))
+ })
+
+ t.Run("all functions properly inject table name", func(t *testing.T) {
+ assert.Contains(t, GetInsertLocalTCCLogSQL(tableName),
tableName)
+ assert.Contains(t, GetQuerySQLByBranchIdAndXid(tableName),
tableName)
+ assert.Contains(t,
GetUpdateStatusSQLByBranchIdAndXid(tableName), tableName)
+ assert.Contains(t, GetDeleteSQLByBranchIdAndXid(tableName),
tableName)
+ assert.Contains(t, GertDeleteSQLByBranchIdsAndXids(tableName,
"(?,?)"), tableName)
+ assert.Contains(t, GetDeleteSQLByMdfDateAndStatus(tableName),
tableName)
+ assert.Contains(t, GetQuerySQLByMdDate(tableName), tableName)
+ })
+}
+
+func TestStatusEnumValues(t *testing.T) {
+ // Verify that the status enum values used in SQL generation are correct
+ t.Run("status values are as expected", func(t *testing.T) {
+ assert.Equal(t, enum.FenceStatus(1), enum.StatusTried)
+ assert.Equal(t, enum.FenceStatus(2), enum.StatusCommitted)
+ assert.Equal(t, enum.FenceStatus(3), enum.StatusRollbacked)
+ assert.Equal(t, enum.FenceStatus(4), enum.StatusSuspended)
+ })
+
+ t.Run("status values in SQL match enum", func(t *testing.T) {
+ sql := GetDeleteSQLByMdfDateAndStatus("test_table")
+
+ // Verify the SQL contains the correct status values
+ assert.Contains(t, sql, strconv.Itoa(int(enum.StatusCommitted)))
+ assert.Contains(t, sql,
strconv.Itoa(int(enum.StatusRollbacked)))
+ assert.Contains(t, sql, strconv.Itoa(int(enum.StatusSuspended)))
+
+ // Verify StatusTried is NOT in the cleanup SQL (as it's an
active status)
+ assert.NotContains(t, sql, strconv.Itoa(int(enum.StatusTried)))
+ })
+}
+
+func TestSQLInjectionProtection(t *testing.T) {
+ // Test that the functions handle potentially malicious input
+ // Note: The actual protection comes from using prepared statements
with ?
+ // These tests verify the SQL structure remains valid even with unusual
table names
+
+ tests := []struct {
+ name string
+ tableName string
+ }{
+ {
+ name: "table name with quotes",
+ tableName: "table'name",
+ },
+ {
+ name: "table name with semicolon",
+ tableName: "table;name",
+ },
+ {
+ name: "table name with dashes",
+ tableName: "table-name-test",
+ },
+ {
+ name: "table name with underscores",
+ tableName: "table_name_test",
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ // All functions should handle these inputs without
panic
+ assert.NotPanics(t, func() {
+ GetInsertLocalTCCLogSQL(tt.tableName)
+ GetQuerySQLByBranchIdAndXid(tt.tableName)
+ GetUpdateStatusSQLByBranchIdAndXid(tt.tableName)
+ GetDeleteSQLByBranchIdAndXid(tt.tableName)
+ GertDeleteSQLByBranchIdsAndXids(tt.tableName,
"(?,?)")
+ GetDeleteSQLByMdfDateAndStatus(tt.tableName)
+ GetQuerySQLByMdDate(tt.tableName)
+ })
+ })
+ }
+}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]