syncPermsLogic_mock_test.go 3.2 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586
  1. package pub
  2. import (
  3. "context"
  4. "errors"
  5. "testing"
  6. permModel "perms-system-server/internal/model/perm"
  7. productModel "perms-system-server/internal/model/product"
  8. "perms-system-server/internal/testutil/mocks"
  9. "perms-system-server/internal/types"
  10. "github.com/stretchr/testify/assert"
  11. "github.com/stretchr/testify/require"
  12. "github.com/zeromicro/go-zero/core/stores/sqlx"
  13. "go.uber.org/mock/gomock"
  14. "golang.org/x/crypto/bcrypt"
  15. )
  16. // TC-0048: 事务保护 —— BatchUpdate 失败时,service 必须回滚整个事务并对外返回
  17. // SyncPermsError{500, "同步权限事务失败"}(不得泄漏内部 DB 驱动错误)。
  18. //
  19. // 旧版本使用已废弃的 FindMapByProductCode(非事务版)做 mock;H-3 修复后读/锁都必须
  20. // 落在同一个 tx 里,这里按新契约重写 mock:LockByCodeTx → FindMapByProductCodeWithTx →
  21. // BatchInsertWithTx OK → BatchUpdateWithTx 报错 → 统一 500。
  22. func TestSyncPerms_Mock_TransactionRollbackOnBatchUpdateFail(t *testing.T) {
  23. ctrl := gomock.NewController(t)
  24. defer ctrl.Finish()
  25. dbErr := errors.New("batch update failed")
  26. hashedSecret, err := bcrypt.GenerateFromPassword([]byte("test_app_secret"), bcrypt.MinCost)
  27. require.NoError(t, err)
  28. mockProduct := mocks.NewMockSysProductModel(ctrl)
  29. mockProduct.EXPECT().FindOneByAppKey(gomock.Any(), "test_app_key").
  30. Return(&productModel.SysProduct{
  31. Id: 1,
  32. Code: "test_product",
  33. AppKey: "test_app_key",
  34. AppSecret: string(hashedSecret),
  35. Status: 1,
  36. }, nil)
  37. // H-3:tx 内必须先 LockByCodeTx 锁 product 行,再 FindMapByProductCodeWithTx。
  38. // 审计 M-R10-1:LockByCodeTx 拿到的行必须 Status=1,否则直接 403 不进入 diff
  39. mockProduct.EXPECT().LockByCodeTx(gomock.Any(), gomock.Any(), "test_product").
  40. Return(&productModel.SysProduct{Id: 1, Code: "test_product", Status: 1}, nil)
  41. mockPerm := mocks.NewMockSysPermModel(ctrl)
  42. mockPerm.EXPECT().FindMapByProductCodeWithTx(gomock.Any(), gomock.Any(), "test_product").
  43. Return(map[string]*permModel.SysPerm{
  44. "existing_code": {
  45. Id: 10, ProductCode: "test_product", Code: "existing_code",
  46. Name: "Old Name", Remark: "old remark", Status: 1,
  47. },
  48. }, nil)
  49. mockPerm.EXPECT().TransactCtx(gomock.Any(), gomock.Any()).
  50. DoAndReturn(func(ctx context.Context, fn func(context.Context, sqlx.Session) error) error {
  51. return fn(ctx, nil)
  52. })
  53. mockPerm.EXPECT().BatchInsertWithTx(gomock.Any(), nil, gomock.Any()).Return(nil)
  54. mockPerm.EXPECT().BatchUpdateWithTx(gomock.Any(), nil, gomock.Any()).Return(dbErr)
  55. svcCtx := mocks.NewMockServiceContext(mocks.MockModels{
  56. Product: mockProduct,
  57. Perm: mockPerm,
  58. })
  59. logic := NewSyncPermsLogic(context.Background(), svcCtx)
  60. resp, err := logic.SyncPerms(&types.SyncPermsReq{
  61. AppKey: "test_app_key",
  62. AppSecret: "test_app_secret",
  63. Perms: []types.SyncPermItem{
  64. {Code: "new_code", Name: "New Perm"},
  65. {Code: "existing_code", Name: "Updated Name", Remark: "new remark"},
  66. },
  67. })
  68. assert.Nil(t, resp)
  69. require.Error(t, err)
  70. // H-3 后的统一错误文案;原 DB 驱动错误必须被吞掉,避免泄漏内部实现。
  71. assert.Contains(t, err.Error(), "同步权限事务失败")
  72. assert.NotContains(t, err.Error(), "batch update failed",
  73. "内部 DB 错误不得透传到客户端")
  74. }