package role import ( "context" "errors" "testing" "perms-system-server/internal/consts" "perms-system-server/internal/loaders" "perms-system-server/internal/middleware" roleModel "perms-system-server/internal/model/role" "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" ) // --------------------------------------------------------------------------- // 覆盖目标:审计第 6 轮 M-4 修复回归 —— 角色更新 / 角色权限绑定的 post-commit 缓存清理 // 必须是 "尽力而为":事务已 COMMIT 成功后,任何缓存清理路径的失败只应记 Errorf, // 不得把 degraded 成功映射成 5xx 让客户端误触发重试。 // // 场景:事务外 `FindUserIdsByRoleId` 返回 err。 // 期望:handler 仍返回 nil,200 OK;客户端无须重试;旧缓存最终靠 TTL 过期兜底。 // --------------------------------------------------------------------------- func adminCtx(productCode string) context.Context { return middleware.WithUserDetails(context.Background(), &loaders.UserDetails{ UserId: 1, Username: "admin", IsSuperAdmin: true, MemberType: consts.MemberTypeAdmin, Status: consts.StatusEnabled, ProductCode: productCode, }) } // TC-0858: BindRolePerms —— tx 成功、FindUserIdsByRoleId 抛 err,logic 返回 nil。 func TestBindRolePerms_PostCommitUserIdsError_StaysSuccess(t *testing.T) { ctrl := gomock.NewController(t) t.Cleanup(ctrl.Finish) roleMock := mocks.NewMockSysRoleModel(ctrl) rpMock := mocks.NewMockSysRolePermModel(ctrl) urMock := mocks.NewMockSysUserRoleModel(ctrl) roleMock.EXPECT().FindOne(gomock.Any(), int64(7)). Return(&roleModel.SysRole{Id: 7, ProductCode: "pc_m4", PermsLevel: 50, Status: 1}, nil) // permIds=[] 走 "全部删除" 路径;existingIds=[1] 需触发 tx。 rpMock.EXPECT().FindPermIdsByRoleId(gomock.Any(), int64(7)).Return([]int64{1}, nil) rpMock.EXPECT().TransactCtx(gomock.Any(), gomock.Any()). DoAndReturn(func(ctx context.Context, fn func(context.Context, sqlx.Session) error) error { return fn(ctx, nil) }) rpMock.EXPECT().DeleteByRoleIdAndPermIdsTx(gomock.Any(), nil, int64(7), []int64{1}). Return(nil) // 关键断言:post-commit FindUserIdsByRoleId 返回 err,logic 必须吞掉 err。 urMock.EXPECT().FindUserIdsByRoleId(gomock.Any(), int64(7)). Return(nil, errors.New("redis/db transient error")) svcCtx := mocks.NewMockServiceContext(mocks.MockModels{ Role: roleMock, RolePerm: rpMock, UserRole: urMock, }) err := NewBindRolePermsLogic(adminCtx("pc_m4"), svcCtx).BindRolePerms(&types.BindPermsReq{ RoleId: 7, PermIds: []int64{}, }) require.NoError(t, err, "M-4:post-commit 缓存步骤的 transient err 不应把 degraded 成功映射成 500") } // TC-0859: UpdateRole —— UpdateWithOptLock 成功,FindUserIdsByRoleId 失败,handler 返回 nil。 func TestUpdateRole_PostCommitUserIdsError_StaysSuccess(t *testing.T) { ctrl := gomock.NewController(t) t.Cleanup(ctrl.Finish) roleMock := mocks.NewMockSysRoleModel(ctrl) urMock := mocks.NewMockSysUserRoleModel(ctrl) roleMock.EXPECT().FindOne(gomock.Any(), int64(9)). Return(&roleModel.SysRole{ Id: 9, ProductCode: "pc_m4u", Name: "before", PermsLevel: 50, Status: consts.StatusEnabled, UpdateTime: 100, }, nil) // UpdateWithOptLock 成功;签名:UpdateWithOptLock(ctx, role, prevUpdateTime)。 roleMock.EXPECT().UpdateWithOptLock(gomock.Any(), gomock.Any(), int64(100)).Return(nil) // 关键断言:post-commit transient err 不应导致 handler 失败。 urMock.EXPECT().FindUserIdsByRoleId(gomock.Any(), int64(9)). Return(nil, errors.New("boom")) svcCtx := mocks.NewMockServiceContext(mocks.MockModels{ Role: roleMock, UserRole: urMock, }) err := NewUpdateRoleLogic(adminCtx("pc_m4u"), svcCtx).UpdateRole(&types.UpdateRoleReq{ Id: 9, Name: "after", Remark: "r", PermsLevel: 60, Status: 0, }) assert.NoError(t, err, "M-4:UpdateRole 已提交成功,post-commit 缓存失败只记日志,handler 必须返回 nil") }