| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172 |
- package user
- import (
- "context"
- "database/sql"
- "errors"
- "math"
- "testing"
- "time"
- "perms-system-server/internal/consts"
- "perms-system-server/internal/loaders"
- "perms-system-server/internal/middleware"
- deptModel "perms-system-server/internal/model/dept"
- userModel "perms-system-server/internal/model/user"
- "perms-system-server/internal/response"
- "perms-system-server/internal/svc"
- "perms-system-server/internal/testutil"
- "perms-system-server/internal/types"
- "github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/require"
- )
- func insertTestDeptForScope(t *testing.T, ctx context.Context, svcCtx *svc.ServiceContext, tag, path string) int64 {
- t.Helper()
- now := time.Now().Unix()
- res, err := svcCtx.SysDeptModel.Insert(ctx, &deptModel.SysDept{
- ParentId: 0, Name: tag + "_" + testutil.UniqueId(), Path: path, Sort: 0,
- DeptType: "NORMAL", Remark: "", Status: 1, CreateTime: now, UpdateTime: now,
- })
- require.NoError(t, err)
- id, _ := res.LastInsertId()
- return id
- }
- func insertTestUserWithDept(t *testing.T, ctx context.Context, tag string, deptId int64) int64 {
- t.Helper()
- now := time.Now().Unix()
- return insertTestUserFull(t, ctx, &userModel.SysUser{
- Username: "ddu_" + tag + "_" + testutil.UniqueId(),
- Password: testutil.HashPassword("pw"),
- Nickname: "n",
- Avatar: sql.NullString{},
- Email: "[email protected]",
- Phone: "13800000000",
- DeptId: deptId,
- IsSuperAdmin: consts.IsSuperAdminNo,
- MustChangePassword: 2,
- Status: consts.StatusEnabled,
- CreateTime: now,
- UpdateTime: now,
- })
- }
- // TC-0746: L-F 修复回归 —— DEVELOPER 调用者不得将目标用户的 deptId 调到
- // 自己 DeptPath 子树之外的部门。UpdateUser 必须在 req.DeptId 变更时做 Path 前缀校验。
- func TestUpdateUser_DeveloperCannotMoveTargetOutsideSubtree(t *testing.T) {
- bootstrap := context.Background()
- svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
- conn := testutil.GetTestSqlConn()
- callerDeptId := insertTestDeptForScope(t, bootstrap, svcCtx, "caller", "/100/")
- targetDeptId := insertTestDeptForScope(t, bootstrap, svcCtx, "target", "/100/200/")
- outsideDeptId := insertTestDeptForScope(t, bootstrap, svcCtx, "outside", "/999/")
- targetId := insertTestUserWithDept(t, bootstrap, "lf_out", targetDeptId)
- mId := insertTestMember(t, svcCtx, "test_product", targetId)
- t.Cleanup(func() {
- testutil.CleanTable(bootstrap, conn, "`sys_product_member`", mId)
- testutil.CleanTable(bootstrap, conn, "`sys_user`", targetId)
- testutil.CleanTable(bootstrap, conn, "`sys_dept`", callerDeptId, targetDeptId, outsideDeptId)
- })
- devCtx := middleware.WithUserDetails(context.Background(), &loaders.UserDetails{
- UserId: 55555, Username: "lf_dev",
- IsSuperAdmin: false,
- MemberType: consts.MemberTypeDeveloper,
- Status: consts.StatusEnabled,
- ProductCode: "test_product",
- DeptId: callerDeptId,
- DeptPath: "/100/",
- MinPermsLevel: math.MaxInt64,
- })
- newDept := outsideDeptId
- err := NewUpdateUserLogic(devCtx, svcCtx).UpdateUser(&types.UpdateUserReq{
- Id: targetId,
- DeptId: &newDept,
- })
- require.Error(t, err, "调入外部部门应被拒绝")
- var ce *response.CodeError
- require.True(t, errors.As(err, &ce))
- assert.Equal(t, 403, ce.Code())
- assert.Contains(t, ce.Error(), "无权将用户调入")
- user, err := svcCtx.SysUserModel.FindOne(bootstrap, targetId)
- require.NoError(t, err)
- assert.Equal(t, targetDeptId, user.DeptId, "被拒绝的请求必须不改动 DB")
- }
- // TC-0747: L-F 正向回归 —— DEVELOPER 将目标用户调入自己子树下的部门应允许。
- func TestUpdateUser_DeveloperCanMoveTargetWithinSubtree(t *testing.T) {
- bootstrap := context.Background()
- svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
- conn := testutil.GetTestSqlConn()
- callerDeptId := insertTestDeptForScope(t, bootstrap, svcCtx, "caller_in", "/200/")
- srcDeptId := insertTestDeptForScope(t, bootstrap, svcCtx, "src_in", "/200/1/")
- dstDeptId := insertTestDeptForScope(t, bootstrap, svcCtx, "dst_in", "/200/2/")
- targetId := insertTestUserWithDept(t, bootstrap, "lf_in", srcDeptId)
- mId := insertTestMember(t, svcCtx, "test_product", targetId)
- t.Cleanup(func() {
- testutil.CleanTable(bootstrap, conn, "`sys_product_member`", mId)
- testutil.CleanTable(bootstrap, conn, "`sys_user`", targetId)
- testutil.CleanTable(bootstrap, conn, "`sys_dept`", callerDeptId, srcDeptId, dstDeptId)
- })
- devCtx := middleware.WithUserDetails(context.Background(), &loaders.UserDetails{
- UserId: 66666, Username: "lf_dev_ok",
- IsSuperAdmin: false,
- MemberType: consts.MemberTypeDeveloper,
- Status: consts.StatusEnabled,
- ProductCode: "test_product",
- DeptId: callerDeptId,
- DeptPath: "/200/",
- MinPermsLevel: math.MaxInt64,
- })
- newDept := dstDeptId
- require.NoError(t,
- NewUpdateUserLogic(devCtx, svcCtx).UpdateUser(&types.UpdateUserReq{
- Id: targetId, DeptId: &newDept,
- }),
- "caller DeptPath 的前缀子部门必须允许调入")
- user, err := svcCtx.SysUserModel.FindOne(bootstrap, targetId)
- require.NoError(t, err)
- assert.Equal(t, dstDeptId, user.DeptId)
- }
- // TC-0748: L-F —— 产品 ADMIN 调用者被豁免 DeptPath 前缀校验(可跨部门转移)。
- func TestUpdateUser_ProductAdminExemptFromSubtreeCheck(t *testing.T) {
- bootstrap := context.Background()
- svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
- conn := testutil.GetTestSqlConn()
- adminDeptId := insertTestDeptForScope(t, bootstrap, svcCtx, "admin_home", "/300/")
- targetHomeDept := insertTestDeptForScope(t, bootstrap, svcCtx, "target_home", "/400/")
- anywhereDept := insertTestDeptForScope(t, bootstrap, svcCtx, "anywhere", "/500/")
- targetId := insertTestUserWithDept(t, bootstrap, "lf_admin", targetHomeDept)
- mId := insertTestMember(t, svcCtx, "test_product", targetId)
- t.Cleanup(func() {
- testutil.CleanTable(bootstrap, conn, "`sys_product_member`", mId)
- testutil.CleanTable(bootstrap, conn, "`sys_user`", targetId)
- testutil.CleanTable(bootstrap, conn, "`sys_dept`", adminDeptId, targetHomeDept, anywhereDept)
- })
- adminCtx := middleware.WithUserDetails(context.Background(), &loaders.UserDetails{
- UserId: 77777, Username: "lf_admin",
- IsSuperAdmin: false, MemberType: consts.MemberTypeAdmin,
- Status: consts.StatusEnabled, ProductCode: "test_product",
- DeptId: adminDeptId, DeptPath: "/300/", MinPermsLevel: math.MaxInt64,
- })
- newDept := anywhereDept
- require.NoError(t,
- NewUpdateUserLogic(adminCtx, svcCtx).UpdateUser(&types.UpdateUserReq{
- Id: targetId, DeptId: &newDept,
- }),
- "产品 ADMIN 在 UpdateUser 的 DeptPath 前缀校验中被豁免")
- }
|