| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253 |
- package role
- import (
- "context"
- "errors"
- "sync"
- "testing"
- "time"
- productModel "perms-system-server/internal/model/product"
- 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/types"
- "github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/require"
- )
- func mustInsertEnabledProduct(t *testing.T, ctx context.Context, svcCtx *svc.ServiceContext, code string) int64 {
- t.Helper()
- now := time.Now().Unix()
- res, err := svcCtx.SysProductModel.Insert(ctx, &productModel.SysProduct{
- Code: code, Name: "test_prod_" + code, AppKey: code + "_k", AppSecret: "s",
- Status: 1, CreateTime: now, UpdateTime: now,
- })
- require.NoError(t, err)
- id, err := res.LastInsertId()
- require.NoError(t, err)
- return id
- }
- // TC-0117: 正常创建
- func TestCreateRole_Normal(t *testing.T) {
- ctx := ctxhelper.SuperAdminCtx()
- svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
- conn := testutil.GetTestSqlConn()
- pc := testutil.UniqueId()
- pid := mustInsertEnabledProduct(t, ctx, svcCtx, pc)
- logic := NewCreateRoleLogic(ctx, svcCtx)
- resp, err := logic.CreateRole(&types.CreateRoleReq{
- ProductCode: pc,
- Name: testutil.UniqueId(),
- Remark: "test role",
- PermsLevel: 1,
- })
- require.NoError(t, err)
- assert.Greater(t, resp.Id, int64(0))
- t.Cleanup(func() {
- testutil.CleanTable(ctx, conn, "`sys_role`", resp.Id)
- testutil.CleanTable(ctx, conn, "`sys_product`", pid)
- })
- role, err := svcCtx.SysRoleModel.FindOne(ctx, resp.Id)
- require.NoError(t, err)
- assert.Equal(t, pc, role.ProductCode)
- assert.Equal(t, int64(1), role.Status)
- assert.Equal(t, int64(1), role.PermsLevel)
- }
- // TC-0118: 重复角色名
- func TestCreateRole_DuplicateName(t *testing.T) {
- ctx := ctxhelper.SuperAdminCtx()
- svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
- conn := testutil.GetTestSqlConn()
- pc := testutil.UniqueId()
- pid := mustInsertEnabledProduct(t, ctx, svcCtx, pc)
- name := testutil.UniqueId()
- now := time.Now().Unix()
- res, err := svcCtx.SysRoleModel.Insert(ctx, &roleModel.SysRole{
- ProductCode: pc, Name: name, Status: 1, PermsLevel: 1,
- CreateTime: now, UpdateTime: now,
- })
- require.NoError(t, err)
- existingId, _ := res.LastInsertId()
- t.Cleanup(func() {
- testutil.CleanTable(ctx, conn, "`sys_role`", existingId)
- testutil.CleanTable(ctx, conn, "`sys_product`", pid)
- })
- logic := NewCreateRoleLogic(ctx, svcCtx)
- resp, err := logic.CreateRole(&types.CreateRoleReq{
- ProductCode: pc,
- Name: name,
- PermsLevel: 1,
- })
- assert.Nil(t, resp)
- require.Error(t, err)
- var ce *response.CodeError
- require.True(t, errors.As(err, &ce))
- assert.Equal(t, 409, ce.Code())
- assert.Equal(t, "该产品下角色名已存在", ce.Error())
- }
- // TC-0119: 并发同名创建
- func TestCreateRole_ConcurrentSameName(t *testing.T) {
- ctx := ctxhelper.SuperAdminCtx()
- svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
- conn := testutil.GetTestSqlConn()
- pc := testutil.UniqueId()
- pid := mustInsertEnabledProduct(t, ctx, svcCtx, pc)
- name := testutil.UniqueId()
- t.Cleanup(func() {
- testutil.CleanTableByField(ctx, conn, "`sys_role`", "productCode", pc)
- testutil.CleanTable(ctx, conn, "`sys_product`", pid)
- })
- var wg sync.WaitGroup
- results := make(chan error, 2)
- for i := 0; i < 2; i++ {
- wg.Add(1)
- go func() {
- defer wg.Done()
- logic := NewCreateRoleLogic(ctx, svcCtx)
- _, err := logic.CreateRole(&types.CreateRoleReq{
- ProductCode: pc,
- Name: name,
- PermsLevel: 1,
- })
- results <- err
- }()
- }
- wg.Wait()
- close(results)
- var errs []error
- for err := range results {
- errs = append(errs, err)
- }
- require.Len(t, errs, 2)
- successCount := 0
- failCount := 0
- for _, err := range errs {
- if err == nil {
- successCount++
- } else {
- failCount++
- var codeErr *response.CodeError
- require.True(t, errors.As(err, &codeErr))
- assert.Equal(t, 409, codeErr.Code())
- }
- }
- assert.Equal(t, 1, successCount)
- assert.Equal(t, 1, failCount)
- }
- // TC-0538: createRole非管理员拒绝
- func TestCreateRole_MemberRejected(t *testing.T) {
- ctx := ctxhelper.MemberCtx("test_product")
- svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
- logic := NewCreateRoleLogic(ctx, svcCtx)
- _, err := logic.CreateRole(&types.CreateRoleReq{ProductCode: "test", 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())
- }
- // TC-1197: H-R17-3 —— 非超管 product ADMIN 不得创建 PermsLevel=1 顶格角色。
- //
- // 核心风险:product ADMIN 可用 CreateRole(PermsLevel=1) 造出"顶格角色",再通过
- // BindRoles 给下属 MEMBER/DEVELOPER,绕过 GuardRoleLevelAssignable 的"同级拦截",
- // 等价于横向提权。GuardCreateRolePermsLevel 对 HasFullPerms 的调用者强制 reqLevel >= 2,
- // 把 permsLevel=1 保留给 SuperAdmin。
- func TestCreateRole_H_R17_3_AdminCannotCreatePermsLevel1(t *testing.T) {
- pc := "h_r17_3_" + testutil.UniqueId()
- ctx := ctxhelper.AdminCtx(pc)
- svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
- conn := testutil.GetTestSqlConn()
- pid := mustInsertEnabledProduct(t, ctx, svcCtx, pc)
- t.Cleanup(func() {
- testutil.CleanTableByField(ctx, conn, "`sys_role`", "productCode", pc)
- testutil.CleanTable(ctx, conn, "`sys_product`", pid)
- })
- _, err := NewCreateRoleLogic(ctx, svcCtx).CreateRole(&types.CreateRoleReq{
- ProductCode: pc,
- Name: "top_role_" + testutil.UniqueId(),
- PermsLevel: 1,
- })
- require.Error(t, err, "product ADMIN 尝试创建 PermsLevel=1 必须 403")
- var ce *response.CodeError
- require.True(t, errors.As(err, &ce))
- assert.Equal(t, 403, ce.Code(),
- "H-R17-3:非超管创建顶格角色必须 403,防止'建 R_super + BindRoles' 横向提权链路")
- assert.Contains(t, ce.Error(), "权限级别为 1 的顶格角色",
- "错误文案必须点名 PermsLevel=1 的边界条件,便于调用方一眼看出约束")
- }
- // TC-1198: H-R17-3 正向 —— product ADMIN 可创建 PermsLevel>=2 的次级角色。
- // 防 GuardCreateRolePermsLevel 过度收紧把合法业务路径也误伤。
- func TestCreateRole_H_R17_3_AdminCanCreatePermsLevel2(t *testing.T) {
- pc := "h_r17_3_ok_" + testutil.UniqueId()
- ctx := ctxhelper.AdminCtx(pc)
- svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
- conn := testutil.GetTestSqlConn()
- pid := mustInsertEnabledProduct(t, ctx, svcCtx, pc)
- resp, err := NewCreateRoleLogic(ctx, svcCtx).CreateRole(&types.CreateRoleReq{
- ProductCode: pc,
- Name: "ok_role_" + testutil.UniqueId(),
- PermsLevel: 2,
- })
- require.NoError(t, err)
- require.NotNil(t, resp)
- t.Cleanup(func() {
- testutil.CleanTable(ctx, conn, "`sys_role`", resp.Id)
- testutil.CleanTable(ctx, conn, "`sys_product`", pid)
- })
- role, err := svcCtx.SysRoleModel.FindOne(ctx, resp.Id)
- require.NoError(t, err)
- assert.Equal(t, int64(2), role.PermsLevel,
- "ADMIN 创建 PermsLevel=2 应当成功并如实落盘,作为 H-R17-3 正向基线")
- }
- // TC-1199: H-R17-3 —— SuperAdmin 继续不受 PermsLevel=1 约束。
- // SuperAdmin 创建 permsLevel=1 是系统默认的"顶格角色唯一合法来源",必须保持放行。
- func TestCreateRole_H_R17_3_SuperAdminCanCreatePermsLevel1(t *testing.T) {
- pc := "h_r17_3_su_" + testutil.UniqueId()
- ctx := ctxhelper.SuperAdminCtx()
- svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
- conn := testutil.GetTestSqlConn()
- pid := mustInsertEnabledProduct(t, ctx, svcCtx, pc)
- resp, err := NewCreateRoleLogic(ctx, svcCtx).CreateRole(&types.CreateRoleReq{
- ProductCode: pc,
- Name: "su_top_" + testutil.UniqueId(),
- PermsLevel: 1,
- })
- require.NoError(t, err, "SuperAdmin 必须能够创建 PermsLevel=1,否则顶格角色无合法来源")
- require.NotNil(t, resp)
- t.Cleanup(func() {
- testutil.CleanTable(ctx, conn, "`sys_role`", resp.Id)
- testutil.CleanTable(ctx, conn, "`sys_product`", pid)
- })
- role, err := svcCtx.SysRoleModel.FindOne(ctx, resp.Id)
- require.NoError(t, err)
- assert.Equal(t, int64(1), role.PermsLevel)
- }
|