package role import ( "context" "errors" "testing" "time" "perms-system-server/internal/consts" "perms-system-server/internal/loaders" "perms-system-server/internal/middleware" permModel "perms-system-server/internal/model/perm" roleModel "perms-system-server/internal/model/role" "perms-system-server/internal/model/roleperm" "perms-system-server/internal/response" "perms-system-server/internal/svc" "perms-system-server/internal/testutil" "perms-system-server/internal/testutil/ctxhelper" "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" ) // TC-0129: 正常绑定 func TestBindRolePerms_Normal(t *testing.T) { ctx := ctxhelper.SuperAdminCtx() svcCtx := svc.NewServiceContext(testutil.GetTestConfig()) conn := testutil.GetTestSqlConn() now := time.Now().Unix() pc := testutil.UniqueId() roleRes, err := svcCtx.SysRoleModel.Insert(ctx, &roleModel.SysRole{ ProductCode: pc, Name: testutil.UniqueId(), Status: 1, PermsLevel: 1, CreateTime: now, UpdateTime: now, }) require.NoError(t, err) roleId, _ := roleRes.LastInsertId() p1Res, err := svcCtx.SysPermModel.Insert(ctx, &permModel.SysPerm{ ProductCode: pc, Name: testutil.UniqueId(), Code: testutil.UniqueId(), Status: 1, CreateTime: now, UpdateTime: now, }) require.NoError(t, err) p1Id, _ := p1Res.LastInsertId() p2Res, err := svcCtx.SysPermModel.Insert(ctx, &permModel.SysPerm{ ProductCode: pc, Name: testutil.UniqueId(), Code: testutil.UniqueId(), Status: 1, CreateTime: now, UpdateTime: now, }) require.NoError(t, err) p2Id, _ := p2Res.LastInsertId() t.Cleanup(func() { testutil.CleanTableByField(ctx, conn, "`sys_role_perm`", "roleId", roleId) testutil.CleanTable(ctx, conn, "`sys_perm`", p1Id, p2Id) testutil.CleanTable(ctx, conn, "`sys_role`", roleId) }) logic := NewBindRolePermsLogic(ctx, svcCtx) err = logic.BindRolePerms(&types.BindPermsReq{ RoleId: roleId, PermIds: []int64{p1Id, p2Id}, }) require.NoError(t, err) permIds, err := svcCtx.SysRolePermModel.FindPermIdsByRoleId(ctx, roleId) require.NoError(t, err) assert.ElementsMatch(t, []int64{p1Id, p2Id}, permIds) } // TC-0130: 角色不存在 func TestBindRolePerms_RoleNotFound(t *testing.T) { ctx := ctxhelper.SuperAdminCtx() svcCtx := svc.NewServiceContext(testutil.GetTestConfig()) logic := NewBindRolePermsLogic(ctx, svcCtx) err := logic.BindRolePerms(&types.BindPermsReq{ RoleId: 999999999, PermIds: []int64{1}, }) require.Error(t, err) var ce *response.CodeError require.True(t, errors.As(err, &ce)) assert.Equal(t, 404, ce.Code()) assert.Equal(t, "角色不存在", ce.Error()) } // TC-0131: 清空权限 func TestBindRolePerms_EmptyPermIds(t *testing.T) { ctx := ctxhelper.SuperAdminCtx() svcCtx := svc.NewServiceContext(testutil.GetTestConfig()) conn := testutil.GetTestSqlConn() now := time.Now().Unix() pc := testutil.UniqueId() roleRes, err := svcCtx.SysRoleModel.Insert(ctx, &roleModel.SysRole{ ProductCode: pc, Name: testutil.UniqueId(), Status: 1, PermsLevel: 1, CreateTime: now, UpdateTime: now, }) require.NoError(t, err) roleId, _ := roleRes.LastInsertId() pRes, err := svcCtx.SysPermModel.Insert(ctx, &permModel.SysPerm{ ProductCode: pc, Name: testutil.UniqueId(), Code: testutil.UniqueId(), Status: 1, CreateTime: now, UpdateTime: now, }) require.NoError(t, err) pId, _ := pRes.LastInsertId() rpRes, err := svcCtx.SysRolePermModel.Insert(ctx, &roleperm.SysRolePerm{ RoleId: roleId, PermId: pId, CreateTime: now, UpdateTime: now, }) require.NoError(t, err) rpId, _ := rpRes.LastInsertId() t.Cleanup(func() { testutil.CleanTable(ctx, conn, "`sys_role_perm`", rpId) testutil.CleanTable(ctx, conn, "`sys_perm`", pId) testutil.CleanTable(ctx, conn, "`sys_role`", roleId) }) logic := NewBindRolePermsLogic(ctx, svcCtx) err = logic.BindRolePerms(&types.BindPermsReq{ RoleId: roleId, PermIds: []int64{}, }) require.NoError(t, err) permIds, err := svcCtx.SysRolePermModel.FindPermIdsByRoleId(ctx, roleId) require.NoError(t, err) assert.Empty(t, permIds) } // TC-0129: 正常绑定 func TestBindRolePerms_Rebind(t *testing.T) { ctx := ctxhelper.SuperAdminCtx() svcCtx := svc.NewServiceContext(testutil.GetTestConfig()) conn := testutil.GetTestSqlConn() now := time.Now().Unix() pc := testutil.UniqueId() roleRes, err := svcCtx.SysRoleModel.Insert(ctx, &roleModel.SysRole{ ProductCode: pc, Name: testutil.UniqueId(), Status: 1, PermsLevel: 1, CreateTime: now, UpdateTime: now, }) require.NoError(t, err) roleId, _ := roleRes.LastInsertId() var permIds []int64 for i := 0; i < 3; i++ { pRes, err := svcCtx.SysPermModel.Insert(ctx, &permModel.SysPerm{ ProductCode: pc, Name: testutil.UniqueId(), Code: testutil.UniqueId(), Status: 1, CreateTime: now, UpdateTime: now, }) require.NoError(t, err) pId, _ := pRes.LastInsertId() permIds = append(permIds, pId) } t.Cleanup(func() { testutil.CleanTableByField(ctx, conn, "`sys_role_perm`", "roleId", roleId) testutil.CleanTable(ctx, conn, "`sys_perm`", permIds...) testutil.CleanTable(ctx, conn, "`sys_role`", roleId) }) logic := NewBindRolePermsLogic(ctx, svcCtx) err = logic.BindRolePerms(&types.BindPermsReq{ RoleId: roleId, PermIds: []int64{permIds[0], permIds[1]}, }) require.NoError(t, err) got, err := svcCtx.SysRolePermModel.FindPermIdsByRoleId(ctx, roleId) require.NoError(t, err) assert.ElementsMatch(t, []int64{permIds[0], permIds[1]}, got) err = logic.BindRolePerms(&types.BindPermsReq{ RoleId: roleId, PermIds: []int64{permIds[1], permIds[2]}, }) require.NoError(t, err) got, err = svcCtx.SysRolePermModel.FindPermIdsByRoleId(ctx, roleId) require.NoError(t, err) assert.ElementsMatch(t, []int64{permIds[1], permIds[2]}, got) } // TC-0132: 重复permId — 后静默去重 func TestBindRolePerms_DuplicatePermId(t *testing.T) { ctx := ctxhelper.SuperAdminCtx() svcCtx := svc.NewServiceContext(testutil.GetTestConfig()) conn := testutil.GetTestSqlConn() now := time.Now().Unix() pc := testutil.UniqueId() roleRes, err := svcCtx.SysRoleModel.Insert(ctx, &roleModel.SysRole{ ProductCode: pc, Name: testutil.UniqueId(), Status: 1, PermsLevel: 1, CreateTime: now, UpdateTime: now, }) require.NoError(t, err) roleId, _ := roleRes.LastInsertId() pRes, err := svcCtx.SysPermModel.Insert(ctx, &permModel.SysPerm{ ProductCode: pc, Name: testutil.UniqueId(), Code: testutil.UniqueId(), Status: 1, CreateTime: now, UpdateTime: now, }) require.NoError(t, err) pId, _ := pRes.LastInsertId() t.Cleanup(func() { testutil.CleanTableByField(ctx, conn, "`sys_role_perm`", "roleId", roleId) testutil.CleanTable(ctx, conn, "`sys_perm`", pId) testutil.CleanTable(ctx, conn, "`sys_role`", roleId) }) logic := NewBindRolePermsLogic(ctx, svcCtx) err = logic.BindRolePerms(&types.BindPermsReq{ RoleId: roleId, PermIds: []int64{pId, pId}, }) require.NoError(t, err, "重复permId应被静默去重(H-5修复)") permIds, err := svcCtx.SysRolePermModel.FindPermIdsByRoleId(ctx, roleId) require.NoError(t, err) assert.Equal(t, []int64{pId}, permIds, "去重后应只绑定1个权限") } // TC-0541: bindRolePerms非管理员拒绝 func TestBindRolePerms_MemberRejected(t *testing.T) { pc := "test_product" ctx := ctxhelper.MemberCtx(pc) svcCtx := svc.NewServiceContext(testutil.GetTestConfig()) conn := testutil.GetTestSqlConn() now := time.Now().Unix() roleRes, err := svcCtx.SysRoleModel.Insert(ctx, &roleModel.SysRole{ ProductCode: pc, Name: testutil.UniqueId(), Status: 1, PermsLevel: 1, CreateTime: now, UpdateTime: now, }) require.NoError(t, err) roleId, _ := roleRes.LastInsertId() t.Cleanup(func() { testutil.CleanTable(ctx, conn, "`sys_role`", roleId) }) logic := NewBindRolePermsLogic(ctx, svcCtx) err = logic.BindRolePerms(&types.BindPermsReq{RoleId: roleId, PermIds: []int64{1}}) require.Error(t, err) var ce *response.CodeError require.True(t, errors.As(err, &ce)) assert.Equal(t, 403, ce.Code()) } // 覆盖目标:角色更新 / 角色权限绑定的 post-commit 缓存清理 // 必须是 "尽力而为":事务已 COMMIT 成功后,任何缓存清理路径的失败只应记 Errorf, // 不得把 degraded 成功映射成 5xx 让客户端误触发重试。 // adminCtx 是 post-commit 缓存降级回归用例共享的 super-admin context 构造器。 // 同 package 下 TestUpdateRole_PostCommitUserIdsError_StaysSuccess 亦通过 package 作用域复用。 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) // existing 读 + diff + delete/insert 全部收敛进事务;事务首步 LockByIdTx 锁 sys_role 行。 rpMock.EXPECT().TransactCtx(gomock.Any(), gomock.Any()). DoAndReturn(func(ctx context.Context, fn func(context.Context, sqlx.Session) error) error { return fn(ctx, nil) }) roleMock.EXPECT().LockByIdTx(gomock.Any(), nil, int64(7)). Return(&roleModel.SysRole{Id: 7, ProductCode: "pc_m4", PermsLevel: 50, Status: 1}, nil) rpMock.EXPECT().FindPermIdsByRoleIdTx(gomock.Any(), nil, int64(7)).Return([]int64{1}, 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, "post-commit 缓存步骤的 transient err 不应把 degraded 成功映射成 500") }