package role import ( "errors" "testing" "time" "perms-system-server/internal/consts" roleModel "perms-system-server/internal/model/role" "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" "go.uber.org/mock/gomock" ) // TC-0120: 正常更新 func TestUpdateRole_Normal(t *testing.T) { ctx := ctxhelper.SuperAdminCtx() svcCtx := svc.NewServiceContext(testutil.GetTestConfig()) conn := testutil.GetTestSqlConn() now := time.Now().Unix() pc := testutil.UniqueId() res, 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, _ := res.LastInsertId() t.Cleanup(func() { testutil.CleanTable(ctx, conn, "`sys_role`", roleId) }) newName := testutil.UniqueId() logic := NewUpdateRoleLogic(ctx, svcCtx) err = logic.UpdateRole(&types.UpdateRoleReq{ Id: roleId, Name: newName, Remark: "updated remark", PermsLevel: 2, Status: 2, }) require.NoError(t, err) updated, err := svcCtx.SysRoleModel.FindOne(ctx, roleId) require.NoError(t, err) assert.Equal(t, newName, updated.Name) assert.Equal(t, "updated remark", updated.Remark) assert.Equal(t, int64(2), updated.PermsLevel) assert.Equal(t, int64(2), updated.Status) } // TC-0121: 不存在 func TestUpdateRole_NotFound(t *testing.T) { ctx := ctxhelper.SuperAdminCtx() svcCtx := svc.NewServiceContext(testutil.GetTestConfig()) logic := NewUpdateRoleLogic(ctx, svcCtx) err := logic.UpdateRole(&types.UpdateRoleReq{ Id: 999999999, Name: "whatever", PermsLevel: 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-0539: updateRole非管理员拒绝 func TestUpdateRole_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 := NewUpdateRoleLogic(ctx, svcCtx) err = logic.UpdateRole(&types.UpdateRoleReq{Id: roleId, Name: "test", PermsLevel: 1}) require.Error(t, err) var ce *response.CodeError require.True(t, errors.As(err, &ce)) assert.Equal(t, 403, ce.Code()) } // 覆盖目标:事务已 COMMIT 成功后, // 任何缓存清理路径的失败只应记 Errorf,不得把 degraded 成功映射成 5xx 让客户端误触发重试。 // adminCtx helper 定义于 bindRolePermsLogic_test.go (同 package role)。 // TC-1119: L-R14-1 非超管 UpdateRole 访问别产品的 roleId 必须返回 404 "角色不存在", // 与 RoleDetail 的 M-N3 口径一致,消除 404 vs 403 的跨产品 roleId 枚举 oracle。 func TestUpdateRole_L_R14_1_CrossProductReturns404(t *testing.T) { ctx := ctxhelper.SuperAdminCtx() svcCtx := svc.NewServiceContext(testutil.GetTestConfig()) conn := testutil.GetTestSqlConn() now := time.Now().Unix() otherProduct := "l_r14_1_upd_" + testutil.UniqueId() res, err := svcCtx.SysRoleModel.Insert(ctx, &roleModel.SysRole{ ProductCode: otherProduct, Name: testutil.UniqueId(), Status: 1, PermsLevel: 1, CreateTime: now, UpdateTime: now, }) require.NoError(t, err) roleId, _ := res.LastInsertId() t.Cleanup(func() { testutil.CleanTable(ctx, conn, "`sys_role`", roleId) }) adminCtx := ctxhelper.AdminCtx("test_product") err = NewUpdateRoleLogic(adminCtx, svcCtx).UpdateRole(&types.UpdateRoleReq{ Id: roleId, Name: "should_not_update", PermsLevel: 1, }) require.Error(t, err) var ce *response.CodeError require.True(t, errors.As(err, &ce)) assert.Equal(t, 404, ce.Code(), "L-R14-1:跨产品 roleId 必须 404,不得以 403 暴露存在性") assert.Equal(t, "角色不存在", ce.Error(), "L-R14-1:文案必须与 'id 不存在' 完全一致,彻底消除枚举 oracle") // DB 不得被污染 after, err := svcCtx.SysRoleModel.FindOne(ctx, roleId) require.NoError(t, err) assert.NotEqual(t, "should_not_update", after.Name, "跨产品被拒绝的请求不得对 DB 产生任何副作用") } // 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, "UpdateRole 已提交成功,post-commit 缓存失败只记日志,handler 必须返回 nil") }