createRoleLogic_test.go 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253
  1. package role
  2. import (
  3. "context"
  4. "errors"
  5. "sync"
  6. "testing"
  7. "time"
  8. productModel "perms-system-server/internal/model/product"
  9. roleModel "perms-system-server/internal/model/role"
  10. "perms-system-server/internal/response"
  11. "perms-system-server/internal/svc"
  12. "perms-system-server/internal/testutil"
  13. "perms-system-server/internal/testutil/ctxhelper"
  14. "perms-system-server/internal/types"
  15. "github.com/stretchr/testify/assert"
  16. "github.com/stretchr/testify/require"
  17. )
  18. func mustInsertEnabledProduct(t *testing.T, ctx context.Context, svcCtx *svc.ServiceContext, code string) int64 {
  19. t.Helper()
  20. now := time.Now().Unix()
  21. res, err := svcCtx.SysProductModel.Insert(ctx, &productModel.SysProduct{
  22. Code: code, Name: "test_prod_" + code, AppKey: code + "_k", AppSecret: "s",
  23. Status: 1, CreateTime: now, UpdateTime: now,
  24. })
  25. require.NoError(t, err)
  26. id, err := res.LastInsertId()
  27. require.NoError(t, err)
  28. return id
  29. }
  30. // TC-0117: 正常创建
  31. func TestCreateRole_Normal(t *testing.T) {
  32. ctx := ctxhelper.SuperAdminCtx()
  33. svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
  34. conn := testutil.GetTestSqlConn()
  35. pc := testutil.UniqueId()
  36. pid := mustInsertEnabledProduct(t, ctx, svcCtx, pc)
  37. logic := NewCreateRoleLogic(ctx, svcCtx)
  38. resp, err := logic.CreateRole(&types.CreateRoleReq{
  39. ProductCode: pc,
  40. Name: testutil.UniqueId(),
  41. Remark: "test role",
  42. PermsLevel: 1,
  43. })
  44. require.NoError(t, err)
  45. assert.Greater(t, resp.Id, int64(0))
  46. t.Cleanup(func() {
  47. testutil.CleanTable(ctx, conn, "`sys_role`", resp.Id)
  48. testutil.CleanTable(ctx, conn, "`sys_product`", pid)
  49. })
  50. role, err := svcCtx.SysRoleModel.FindOne(ctx, resp.Id)
  51. require.NoError(t, err)
  52. assert.Equal(t, pc, role.ProductCode)
  53. assert.Equal(t, int64(1), role.Status)
  54. assert.Equal(t, int64(1), role.PermsLevel)
  55. }
  56. // TC-0118: 重复角色名
  57. func TestCreateRole_DuplicateName(t *testing.T) {
  58. ctx := ctxhelper.SuperAdminCtx()
  59. svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
  60. conn := testutil.GetTestSqlConn()
  61. pc := testutil.UniqueId()
  62. pid := mustInsertEnabledProduct(t, ctx, svcCtx, pc)
  63. name := testutil.UniqueId()
  64. now := time.Now().Unix()
  65. res, err := svcCtx.SysRoleModel.Insert(ctx, &roleModel.SysRole{
  66. ProductCode: pc, Name: name, Status: 1, PermsLevel: 1,
  67. CreateTime: now, UpdateTime: now,
  68. })
  69. require.NoError(t, err)
  70. existingId, _ := res.LastInsertId()
  71. t.Cleanup(func() {
  72. testutil.CleanTable(ctx, conn, "`sys_role`", existingId)
  73. testutil.CleanTable(ctx, conn, "`sys_product`", pid)
  74. })
  75. logic := NewCreateRoleLogic(ctx, svcCtx)
  76. resp, err := logic.CreateRole(&types.CreateRoleReq{
  77. ProductCode: pc,
  78. Name: name,
  79. PermsLevel: 1,
  80. })
  81. assert.Nil(t, resp)
  82. require.Error(t, err)
  83. var ce *response.CodeError
  84. require.True(t, errors.As(err, &ce))
  85. assert.Equal(t, 409, ce.Code())
  86. assert.Equal(t, "该产品下角色名已存在", ce.Error())
  87. }
  88. // TC-0119: 并发同名创建
  89. func TestCreateRole_ConcurrentSameName(t *testing.T) {
  90. ctx := ctxhelper.SuperAdminCtx()
  91. svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
  92. conn := testutil.GetTestSqlConn()
  93. pc := testutil.UniqueId()
  94. pid := mustInsertEnabledProduct(t, ctx, svcCtx, pc)
  95. name := testutil.UniqueId()
  96. t.Cleanup(func() {
  97. testutil.CleanTableByField(ctx, conn, "`sys_role`", "productCode", pc)
  98. testutil.CleanTable(ctx, conn, "`sys_product`", pid)
  99. })
  100. var wg sync.WaitGroup
  101. results := make(chan error, 2)
  102. for i := 0; i < 2; i++ {
  103. wg.Add(1)
  104. go func() {
  105. defer wg.Done()
  106. logic := NewCreateRoleLogic(ctx, svcCtx)
  107. _, err := logic.CreateRole(&types.CreateRoleReq{
  108. ProductCode: pc,
  109. Name: name,
  110. PermsLevel: 1,
  111. })
  112. results <- err
  113. }()
  114. }
  115. wg.Wait()
  116. close(results)
  117. var errs []error
  118. for err := range results {
  119. errs = append(errs, err)
  120. }
  121. require.Len(t, errs, 2)
  122. successCount := 0
  123. failCount := 0
  124. for _, err := range errs {
  125. if err == nil {
  126. successCount++
  127. } else {
  128. failCount++
  129. var codeErr *response.CodeError
  130. require.True(t, errors.As(err, &codeErr))
  131. assert.Equal(t, 409, codeErr.Code())
  132. }
  133. }
  134. assert.Equal(t, 1, successCount)
  135. assert.Equal(t, 1, failCount)
  136. }
  137. // TC-0538: createRole非管理员拒绝
  138. func TestCreateRole_MemberRejected(t *testing.T) {
  139. ctx := ctxhelper.MemberCtx("test_product")
  140. svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
  141. logic := NewCreateRoleLogic(ctx, svcCtx)
  142. _, err := logic.CreateRole(&types.CreateRoleReq{ProductCode: "test", Name: "test", PermsLevel: 1})
  143. require.Error(t, err)
  144. var ce *response.CodeError
  145. require.True(t, errors.As(err, &ce))
  146. assert.Equal(t, 403, ce.Code())
  147. }
  148. // TC-1197: H-R17-3 —— 非超管 product ADMIN 不得创建 PermsLevel=1 顶格角色。
  149. //
  150. // 核心风险:product ADMIN 可用 CreateRole(PermsLevel=1) 造出"顶格角色",再通过
  151. // BindRoles 给下属 MEMBER/DEVELOPER,绕过 GuardRoleLevelAssignable 的"同级拦截",
  152. // 等价于横向提权。GuardCreateRolePermsLevel 对 HasFullPerms 的调用者强制 reqLevel >= 2,
  153. // 把 permsLevel=1 保留给 SuperAdmin。
  154. func TestCreateRole_H_R17_3_AdminCannotCreatePermsLevel1(t *testing.T) {
  155. pc := "h_r17_3_" + testutil.UniqueId()
  156. ctx := ctxhelper.AdminCtx(pc)
  157. svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
  158. conn := testutil.GetTestSqlConn()
  159. pid := mustInsertEnabledProduct(t, ctx, svcCtx, pc)
  160. t.Cleanup(func() {
  161. testutil.CleanTableByField(ctx, conn, "`sys_role`", "productCode", pc)
  162. testutil.CleanTable(ctx, conn, "`sys_product`", pid)
  163. })
  164. _, err := NewCreateRoleLogic(ctx, svcCtx).CreateRole(&types.CreateRoleReq{
  165. ProductCode: pc,
  166. Name: "top_role_" + testutil.UniqueId(),
  167. PermsLevel: 1,
  168. })
  169. require.Error(t, err, "product ADMIN 尝试创建 PermsLevel=1 必须 403")
  170. var ce *response.CodeError
  171. require.True(t, errors.As(err, &ce))
  172. assert.Equal(t, 403, ce.Code(),
  173. "H-R17-3:非超管创建顶格角色必须 403,防止'建 R_super + BindRoles' 横向提权链路")
  174. assert.Contains(t, ce.Error(), "权限级别为 1 的顶格角色",
  175. "错误文案必须点名 PermsLevel=1 的边界条件,便于调用方一眼看出约束")
  176. }
  177. // TC-1198: H-R17-3 正向 —— product ADMIN 可创建 PermsLevel>=2 的次级角色。
  178. // 防 GuardCreateRolePermsLevel 过度收紧把合法业务路径也误伤。
  179. func TestCreateRole_H_R17_3_AdminCanCreatePermsLevel2(t *testing.T) {
  180. pc := "h_r17_3_ok_" + testutil.UniqueId()
  181. ctx := ctxhelper.AdminCtx(pc)
  182. svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
  183. conn := testutil.GetTestSqlConn()
  184. pid := mustInsertEnabledProduct(t, ctx, svcCtx, pc)
  185. resp, err := NewCreateRoleLogic(ctx, svcCtx).CreateRole(&types.CreateRoleReq{
  186. ProductCode: pc,
  187. Name: "ok_role_" + testutil.UniqueId(),
  188. PermsLevel: 2,
  189. })
  190. require.NoError(t, err)
  191. require.NotNil(t, resp)
  192. t.Cleanup(func() {
  193. testutil.CleanTable(ctx, conn, "`sys_role`", resp.Id)
  194. testutil.CleanTable(ctx, conn, "`sys_product`", pid)
  195. })
  196. role, err := svcCtx.SysRoleModel.FindOne(ctx, resp.Id)
  197. require.NoError(t, err)
  198. assert.Equal(t, int64(2), role.PermsLevel,
  199. "ADMIN 创建 PermsLevel=2 应当成功并如实落盘,作为 H-R17-3 正向基线")
  200. }
  201. // TC-1199: H-R17-3 —— SuperAdmin 继续不受 PermsLevel=1 约束。
  202. // SuperAdmin 创建 permsLevel=1 是系统默认的"顶格角色唯一合法来源",必须保持放行。
  203. func TestCreateRole_H_R17_3_SuperAdminCanCreatePermsLevel1(t *testing.T) {
  204. pc := "h_r17_3_su_" + testutil.UniqueId()
  205. ctx := ctxhelper.SuperAdminCtx()
  206. svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
  207. conn := testutil.GetTestSqlConn()
  208. pid := mustInsertEnabledProduct(t, ctx, svcCtx, pc)
  209. resp, err := NewCreateRoleLogic(ctx, svcCtx).CreateRole(&types.CreateRoleReq{
  210. ProductCode: pc,
  211. Name: "su_top_" + testutil.UniqueId(),
  212. PermsLevel: 1,
  213. })
  214. require.NoError(t, err, "SuperAdmin 必须能够创建 PermsLevel=1,否则顶格角色无合法来源")
  215. require.NotNil(t, resp)
  216. t.Cleanup(func() {
  217. testutil.CleanTable(ctx, conn, "`sys_role`", resp.Id)
  218. testutil.CleanTable(ctx, conn, "`sys_product`", pid)
  219. })
  220. role, err := svcCtx.SysRoleModel.FindOne(ctx, resp.Id)
  221. require.NoError(t, err)
  222. assert.Equal(t, int64(1), role.PermsLevel)
  223. }