bindRolesLogic_test.go 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578
  1. package user
  2. import (
  3. "errors"
  4. "fmt"
  5. "math"
  6. "testing"
  7. "time"
  8. "perms-system-server/internal/consts"
  9. "perms-system-server/internal/loaders"
  10. deptModel "perms-system-server/internal/model/dept"
  11. memberModel "perms-system-server/internal/model/productmember"
  12. roleModel "perms-system-server/internal/model/role"
  13. userModel "perms-system-server/internal/model/user"
  14. "perms-system-server/internal/response"
  15. "perms-system-server/internal/svc"
  16. "perms-system-server/internal/testutil"
  17. "perms-system-server/internal/testutil/ctxhelper"
  18. "perms-system-server/internal/types"
  19. "github.com/stretchr/testify/assert"
  20. "github.com/stretchr/testify/require"
  21. )
  22. func insertTestMember(t *testing.T, svcCtx *svc.ServiceContext, productCode string, userId int64) int64 {
  23. t.Helper()
  24. now := time.Now().Unix()
  25. res, err := svcCtx.SysProductMemberModel.Insert(ctxhelper.SuperAdminCtx(), &memberModel.SysProductMember{
  26. ProductCode: productCode,
  27. UserId: userId,
  28. MemberType: "MEMBER",
  29. Status: 1,
  30. CreateTime: now,
  31. UpdateTime: now,
  32. })
  33. require.NoError(t, err)
  34. id, _ := res.LastInsertId()
  35. return id
  36. }
  37. func insertTestRole(t *testing.T, svcCtx *svc.ServiceContext, productCode string, status int64) int64 {
  38. t.Helper()
  39. now := time.Now().Unix()
  40. res, err := svcCtx.SysRoleModel.Insert(ctxhelper.SuperAdminCtx(), &roleModel.SysRole{
  41. ProductCode: productCode,
  42. Name: "role_" + testutil.UniqueId(),
  43. Status: status,
  44. PermsLevel: 1,
  45. CreateTime: now,
  46. UpdateTime: now,
  47. })
  48. require.NoError(t, err)
  49. id, _ := res.LastInsertId()
  50. return id
  51. }
  52. // TC-0184: 正常绑定
  53. func TestBindRoles_Success(t *testing.T) {
  54. ctx := ctxhelper.SuperAdminCtx()
  55. svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
  56. conn := testutil.GetTestSqlConn()
  57. username := testutil.UniqueId()
  58. userId := insertTestUser(t, ctx, username, testutil.HashPassword("pass"))
  59. mId := insertTestMember(t, svcCtx, "test_product", userId)
  60. r1 := insertTestRole(t, svcCtx, "test_product", 1)
  61. r2 := insertTestRole(t, svcCtx, "test_product", 1)
  62. t.Cleanup(func() {
  63. testutil.CleanTableByField(ctx, conn, "`sys_user_role`", "userId", userId)
  64. testutil.CleanTable(ctx, conn, "`sys_product_member`", mId)
  65. testutil.CleanTable(ctx, conn, "`sys_user`", userId)
  66. testutil.CleanTable(ctx, conn, "`sys_role`", r1, r2)
  67. })
  68. logic := NewBindRolesLogic(ctx, svcCtx)
  69. err := logic.BindRoles(&types.BindRolesReq{
  70. UserId: userId,
  71. RoleIds: []int64{r1, r2},
  72. })
  73. require.NoError(t, err)
  74. roleIds, err := svcCtx.SysUserRoleModel.FindRoleIdsByUserId(ctx, userId)
  75. require.NoError(t, err)
  76. assert.ElementsMatch(t, []int64{r1, r2}, roleIds)
  77. }
  78. // TC-0185: 用户不存在
  79. func TestBindRoles_UserNotFound(t *testing.T) {
  80. ctx := ctxhelper.SuperAdminCtx()
  81. svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
  82. logic := NewBindRolesLogic(ctx, svcCtx)
  83. err := logic.BindRoles(&types.BindRolesReq{
  84. UserId: 999999999,
  85. RoleIds: []int64{1},
  86. })
  87. require.Error(t, err)
  88. var codeErr *response.CodeError
  89. require.True(t, errors.As(err, &codeErr))
  90. assert.Equal(t, 404, codeErr.Code())
  91. assert.Equal(t, "用户不存在", codeErr.Error())
  92. }
  93. // TC-0186: 清空角色
  94. func TestBindRoles_EmptyRoleIds_ClearsAll(t *testing.T) {
  95. ctx := ctxhelper.SuperAdminCtx()
  96. svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
  97. conn := testutil.GetTestSqlConn()
  98. username := testutil.UniqueId()
  99. userId := insertTestUser(t, ctx, username, testutil.HashPassword("pass"))
  100. mId := insertTestMember(t, svcCtx, "test_product", userId)
  101. r1 := insertTestRole(t, svcCtx, "test_product", 1)
  102. t.Cleanup(func() {
  103. testutil.CleanTableByField(ctx, conn, "`sys_user_role`", "userId", userId)
  104. testutil.CleanTable(ctx, conn, "`sys_product_member`", mId)
  105. testutil.CleanTable(ctx, conn, "`sys_user`", userId)
  106. testutil.CleanTable(ctx, conn, "`sys_role`", r1)
  107. })
  108. logic := NewBindRolesLogic(ctx, svcCtx)
  109. err := logic.BindRoles(&types.BindRolesReq{
  110. UserId: userId,
  111. RoleIds: []int64{r1},
  112. })
  113. require.NoError(t, err)
  114. err = logic.BindRoles(&types.BindRolesReq{
  115. UserId: userId,
  116. RoleIds: []int64{},
  117. })
  118. require.NoError(t, err)
  119. roleIds, err := svcCtx.SysUserRoleModel.FindRoleIdsByUserId(ctx, userId)
  120. require.NoError(t, err)
  121. assert.Empty(t, roleIds)
  122. }
  123. // TC-0184: 正常重新绑定
  124. func TestBindRoles_Rebind(t *testing.T) {
  125. ctx := ctxhelper.SuperAdminCtx()
  126. svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
  127. conn := testutil.GetTestSqlConn()
  128. username := testutil.UniqueId()
  129. userId := insertTestUser(t, ctx, username, testutil.HashPassword("pass"))
  130. mId := insertTestMember(t, svcCtx, "test_product", userId)
  131. r1 := insertTestRole(t, svcCtx, "test_product", 1)
  132. r2 := insertTestRole(t, svcCtx, "test_product", 1)
  133. r3 := insertTestRole(t, svcCtx, "test_product", 1)
  134. t.Cleanup(func() {
  135. testutil.CleanTableByField(ctx, conn, "`sys_user_role`", "userId", userId)
  136. testutil.CleanTable(ctx, conn, "`sys_product_member`", mId)
  137. testutil.CleanTable(ctx, conn, "`sys_user`", userId)
  138. testutil.CleanTable(ctx, conn, "`sys_role`", r1, r2, r3)
  139. })
  140. logic := NewBindRolesLogic(ctx, svcCtx)
  141. err := logic.BindRoles(&types.BindRolesReq{
  142. UserId: userId,
  143. RoleIds: []int64{r1, r2},
  144. })
  145. require.NoError(t, err)
  146. err = logic.BindRoles(&types.BindRolesReq{
  147. UserId: userId,
  148. RoleIds: []int64{r2, r3},
  149. })
  150. require.NoError(t, err)
  151. roleIds, err := svcCtx.SysUserRoleModel.FindRoleIdsByUserId(ctx, userId)
  152. require.NoError(t, err)
  153. assert.ElementsMatch(t, []int64{r2, r3}, roleIds)
  154. }
  155. // TC-0188: 角色不属于当前产品
  156. func TestBindRoles_RoleBelongsToOtherProduct(t *testing.T) {
  157. ctx := ctxhelper.SuperAdminCtx()
  158. svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
  159. conn := testutil.GetTestSqlConn()
  160. username := testutil.UniqueId()
  161. userId := insertTestUser(t, ctx, username, testutil.HashPassword("pass"))
  162. mId := insertTestMember(t, svcCtx, "test_product", userId)
  163. otherRole := insertTestRole(t, svcCtx, "other_product", 1)
  164. t.Cleanup(func() {
  165. testutil.CleanTable(ctx, conn, "`sys_product_member`", mId)
  166. testutil.CleanTable(ctx, conn, "`sys_user`", userId)
  167. testutil.CleanTable(ctx, conn, "`sys_role`", otherRole)
  168. })
  169. logic := NewBindRolesLogic(ctx, svcCtx)
  170. err := logic.BindRoles(&types.BindRolesReq{
  171. UserId: userId,
  172. RoleIds: []int64{otherRole},
  173. })
  174. require.Error(t, err)
  175. var codeErr *response.CodeError
  176. require.True(t, errors.As(err, &codeErr))
  177. assert.Equal(t, 400, codeErr.Code())
  178. assert.Contains(t, codeErr.Error(), "其他产品的角色")
  179. }
  180. // TC-0189: 角色已禁用
  181. func TestBindRoles_RoleDisabled(t *testing.T) {
  182. ctx := ctxhelper.SuperAdminCtx()
  183. svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
  184. conn := testutil.GetTestSqlConn()
  185. username := testutil.UniqueId()
  186. userId := insertTestUser(t, ctx, username, testutil.HashPassword("pass"))
  187. mId := insertTestMember(t, svcCtx, "test_product", userId)
  188. disabledRole := insertTestRole(t, svcCtx, "test_product", 2)
  189. t.Cleanup(func() {
  190. testutil.CleanTable(ctx, conn, "`sys_product_member`", mId)
  191. testutil.CleanTable(ctx, conn, "`sys_user`", userId)
  192. testutil.CleanTable(ctx, conn, "`sys_role`", disabledRole)
  193. })
  194. logic := NewBindRolesLogic(ctx, svcCtx)
  195. err := logic.BindRoles(&types.BindRolesReq{
  196. UserId: userId,
  197. RoleIds: []int64{disabledRole},
  198. })
  199. require.Error(t, err)
  200. var codeErr *response.CodeError
  201. require.True(t, errors.As(err, &codeErr))
  202. assert.Equal(t, 400, codeErr.Code())
  203. assert.Contains(t, codeErr.Error(), "已禁用的角色")
  204. }
  205. // TC-0190: 角色不存在
  206. func TestBindRoles_RoleNotExists(t *testing.T) {
  207. ctx := ctxhelper.SuperAdminCtx()
  208. svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
  209. conn := testutil.GetTestSqlConn()
  210. username := testutil.UniqueId()
  211. userId := insertTestUser(t, ctx, username, testutil.HashPassword("pass"))
  212. mId := insertTestMember(t, svcCtx, "test_product", userId)
  213. t.Cleanup(func() {
  214. testutil.CleanTable(ctx, conn, "`sys_product_member`", mId)
  215. testutil.CleanTable(ctx, conn, "`sys_user`", userId)
  216. })
  217. logic := NewBindRolesLogic(ctx, svcCtx)
  218. err := logic.BindRoles(&types.BindRolesReq{
  219. UserId: userId,
  220. RoleIds: []int64{999999999},
  221. })
  222. require.Error(t, err)
  223. var codeErr *response.CodeError
  224. require.True(t, errors.As(err, &codeErr))
  225. assert.Equal(t, 400, codeErr.Code())
  226. assert.Contains(t, codeErr.Error(), "无效的角色ID")
  227. }
  228. func insertTestRoleWithLevel(t *testing.T, svcCtx *svc.ServiceContext, productCode string, status int64, permsLevel int64) int64 {
  229. t.Helper()
  230. now := time.Now().Unix()
  231. res, err := svcCtx.SysRoleModel.Insert(ctxhelper.SuperAdminCtx(), &roleModel.SysRole{
  232. ProductCode: productCode,
  233. Name: "role_" + testutil.UniqueId(),
  234. Status: status,
  235. PermsLevel: permsLevel,
  236. CreateTime: now,
  237. UpdateTime: now,
  238. })
  239. require.NoError(t, err)
  240. id, _ := res.LastInsertId()
  241. return id
  242. }
  243. // setupDeptForCaller 插入一个 dept,同时构造 caller(使用该 dept)与 target(同 dept 下)的环境
  244. // 返回 deptId、deptPath(caller 使用)、cleanup function
  245. func setupDeptForCaller(t *testing.T, svcCtx *svc.ServiceContext) (int64, string, func()) {
  246. t.Helper()
  247. now := time.Now().Unix()
  248. superCtx := ctxhelper.SuperAdminCtx()
  249. res, err := svcCtx.SysDeptModel.Insert(superCtx, &deptModel.SysDept{
  250. Name: "dept_" + testutil.UniqueId(),
  251. ParentId: 0,
  252. Path: "/",
  253. DeptType: consts.DeptTypeNormal,
  254. Status: consts.StatusEnabled,
  255. CreateTime: now,
  256. UpdateTime: now,
  257. })
  258. require.NoError(t, err)
  259. deptId, _ := res.LastInsertId()
  260. // 先占位再用真实 deptId 构造 path:"/{deptId}/"
  261. path := fmt.Sprintf("/%d/", deptId)
  262. _, err = svcCtx.SysDeptModel.Insert(superCtx, &deptModel.SysDept{}) // noop — keep linter happy
  263. _ = err
  264. // 更新 path
  265. dept, _ := svcCtx.SysDeptModel.FindOne(superCtx, deptId)
  266. dept.Path = path
  267. dept.UpdateTime = time.Now().Unix()
  268. require.NoError(t, svcCtx.SysDeptModel.Update(superCtx, dept))
  269. conn := testutil.GetTestSqlConn()
  270. cleanup := func() {
  271. testutil.CleanTable(superCtx, conn, "`sys_dept`", deptId)
  272. }
  273. return deptId, path, cleanup
  274. }
  275. // TC-0208: MEMBER 调用者不能分配权限级别高于自身的角色 (audit H-1 修复后 permsLevel 仅对 MEMBER 生效)
  276. func TestBindRoles_PermsLevelEscalation_Rejected(t *testing.T) {
  277. svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
  278. conn := testutil.GetTestSqlConn()
  279. superCtx := ctxhelper.SuperAdminCtx()
  280. deptId, deptPath, cleanupDept := setupDeptForCaller(t, svcCtx)
  281. t.Cleanup(cleanupDept)
  282. productCode := "test_product"
  283. // 目标用户:放进 dept 下,MEMBER 产品成员
  284. username := testutil.UniqueId()
  285. targetUserId := insertTestUserFull(t, superCtx, &userModel.SysUser{
  286. Username: username, Password: testutil.HashPassword("pass"),
  287. Nickname: "tgt", DeptId: deptId,
  288. IsSuperAdmin: 2, MustChangePassword: 2, Status: 1,
  289. })
  290. mId := insertTestMember(t, svcCtx, productCode, targetUserId)
  291. highLevelRole := insertTestRoleWithLevel(t, svcCtx, productCode, 1, 1)
  292. t.Cleanup(func() {
  293. testutil.CleanTableByField(superCtx, conn, "`sys_user_role`", "userId", targetUserId)
  294. testutil.CleanTable(superCtx, conn, "`sys_product_member`", mId)
  295. testutil.CleanTable(superCtx, conn, "`sys_user`", targetUserId)
  296. testutil.CleanTable(superCtx, conn, "`sys_role`", highLevelRole)
  297. })
  298. // MEMBER 调用者与 target 同 dept,MinPermsLevel=50,目标角色 permsLevel=1 → 越级
  299. ctx := ctxhelper.CustomCtx(&loaders.UserDetails{
  300. UserId: 999998,
  301. Username: "member_caller",
  302. IsSuperAdmin: false,
  303. MemberType: consts.MemberTypeMember,
  304. Status: consts.StatusEnabled,
  305. ProductCode: productCode,
  306. DeptId: deptId,
  307. DeptPath: deptPath,
  308. MinPermsLevel: 50,
  309. })
  310. logic := NewBindRolesLogic(ctx, svcCtx)
  311. err := logic.BindRoles(&types.BindRolesReq{
  312. UserId: targetUserId,
  313. RoleIds: []int64{highLevelRole},
  314. })
  315. require.Error(t, err)
  316. var ce *response.CodeError
  317. require.True(t, errors.As(err, &ce))
  318. assert.Equal(t, 403, ce.Code())
  319. assert.Contains(t, ce.Error(), "不能分配权限级别高于自身的角色")
  320. }
  321. // TC-0711: ADMIN 调用者(MinPermsLevel=math.MaxInt64)不受 permsLevel 校验约束 (audit H-1 回归)
  322. // 修复前:ADMIN 通过 member_type 获得权限,MinPermsLevel 保持 math.MaxInt64,
  323. // r.PermsLevel < math.MaxInt64 必然成立 → ADMIN 无法绑定任何角色。
  324. // 修复后:代码显式豁免 ADMIN/DEVELOPER 的 permsLevel 校验。
  325. func TestBindRoles_AdminBypassesPermsLevelCheck(t *testing.T) {
  326. svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
  327. conn := testutil.GetTestSqlConn()
  328. superCtx := ctxhelper.SuperAdminCtx()
  329. productCode := "test_product"
  330. username := testutil.UniqueId()
  331. userId := insertTestUser(t, superCtx, username, testutil.HashPassword("pass"))
  332. mId := insertTestMember(t, svcCtx, productCode, userId)
  333. lowLevelRole := insertTestRoleWithLevel(t, svcCtx, productCode, 1, 1)
  334. t.Cleanup(func() {
  335. testutil.CleanTableByField(superCtx, conn, "`sys_user_role`", "userId", userId)
  336. testutil.CleanTable(superCtx, conn, "`sys_product_member`", mId)
  337. testutil.CleanTable(superCtx, conn, "`sys_user`", userId)
  338. testutil.CleanTable(superCtx, conn, "`sys_role`", lowLevelRole)
  339. })
  340. // 关键:模拟 loader 真实产出——ADMIN 没有自定义角色,MinPermsLevel=math.MaxInt64
  341. ctx := ctxhelper.CustomCtx(&loaders.UserDetails{
  342. UserId: 999997,
  343. Username: "admin_caller",
  344. IsSuperAdmin: false,
  345. MemberType: consts.MemberTypeAdmin,
  346. Status: consts.StatusEnabled,
  347. ProductCode: productCode,
  348. DeptId: 1,
  349. DeptPath: "/1/",
  350. MinPermsLevel: math.MaxInt64, // 默认 sentinel
  351. })
  352. logic := NewBindRolesLogic(ctx, svcCtx)
  353. err := logic.BindRoles(&types.BindRolesReq{
  354. UserId: userId,
  355. RoleIds: []int64{lowLevelRole},
  356. })
  357. require.NoError(t, err, "ADMIN 调用者应当能绑定任意级别的角色 (audit H-1)")
  358. roleIds, err := svcCtx.SysUserRoleModel.FindRoleIdsByUserId(ctx, userId)
  359. require.NoError(t, err)
  360. assert.Contains(t, roleIds, lowLevelRole)
  361. }
  362. // TC-0712: DEVELOPER 调用者同样不受 permsLevel 校验约束 (audit H-1 回归)
  363. func TestBindRoles_DeveloperBypassesPermsLevelCheck(t *testing.T) {
  364. svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
  365. conn := testutil.GetTestSqlConn()
  366. superCtx := ctxhelper.SuperAdminCtx()
  367. deptId, deptPath, cleanupDept := setupDeptForCaller(t, svcCtx)
  368. t.Cleanup(cleanupDept)
  369. productCode := "test_product"
  370. username := testutil.UniqueId()
  371. userId := insertTestUserFull(t, superCtx, &userModel.SysUser{
  372. Username: username, Password: testutil.HashPassword("pass"),
  373. Nickname: "tgt_dev", DeptId: deptId,
  374. IsSuperAdmin: 2, MustChangePassword: 2, Status: 1,
  375. })
  376. mId := insertTestMember(t, svcCtx, productCode, userId)
  377. lowLevelRole := insertTestRoleWithLevel(t, svcCtx, productCode, 1, 1)
  378. t.Cleanup(func() {
  379. testutil.CleanTableByField(superCtx, conn, "`sys_user_role`", "userId", userId)
  380. testutil.CleanTable(superCtx, conn, "`sys_product_member`", mId)
  381. testutil.CleanTable(superCtx, conn, "`sys_user`", userId)
  382. testutil.CleanTable(superCtx, conn, "`sys_role`", lowLevelRole)
  383. })
  384. ctx := ctxhelper.CustomCtx(&loaders.UserDetails{
  385. UserId: 999996,
  386. Username: "developer_caller",
  387. IsSuperAdmin: false,
  388. MemberType: consts.MemberTypeDeveloper,
  389. Status: consts.StatusEnabled,
  390. ProductCode: productCode,
  391. DeptId: deptId,
  392. DeptPath: deptPath,
  393. MinPermsLevel: math.MaxInt64,
  394. })
  395. logic := NewBindRolesLogic(ctx, svcCtx)
  396. err := logic.BindRoles(&types.BindRolesReq{
  397. UserId: userId,
  398. RoleIds: []int64{lowLevelRole},
  399. })
  400. require.NoError(t, err, "DEVELOPER 调用者应当能绑定任意级别的角色 (audit H-1)")
  401. }
  402. // TC-0713: MinPermsLevel == math.MaxInt64 的 MEMBER 调用者也必须被豁免
  403. // (sentinel 判定路径:既不是 ADMIN/DEVELOPER,也没有角色,此时 r.PermsLevel<MaxInt64 的逐字面比较
  404. // 曾经误伤此类 MEMBER;修复后代码用 MinPermsLevel==MaxInt64 做短路)
  405. func TestBindRoles_MemberWithSentinelMinLevel_NotBlocked(t *testing.T) {
  406. svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
  407. conn := testutil.GetTestSqlConn()
  408. superCtx := ctxhelper.SuperAdminCtx()
  409. productCode := "test_product"
  410. username := testutil.UniqueId()
  411. userId := insertTestUser(t, superCtx, username, testutil.HashPassword("pass"))
  412. mId := insertTestMember(t, svcCtx, productCode, userId)
  413. role := insertTestRoleWithLevel(t, svcCtx, productCode, 1, 100)
  414. t.Cleanup(func() {
  415. testutil.CleanTableByField(superCtx, conn, "`sys_user_role`", "userId", userId)
  416. testutil.CleanTable(superCtx, conn, "`sys_product_member`", mId)
  417. testutil.CleanTable(superCtx, conn, "`sys_user`", userId)
  418. testutil.CleanTable(superCtx, conn, "`sys_role`", role)
  419. })
  420. // MEMBER 调用者没有绑定任何启用角色,MinPermsLevel=MaxInt64(sentinel)
  421. // 正式语义:"我自己无级别" → 不应触发越级校验(否则所有无角色 MEMBER 都永远无法分配角色)
  422. ctx := ctxhelper.CustomCtx(&loaders.UserDetails{
  423. UserId: 999995,
  424. Username: "member_no_role",
  425. IsSuperAdmin: false,
  426. MemberType: consts.MemberTypeMember,
  427. Status: consts.StatusEnabled,
  428. ProductCode: productCode,
  429. DeptId: 1,
  430. DeptPath: "/1/",
  431. MinPermsLevel: math.MaxInt64,
  432. })
  433. logic := NewBindRolesLogic(ctx, svcCtx)
  434. // 注意:业务层早期就会用 `RequireProductAdminFor` 拦住非 ADMIN/SUPER 的调用;此处是为了单独验证
  435. // bindRoles 内部的 permsLevel 分支。实际发生于 ADMIN 通过上层校验但 MemberType 上下文异常时的防御。
  436. // 这里只断言:"sentinel 路径不应报 403 '不能分配权限级别高于自身的角色'"。
  437. err := logic.BindRoles(&types.BindRolesReq{
  438. UserId: userId,
  439. RoleIds: []int64{role},
  440. })
  441. // 调用者非 ADMIN,且是 MEMBER,上游会拦 403 "仅ADMIN/超管可绑定角色";
  442. // 此处我们只校验"即使走到 permsLevel 分支,sentinel MinPermsLevel 不应命中"
  443. if err != nil {
  444. var ce *response.CodeError
  445. require.True(t, errors.As(err, &ce))
  446. assert.NotContains(t, ce.Error(), "不能分配权限级别高于自身的角色",
  447. "sentinel MinPermsLevel=math.MaxInt64 不应触发越级错误 (audit H-1)")
  448. }
  449. }
  450. // TC-0209: 超管可以分配任意权限级别的角色
  451. func TestBindRoles_SuperAdminCanAssignAnyLevel(t *testing.T) {
  452. ctx := ctxhelper.SuperAdminCtx()
  453. svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
  454. conn := testutil.GetTestSqlConn()
  455. productCode := "test_product"
  456. username := testutil.UniqueId()
  457. userId := insertTestUser(t, ctx, username, testutil.HashPassword("pass"))
  458. mId := insertTestMember(t, svcCtx, productCode, userId)
  459. highLevelRole := insertTestRoleWithLevel(t, svcCtx, productCode, 1, 1)
  460. t.Cleanup(func() {
  461. testutil.CleanTableByField(ctx, conn, "`sys_user_role`", "userId", userId)
  462. testutil.CleanTable(ctx, conn, "`sys_product_member`", mId)
  463. testutil.CleanTable(ctx, conn, "`sys_user`", userId)
  464. testutil.CleanTable(ctx, conn, "`sys_role`", highLevelRole)
  465. })
  466. logic := NewBindRolesLogic(ctx, svcCtx)
  467. err := logic.BindRoles(&types.BindRolesReq{
  468. UserId: userId,
  469. RoleIds: []int64{highLevelRole},
  470. })
  471. require.NoError(t, err)
  472. roleIds, err := svcCtx.SysUserRoleModel.FindRoleIdsByUserId(ctx, userId)
  473. require.NoError(t, err)
  474. assert.Contains(t, roleIds, highLevelRole)
  475. }
  476. // TC-0191: 目标用户不是当前产品成员时拒绝绑定角色(L-4修复验证)
  477. func TestBindRoles_NonMemberRejected(t *testing.T) {
  478. ctx := ctxhelper.SuperAdminCtx()
  479. svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
  480. conn := testutil.GetTestSqlConn()
  481. username := testutil.UniqueId()
  482. userId := insertTestUser(t, ctx, username, testutil.HashPassword("pass"))
  483. t.Cleanup(func() { testutil.CleanTable(ctx, conn, "`sys_user`", userId) })
  484. logic := NewBindRolesLogic(ctx, svcCtx)
  485. err := logic.BindRoles(&types.BindRolesReq{
  486. UserId: userId,
  487. RoleIds: []int64{},
  488. })
  489. require.Error(t, err)
  490. var codeErr2 *response.CodeError
  491. require.True(t, errors.As(err, &codeErr2))
  492. assert.Equal(t, 400, codeErr2.Code())
  493. assert.Contains(t, codeErr2.Error(), "不是当前产品的成员")
  494. }