package exec_test import ( "errors" "testing" dbv2 "git.fsdpf.net/go/db" "git.fsdpf.net/go/db/exec" "github.com/DATA-DOG/go-sqlmock" "github.com/stretchr/testify/suite" ) // mockHooks 是 exec.Hooks 的最小实现,用来验证 UseTx/Before/After 与 db.HookError 的接线是否正确 type mockHooks struct { useTxFactory exec.QueryFactory useTxErr error beforeErr error afterErr error useTxCalls int beforeCalls int afterCalls int } func (h *mockHooks) UseTx(dataset interface{}) (exec.QueryFactory, error) { h.useTxCalls++ return h.useTxFactory, h.useTxErr } func (h *mockHooks) Before(dataset interface{}) *exec.HookError { h.beforeCalls++ if h.beforeErr != nil { return &exec.HookError{Err: h.beforeErr} } return nil } func (h *mockHooks) After(dataset interface{}, result interface{}) *exec.HookError { h.afterCalls++ if h.afterErr != nil { return &exec.HookError{Err: h.afterErr} } return nil } type hooksSuite struct { suite.Suite } // TestBeforeError:Before 失败时 SQL 不会被执行,error 能用 errors.As 解出 db.HookError func (hs *hooksSuite) TestBeforeError() { mDB, mock, err := sqlmock.New() hs.NoError(err) hooks := &mockHooks{beforeErr: errors.New("before failed")} ds := dbv2.New("mock", mDB).Insert("items"). Rows(dbv2.Record{"name": "Test1"}). WithHook(hooks) _, err = ds.Executor().Exec() hs.Error(err) var hookErr *dbv2.HookError hs.True(errors.As(err, &hookErr)) hs.Equal("before failed", hookErr.Unwrap().Error()) hs.Equal(1, hooks.beforeCalls) hs.Equal(0, hooks.afterCalls) hs.NoError(mock.ExpectationsWereMet()) } // TestAfterError:SQL 已经执行成功,After 失败时 error 依然能用 errors.As 解出 db.HookError, // 且 result 仍然可用(区别于 SQL 本身执行失败) func (hs *hooksSuite) TestAfterError() { mDB, mock, err := sqlmock.New() hs.NoError(err) mock.ExpectExec(`INSERT INTO "items"`).WillReturnResult(sqlmock.NewResult(1, 1)) hooks := &mockHooks{afterErr: errors.New("after failed")} ds := dbv2.New("mock", mDB).Insert("items"). Rows(dbv2.Record{"name": "Test1"}). WithHook(hooks) result, err := ds.Executor().Exec() hs.Error(err) hs.NotNil(result) rows, rowsErr := result.RowsAffected() hs.NoError(rowsErr) hs.EqualValues(1, rows) var hookErr *dbv2.HookError hs.True(errors.As(err, &hookErr)) hs.Equal("after failed", hookErr.Unwrap().Error()) hs.NoError(mock.ExpectationsWereMet()) } // TestUseTxSwapsConnection:UseTx 返回的 QueryFactory 会替换默认连接,SQL 应该在返回的 // factory 上执行,原始连接完全不会被用到 func (hs *hooksSuite) TestUseTxSwapsConnection() { origDB, origMock, err := sqlmock.New() hs.NoError(err) txDB, txMock, err := sqlmock.New() hs.NoError(err) txMock.ExpectExec(`INSERT INTO "items"`).WillReturnResult(sqlmock.NewResult(1, 1)) hooks := &mockHooks{useTxFactory: exec.NewQueryFactory(txDB)} ds := dbv2.New("mock", origDB).Insert("items"). Rows(dbv2.Record{"name": "Test1"}). WithHook(hooks) result, err := ds.Executor().Exec() hs.NoError(err) hs.NotNil(result) hs.Equal(1, hooks.useTxCalls) hs.NoError(txMock.ExpectationsWereMet()) hs.NoError(origMock.ExpectationsWereMet()) } // TestUseTxNil:UseTx 返回 nil factory 时沿用默认连接 func (hs *hooksSuite) TestUseTxNil() { mDB, mock, err := sqlmock.New() hs.NoError(err) mock.ExpectExec(`INSERT INTO "items"`).WillReturnResult(sqlmock.NewResult(1, 1)) hooks := &mockHooks{} ds := dbv2.New("mock", mDB).Insert("items"). Rows(dbv2.Record{"name": "Test1"}). WithHook(hooks) _, err = ds.Executor().Exec() hs.NoError(err) hs.Equal(1, hooks.useTxCalls) hs.NoError(mock.ExpectationsWereMet()) } func TestHooks(t *testing.T) { suite.Run(t, new(hooksSuite)) }