deleteRoleLogic_test.go 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216
  1. package role
  2. import (
  3. "errors"
  4. "fmt"
  5. "testing"
  6. "time"
  7. permModel "perms-system-server/internal/model/perm"
  8. roleModel "perms-system-server/internal/model/role"
  9. "perms-system-server/internal/model/roleperm"
  10. "perms-system-server/internal/model/userrole"
  11. "perms-system-server/internal/response"
  12. "perms-system-server/internal/svc"
  13. "perms-system-server/internal/testutil"
  14. "perms-system-server/internal/testutil/ctxhelper"
  15. "perms-system-server/internal/types"
  16. "github.com/stretchr/testify/assert"
  17. "github.com/stretchr/testify/require"
  18. "github.com/zeromicro/go-zero/core/stores/redis"
  19. )
  20. // TC-0126: 正常删除+级联
  21. func TestDeleteRole_WithCascading(t *testing.T) {
  22. ctx := ctxhelper.SuperAdminCtx()
  23. svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
  24. conn := testutil.GetTestSqlConn()
  25. now := time.Now().Unix()
  26. pc := testutil.UniqueId()
  27. roleRes, err := svcCtx.SysRoleModel.Insert(ctx, &roleModel.SysRole{
  28. ProductCode: pc, Name: testutil.UniqueId(), Status: 1, PermsLevel: 1,
  29. CreateTime: now, UpdateTime: now,
  30. })
  31. require.NoError(t, err)
  32. roleId, _ := roleRes.LastInsertId()
  33. pRes, err := svcCtx.SysPermModel.Insert(ctx, &permModel.SysPerm{
  34. ProductCode: pc, Name: testutil.UniqueId(), Code: testutil.UniqueId(),
  35. Status: 1, CreateTime: now, UpdateTime: now,
  36. })
  37. require.NoError(t, err)
  38. permId, _ := pRes.LastInsertId()
  39. rpRes, err := svcCtx.SysRolePermModel.Insert(ctx, &roleperm.SysRolePerm{
  40. RoleId: roleId, PermId: permId, CreateTime: now, UpdateTime: now,
  41. })
  42. require.NoError(t, err)
  43. rpId, _ := rpRes.LastInsertId()
  44. urRes, err := svcCtx.SysUserRoleModel.Insert(ctx, &userrole.SysUserRole{
  45. UserId: 888888, RoleId: roleId, CreateTime: now, UpdateTime: now,
  46. })
  47. require.NoError(t, err)
  48. urId, _ := urRes.LastInsertId()
  49. t.Cleanup(func() {
  50. testutil.CleanTable(ctx, conn, "`sys_role_perm`", rpId)
  51. testutil.CleanTable(ctx, conn, "`sys_user_role`", urId)
  52. testutil.CleanTable(ctx, conn, "`sys_role`", roleId)
  53. testutil.CleanTable(ctx, conn, "`sys_perm`", permId)
  54. })
  55. logic := NewDeleteRoleLogic(ctx, svcCtx)
  56. err = logic.DeleteRole(&types.DeleteRoleReq{Id: roleId})
  57. require.NoError(t, err)
  58. _, err = svcCtx.SysRoleModel.FindOne(ctx, roleId)
  59. assert.Error(t, err)
  60. permIds, err := svcCtx.SysRolePermModel.FindPermIdsByRoleId(ctx, roleId)
  61. require.NoError(t, err)
  62. assert.Empty(t, permIds)
  63. roleIds, err := svcCtx.SysUserRoleModel.FindRoleIdsByUserId(ctx, 888888)
  64. require.NoError(t, err)
  65. assert.NotContains(t, roleIds, roleId)
  66. }
  67. // TC-0128: 无关联数据
  68. func TestDeleteRole_NoAssociations(t *testing.T) {
  69. ctx := ctxhelper.SuperAdminCtx()
  70. svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
  71. conn := testutil.GetTestSqlConn()
  72. now := time.Now().Unix()
  73. pc := testutil.UniqueId()
  74. roleRes, err := svcCtx.SysRoleModel.Insert(ctx, &roleModel.SysRole{
  75. ProductCode: pc, Name: testutil.UniqueId(), Status: 1, PermsLevel: 1,
  76. CreateTime: now, UpdateTime: now,
  77. })
  78. require.NoError(t, err)
  79. roleId, _ := roleRes.LastInsertId()
  80. t.Cleanup(func() {
  81. testutil.CleanTable(ctx, conn, "`sys_role`", roleId)
  82. })
  83. logic := NewDeleteRoleLogic(ctx, svcCtx)
  84. err = logic.DeleteRole(&types.DeleteRoleReq{Id: roleId})
  85. require.NoError(t, err)
  86. _, err = svcCtx.SysRoleModel.FindOne(ctx, roleId)
  87. assert.Error(t, err)
  88. }
  89. // TC-1120: L-R14-1 非超管 DeleteRole 针对别产品的 roleId 必须 404 "角色不存在",
  90. // 与 RoleDetail 的 M-N3 / UpdateRole 的 TC-1119 口径一致。
  91. func TestDeleteRole_L_R14_1_CrossProductReturns404(t *testing.T) {
  92. ctx := ctxhelper.SuperAdminCtx()
  93. svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
  94. conn := testutil.GetTestSqlConn()
  95. now := time.Now().Unix()
  96. otherProduct := "l_r14_1_del_" + testutil.UniqueId()
  97. res, err := svcCtx.SysRoleModel.Insert(ctx, &roleModel.SysRole{
  98. ProductCode: otherProduct, Name: testutil.UniqueId(), Status: 1, PermsLevel: 1,
  99. CreateTime: now, UpdateTime: now,
  100. })
  101. require.NoError(t, err)
  102. roleId, _ := res.LastInsertId()
  103. t.Cleanup(func() { testutil.CleanTable(ctx, conn, "`sys_role`", roleId) })
  104. adminCtx := ctxhelper.AdminCtx("test_product")
  105. err = NewDeleteRoleLogic(adminCtx, svcCtx).DeleteRole(&types.DeleteRoleReq{Id: roleId})
  106. require.Error(t, err)
  107. var ce *response.CodeError
  108. require.True(t, errors.As(err, &ce))
  109. assert.Equal(t, 404, ce.Code(), "跨产品 roleId 必须 404")
  110. assert.Equal(t, "角色不存在", ce.Error(), "文案必须与 id 不存在完全一致")
  111. // DB 必须保持原状
  112. still, err := svcCtx.SysRoleModel.FindOne(ctx, roleId)
  113. require.NoError(t, err, "跨产品拒绝的 DeleteRole 不得真的删角色")
  114. assert.Equal(t, roleId, still.Id)
  115. }
  116. // TC-1201: H-R17-2 —— DeleteRole 成功后必须在 post-commit 上显式失效 sysRole 的两把缓存键
  117. // (id key + productCode:name key),与 DeleteDept 的 H-R17-2 同构。
  118. //
  119. // 失效窗口:DeleteWithTx 里 sqlc.CachedConn.ExecCtx 的 "exec → DelCache" 钩子先于外层
  120. // TransactCtx commit 执行。commit 前并发 FindOne / FindOneByProductCodeName 会把旧行
  121. // 回填回 Redis,commit 后 DB 没了 role 但 Redis 还挂着"幽灵 role 快照"到 TTL。叠加
  122. // 角色树权限模型,后续 ResolveOwnRoleOr404 命中幽灵 snapshot 会产生谜之 409(CAS
  123. // 命中但 DB 无行),排障代价巨大——post-commit 必须再补一次 InvalidateRoleCache。
  124. func TestDeleteRole_H_R17_2_InvalidatesCacheAfterCommit(t *testing.T) {
  125. ctx := ctxhelper.SuperAdminCtx()
  126. cfg := testutil.GetTestConfig()
  127. svcCtx := svc.NewServiceContext(cfg)
  128. conn := testutil.GetTestSqlConn()
  129. rds := redis.MustNewRedis(cfg.CacheRedis.Nodes[0].RedisConf)
  130. now := time.Now().Unix()
  131. pc := "h_r17_2_" + testutil.UniqueId()
  132. roleName := "h_r17_2_role_" + testutil.UniqueId()
  133. res, err := svcCtx.SysRoleModel.Insert(ctx, &roleModel.SysRole{
  134. ProductCode: pc, Name: roleName, Status: 1, PermsLevel: 1,
  135. CreateTime: now, UpdateTime: now,
  136. })
  137. require.NoError(t, err)
  138. roleId, _ := res.LastInsertId()
  139. t.Cleanup(func() { testutil.CleanTable(ctx, conn, "`sys_role`", roleId) })
  140. idKey := fmt.Sprintf("%s:cache:sysRole:id:%d", cfg.CacheRedis.KeyPrefix, roleId)
  141. nameKey := fmt.Sprintf("%s:cache:sysRole:productCode:name:%s:%s",
  142. cfg.CacheRedis.KeyPrefix, pc, roleName)
  143. t.Cleanup(func() { _, _ = rds.Del(idKey, nameKey) })
  144. require.NoError(t, rds.Set(idKey,
  145. fmt.Sprintf(`{"Id":%d,"ProductCode":%q,"Name":%q,"PermsLevel":1,"Status":1}`, roleId, pc, roleName)),
  146. "预置 ghost idKey 失败")
  147. require.NoError(t, rds.Set(nameKey,
  148. fmt.Sprintf(`{"Id":%d,"ProductCode":%q,"Name":%q,"PermsLevel":1,"Status":1}`, roleId, pc, roleName)),
  149. "预置 ghost nameKey 失败")
  150. require.NoError(t, NewDeleteRoleLogic(ctx, svcCtx).DeleteRole(&types.DeleteRoleReq{Id: roleId}))
  151. existsId, err := rds.Exists(idKey)
  152. require.NoError(t, err)
  153. assert.False(t, existsId,
  154. "H-R17-2:commit 之后 InvalidateRoleCache 必须 DEL sysRoleIdKey,"+
  155. "否则后续 FindOne(roleId) 命中 ghost snapshot → CAS 撞不到行回 409")
  156. existsName, err := rds.Exists(nameKey)
  157. require.NoError(t, err)
  158. assert.False(t, existsName,
  159. "H-R17-2:InvalidateRoleCache 必须同时 DEL sysRoleProductCodeNameKey,"+
  160. "因为 FindOneByProductCodeName 是 CreateRole 前的唯一性校验路径,"+
  161. "残留 ghost 会让同名 role 在删除后反复被 409 误判为冲突")
  162. }
  163. // TC-0540: deleteRole非管理员拒绝
  164. func TestDeleteRole_MemberRejected(t *testing.T) {
  165. pc := "test_product"
  166. ctx := ctxhelper.MemberCtx(pc)
  167. svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
  168. conn := testutil.GetTestSqlConn()
  169. now := time.Now().Unix()
  170. roleRes, err := svcCtx.SysRoleModel.Insert(ctx, &roleModel.SysRole{
  171. ProductCode: pc, Name: testutil.UniqueId(), Status: 1, PermsLevel: 1,
  172. CreateTime: now, UpdateTime: now,
  173. })
  174. require.NoError(t, err)
  175. roleId, _ := roleRes.LastInsertId()
  176. t.Cleanup(func() { testutil.CleanTable(ctx, conn, "`sys_role`", roleId) })
  177. logic := NewDeleteRoleLogic(ctx, svcCtx)
  178. err = logic.DeleteRole(&types.DeleteRoleReq{Id: roleId})
  179. require.Error(t, err)
  180. var ce *response.CodeError
  181. require.True(t, errors.As(err, &ce))
  182. assert.Equal(t, 403, ce.Code())
  183. }