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) }