| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485 |
- package pub
- import (
- "context"
- "errors"
- "testing"
- permModel "perms-system-server/internal/model/perm"
- productModel "perms-system-server/internal/model/product"
- "perms-system-server/internal/testutil/mocks"
- "perms-system-server/internal/types"
- "github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/require"
- "github.com/zeromicro/go-zero/core/stores/sqlx"
- "go.uber.org/mock/gomock"
- "golang.org/x/crypto/bcrypt"
- )
- // TC-0048: 事务保护 —— BatchUpdate 失败时,service 必须回滚整个事务并对外返回
- // SyncPermsError{500, "同步权限事务失败"}(不得泄漏内部 DB 驱动错误)。
- //
- // 旧版本使用已废弃的 FindMapByProductCode(非事务版)做 mock;H-3 修复后读/锁都必须
- // 落在同一个 tx 里,这里按新契约重写 mock:LockByCodeTx → FindMapByProductCodeWithTx →
- // BatchInsertWithTx OK → BatchUpdateWithTx 报错 → 统一 500。
- func TestSyncPerms_Mock_TransactionRollbackOnBatchUpdateFail(t *testing.T) {
- ctrl := gomock.NewController(t)
- defer ctrl.Finish()
- dbErr := errors.New("batch update failed")
- hashedSecret, err := bcrypt.GenerateFromPassword([]byte("test_app_secret"), bcrypt.MinCost)
- require.NoError(t, err)
- mockProduct := mocks.NewMockSysProductModel(ctrl)
- mockProduct.EXPECT().FindOneByAppKey(gomock.Any(), "test_app_key").
- Return(&productModel.SysProduct{
- Id: 1,
- Code: "test_product",
- AppKey: "test_app_key",
- AppSecret: string(hashedSecret),
- Status: 1,
- }, nil)
- // H-3:tx 内必须先 LockByCodeTx 锁 product 行,再 FindMapByProductCodeWithTx。
- mockProduct.EXPECT().LockByCodeTx(gomock.Any(), gomock.Any(), "test_product").
- Return(&productModel.SysProduct{Id: 1, Code: "test_product"}, nil)
- mockPerm := mocks.NewMockSysPermModel(ctrl)
- mockPerm.EXPECT().FindMapByProductCodeWithTx(gomock.Any(), gomock.Any(), "test_product").
- Return(map[string]*permModel.SysPerm{
- "existing_code": {
- Id: 10, ProductCode: "test_product", Code: "existing_code",
- Name: "Old Name", Remark: "old remark", Status: 1,
- },
- }, nil)
- mockPerm.EXPECT().TransactCtx(gomock.Any(), gomock.Any()).
- DoAndReturn(func(ctx context.Context, fn func(context.Context, sqlx.Session) error) error {
- return fn(ctx, nil)
- })
- mockPerm.EXPECT().BatchInsertWithTx(gomock.Any(), nil, gomock.Any()).Return(nil)
- mockPerm.EXPECT().BatchUpdateWithTx(gomock.Any(), nil, gomock.Any()).Return(dbErr)
- svcCtx := mocks.NewMockServiceContext(mocks.MockModels{
- Product: mockProduct,
- Perm: mockPerm,
- })
- logic := NewSyncPermsLogic(context.Background(), svcCtx)
- resp, err := logic.SyncPerms(&types.SyncPermsReq{
- AppKey: "test_app_key",
- AppSecret: "test_app_secret",
- Perms: []types.SyncPermItem{
- {Code: "new_code", Name: "New Perm"},
- {Code: "existing_code", Name: "Updated Name", Remark: "new remark"},
- },
- })
- assert.Nil(t, resp)
- require.Error(t, err)
- // H-3 后的统一错误文案;原 DB 驱动错误必须被吞掉,避免泄漏内部实现。
- assert.Contains(t, err.Error(), "同步权限事务失败")
- assert.NotContains(t, err.Error(), "batch update failed",
- "内部 DB 错误不得透传到客户端")
- }
|