| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470 |
- package auth
- import (
- "context"
- "errors"
- "fmt"
- "math"
- "math/rand"
- "testing"
- "time"
- "perms-system-server/internal/consts"
- "perms-system-server/internal/loaders"
- deptModel "perms-system-server/internal/model/dept"
- "perms-system-server/internal/model/productmember"
- 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/testutil/ctxhelper"
- "github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/require"
- "github.com/zeromicro/go-zero/core/stores/sqlx"
- )
- // =====================================================================
- // RequireSuperAdmin
- // =====================================================================
- // TC-0435: 超管通过
- func TestRequireSuperAdmin_SuperAdmin(t *testing.T) {
- err := RequireSuperAdmin(ctxhelper.SuperAdminCtx())
- assert.NoError(t, err)
- }
- // TC-0436: ADMIN → 403 "仅超级管理员"
- func TestRequireSuperAdmin_Admin(t *testing.T) {
- err := RequireSuperAdmin(ctxhelper.AdminCtx("p1"))
- 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(), "仅超级管理员")
- }
- // TC-0437: MEMBER → 403
- func TestRequireSuperAdmin_Member(t *testing.T) {
- err := RequireSuperAdmin(ctxhelper.MemberCtx("p1"))
- require.Error(t, err)
- var ce *response.CodeError
- require.True(t, errors.As(err, &ce))
- assert.Equal(t, 403, ce.Code())
- }
- // TC-0438: 无 UserDetails → 401 "未登录"
- func TestRequireSuperAdmin_NoUserDetails(t *testing.T) {
- err := RequireSuperAdmin(context.Background())
- require.Error(t, err)
- var ce *response.CodeError
- require.True(t, errors.As(err, &ce))
- assert.Equal(t, 401, ce.Code())
- assert.Contains(t, ce.Error(), "未登录")
- }
- // =====================================================================
- // RequireProductAdmin
- // =====================================================================
- // TC-0439: SuperAdmin → nil
- func TestRequireProductAdmin_SuperAdmin(t *testing.T) {
- err := RequireProductAdmin(ctxhelper.SuperAdminCtx())
- assert.NoError(t, err)
- }
- // TC-0440: ADMIN → nil
- func TestRequireProductAdmin_Admin(t *testing.T) {
- err := RequireProductAdmin(ctxhelper.AdminCtx("p1"))
- assert.NoError(t, err)
- }
- // TC-0441: DEVELOPER → 403
- func TestRequireProductAdmin_Developer(t *testing.T) {
- err := RequireProductAdmin(ctxhelper.DeveloperCtx("p1"))
- require.Error(t, err)
- var ce *response.CodeError
- require.True(t, errors.As(err, &ce))
- assert.Equal(t, 403, ce.Code())
- }
- // TC-0442: MEMBER → 403
- func TestRequireProductAdmin_Member(t *testing.T) {
- err := RequireProductAdmin(ctxhelper.MemberCtx("p1"))
- require.Error(t, err)
- var ce *response.CodeError
- require.True(t, errors.As(err, &ce))
- assert.Equal(t, 403, ce.Code())
- }
- // TC-0443: 无 UserDetails → 401
- func TestRequireProductAdmin_NoUserDetails(t *testing.T) {
- err := RequireProductAdmin(context.Background())
- require.Error(t, err)
- var ce *response.CodeError
- require.True(t, errors.As(err, &ce))
- assert.Equal(t, 401, ce.Code())
- assert.Contains(t, ce.Error(), "未登录")
- }
- // =====================================================================
- // CheckMemberTypeAssignment
- // =====================================================================
- // TC-0444: 超管分配 ADMIN → nil
- func TestCheckMemberTypeAssignment_SuperAdminAssignsAdmin(t *testing.T) {
- err := CheckMemberTypeAssignment(ctxhelper.SuperAdminCtx(), consts.MemberTypeAdmin)
- assert.NoError(t, err)
- }
- // TC-0445: ADMIN 分配 DEVELOPER → nil
- func TestCheckMemberTypeAssignment_AdminAssignsDeveloper(t *testing.T) {
- err := CheckMemberTypeAssignment(ctxhelper.AdminCtx("p1"), consts.MemberTypeDeveloper)
- assert.NoError(t, err)
- }
- // TC-0446: ADMIN 分配 ADMIN(同级)→ 403
- func TestCheckMemberTypeAssignment_AdminAssignsAdmin(t *testing.T) {
- err := CheckMemberTypeAssignment(ctxhelper.AdminCtx("p1"), consts.MemberTypeAdmin)
- require.Error(t, err)
- var ce *response.CodeError
- require.True(t, errors.As(err, &ce))
- assert.Equal(t, 403, ce.Code())
- }
- // TC-0447: DEVELOPER 分配 ADMIN(更高级)→ 403
- func TestCheckMemberTypeAssignment_DeveloperAssignsAdmin(t *testing.T) {
- err := CheckMemberTypeAssignment(ctxhelper.DeveloperCtx("p1"), consts.MemberTypeAdmin)
- require.Error(t, err)
- var ce *response.CodeError
- require.True(t, errors.As(err, &ce))
- assert.Equal(t, 403, ce.Code())
- }
- // TC-0448: MEMBER 分配 MEMBER(同级)→ 403
- func TestCheckMemberTypeAssignment_MemberAssignsMember(t *testing.T) {
- err := CheckMemberTypeAssignment(ctxhelper.MemberCtx("p1"), consts.MemberTypeMember)
- require.Error(t, err)
- var ce *response.CodeError
- require.True(t, errors.As(err, &ce))
- assert.Equal(t, 403, ce.Code())
- }
- // TC-0449: 无 UserDetails → 401
- func TestCheckMemberTypeAssignment_NoUserDetails(t *testing.T) {
- err := CheckMemberTypeAssignment(context.Background(), consts.MemberTypeMember)
- require.Error(t, err)
- var ce *response.CodeError
- require.True(t, errors.As(err, &ce))
- assert.Equal(t, 401, ce.Code())
- assert.Contains(t, ce.Error(), "未登录")
- }
- // =====================================================================
- // memberTypePriority (未导出,同包可测)
- // =====================================================================
- // TC-0457: 所有成员类型返回正确优先级
- func TestMemberTypePriority(t *testing.T) {
- tests := []struct {
- name string
- memberType string
- want int
- }{
- {"SUPER_ADMIN=0", consts.MemberTypeSuperAdmin, 0},
- {"ADMIN=1", consts.MemberTypeAdmin, 1},
- {"DEVELOPER=2", consts.MemberTypeDeveloper, 2},
- {"MEMBER=3", consts.MemberTypeMember, 3},
- {"unknown=MaxInt32", "UNKNOWN_TYPE", math.MaxInt32},
- {"empty=MaxInt32", "", math.MaxInt32},
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- got := memberTypePriority(tt.memberType)
- assert.Equal(t, tt.want, got)
- })
- }
- }
- // =====================================================================
- // CheckManageAccess (集成测试,需要真实 DB)
- // =====================================================================
- func newIntegrationSvcCtx() *svc.ServiceContext {
- return svc.NewServiceContext(testutil.GetTestConfig())
- }
- func createTestUser(t *testing.T, ctx context.Context, svcCtx *svc.ServiceContext, conn sqlx.SqlConn, opts struct {
- username string
- deptId int64
- isSuperAdmin int64
- }) int64 {
- t.Helper()
- now := time.Now().Unix()
- res, err := svcCtx.SysUserModel.Insert(ctx, &userModel.SysUser{
- Username: opts.username,
- Password: testutil.HashPassword("test123"),
- Nickname: opts.username,
- Email: opts.username + "@test.com",
- Phone: "",
- Remark: "",
- DeptId: opts.deptId,
- IsSuperAdmin: opts.isSuperAdmin,
- MustChangePassword: consts.MustChangePasswordNo,
- Status: consts.StatusEnabled,
- CreateTime: now,
- UpdateTime: now,
- })
- require.NoError(t, err)
- id, _ := res.LastInsertId()
- return id
- }
- // TC-0450: SuperAdmin 跳过所有检查
- func TestCheckManageAccess_SuperAdminBypasses(t *testing.T) {
- ctx := context.Background()
- svcCtx := newIntegrationSvcCtx()
- conn := testutil.GetTestSqlConn()
- targetId := createTestUser(t, ctx, svcCtx, conn, struct {
- username string
- deptId int64
- isSuperAdmin int64
- }{
- username: fmt.Sprintf("target_sa_%d", rand.Intn(100000)),
- deptId: 0,
- isSuperAdmin: consts.IsSuperAdminNo,
- })
- t.Cleanup(func() {
- testutil.CleanTable(ctx, conn, "`sys_user`", targetId)
- })
- err := CheckManageAccess(ctxhelper.SuperAdminCtx(), svcCtx, targetId, "p1")
- assert.NoError(t, err)
- }
- // TC-0451: 操作自己豁免
- func TestCheckManageAccess_SelfManagement(t *testing.T) {
- selfCtx := ctxhelper.CustomCtx(&loaders.UserDetails{
- UserId: 100,
- Username: "self_user",
- IsSuperAdmin: false,
- MemberType: consts.MemberTypeMember,
- Status: consts.StatusEnabled,
- ProductCode: "p1",
- DeptId: 1,
- DeptPath: "/1/",
- })
- svcCtx := newIntegrationSvcCtx()
- err := CheckManageAccess(selfCtx, svcCtx, 100, "p1")
- assert.NoError(t, err)
- }
- // TC-0452: ADMIN 跳过部门层级检查
- func TestCheckManageAccess_AdminSkipsDeptCheck(t *testing.T) {
- ctx := context.Background()
- svcCtx := newIntegrationSvcCtx()
- conn := testutil.GetTestSqlConn()
- now := time.Now().Unix()
- pc := fmt.Sprintf("tp_access_%d", rand.Intn(100000))
- targetId := createTestUser(t, ctx, svcCtx, conn, struct {
- username string
- deptId int64
- isSuperAdmin int64
- }{
- username: fmt.Sprintf("target_admin_%d", rand.Intn(100000)),
- deptId: 0,
- isSuperAdmin: consts.IsSuperAdminNo,
- })
- pmRes, err := svcCtx.SysProductMemberModel.Insert(ctx, &productmember.SysProductMember{
- ProductCode: pc, UserId: targetId, MemberType: consts.MemberTypeMember,
- Status: consts.StatusEnabled, CreateTime: now, UpdateTime: now,
- })
- require.NoError(t, err)
- pmId, _ := pmRes.LastInsertId()
- t.Cleanup(func() {
- testutil.CleanTable(ctx, conn, "`sys_product_member`", pmId)
- testutil.CleanTable(ctx, conn, "`sys_user`", targetId)
- })
- adminCtx := ctxhelper.CustomCtx(&loaders.UserDetails{
- UserId: 2,
- Username: "admin_test",
- IsSuperAdmin: false,
- MemberType: consts.MemberTypeAdmin,
- Status: consts.StatusEnabled,
- ProductCode: pc,
- DeptId: 999,
- DeptPath: "/999/",
- MinPermsLevel: math.MaxInt64,
- })
- err = CheckManageAccess(adminCtx, svcCtx, targetId, pc)
- assert.NoError(t, err)
- }
- // TC-0453: 无 UserDetails → 401
- func TestCheckManageAccess_NoUserDetails(t *testing.T) {
- svcCtx := newIntegrationSvcCtx()
- err := CheckManageAccess(context.Background(), svcCtx, 1, "p1")
- require.Error(t, err)
- var ce *response.CodeError
- require.True(t, errors.As(err, &ce))
- assert.Equal(t, 401, ce.Code())
- }
- // TC-0454: DEVELOPER 无部门归属 → 403
- func TestCheckManageAccess_NoDept(t *testing.T) {
- svcCtx := newIntegrationSvcCtx()
- noDeptCtx := ctxhelper.CustomCtx(&loaders.UserDetails{
- UserId: 3,
- Username: "dev_nodept",
- IsSuperAdmin: false,
- MemberType: consts.MemberTypeDeveloper,
- Status: consts.StatusEnabled,
- ProductCode: "p1",
- DeptId: 0,
- DeptPath: "",
- MinPermsLevel: math.MaxInt64,
- })
- err := CheckManageAccess(noDeptCtx, svcCtx, 99999, "p1")
- 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(), "未归属任何部门")
- }
- // TC-0455: DEVELOPER 操作不同部门的用户 → 403
- func TestCheckManageAccess_CrossDeptForbidden(t *testing.T) {
- ctx := context.Background()
- svcCtx := newIntegrationSvcCtx()
- conn := testutil.GetTestSqlConn()
- now := time.Now().Unix()
- dept1Res, err := svcCtx.SysDeptModel.Insert(ctx, &deptModel.SysDept{
- ParentId: 0, Name: fmt.Sprintf("dept_a_%d", rand.Intn(100000)),
- Path: fmt.Sprintf("/%d/", rand.Intn(100000)),
- Sort: 1, DeptType: consts.DeptTypeNormal, Status: consts.StatusEnabled,
- CreateTime: now, UpdateTime: now,
- })
- require.NoError(t, err)
- dept1Id, _ := dept1Res.LastInsertId()
- dept2Res, err := svcCtx.SysDeptModel.Insert(ctx, &deptModel.SysDept{
- ParentId: 0, Name: fmt.Sprintf("dept_b_%d", rand.Intn(100000)),
- Path: fmt.Sprintf("/%d/", rand.Intn(100000)),
- Sort: 1, DeptType: consts.DeptTypeNormal, Status: consts.StatusEnabled,
- CreateTime: now, UpdateTime: now,
- })
- require.NoError(t, err)
- dept2Id, _ := dept2Res.LastInsertId()
- targetId := createTestUser(t, ctx, svcCtx, conn, struct {
- username string
- deptId int64
- isSuperAdmin int64
- }{
- username: fmt.Sprintf("target_crossdept_%d", rand.Intn(100000)),
- deptId: dept2Id,
- isSuperAdmin: consts.IsSuperAdminNo,
- })
- dept2, err := svcCtx.SysDeptModel.FindOne(ctx, dept2Id)
- require.NoError(t, err)
- dept1, err := svcCtx.SysDeptModel.FindOne(ctx, dept1Id)
- require.NoError(t, err)
- t.Cleanup(func() {
- testutil.CleanTable(ctx, conn, "`sys_user`", targetId)
- testutil.CleanTable(ctx, conn, "`sys_dept`", dept1Id, dept2Id)
- })
- callerCtx := ctxhelper.CustomCtx(&loaders.UserDetails{
- UserId: 99998,
- Username: "dev_crossdept",
- IsSuperAdmin: false,
- MemberType: consts.MemberTypeDeveloper,
- Status: consts.StatusEnabled,
- ProductCode: "p1",
- DeptId: dept1Id,
- DeptPath: dept1.Path,
- MinPermsLevel: math.MaxInt64,
- })
- _ = dept2
- err = CheckManageAccess(callerCtx, svcCtx, targetId, "p1")
- require.Error(t, err)
- var ce *response.CodeError
- require.True(t, errors.As(err, &ce))
- assert.Equal(t, 403, ce.Code())
- }
- // TC-0456: DEVELOPER 操作同部门的 MEMBER 且权限级别更高 → nil
- func TestCheckManageAccess_SameDeptLowerLevel(t *testing.T) {
- ctx := context.Background()
- svcCtx := newIntegrationSvcCtx()
- conn := testutil.GetTestSqlConn()
- now := time.Now().Unix()
- pc := fmt.Sprintf("tp_samedept_%d", rand.Intn(100000))
- deptRes, err := svcCtx.SysDeptModel.Insert(ctx, &deptModel.SysDept{
- ParentId: 0, Name: fmt.Sprintf("dept_same_%d", rand.Intn(100000)),
- Path: "/", Sort: 1, DeptType: consts.DeptTypeNormal, Status: consts.StatusEnabled,
- CreateTime: now, UpdateTime: now,
- })
- require.NoError(t, err)
- deptId, _ := deptRes.LastInsertId()
- deptObj, err := svcCtx.SysDeptModel.FindOne(ctx, deptId)
- require.NoError(t, err)
- targetId := createTestUser(t, ctx, svcCtx, conn, struct {
- username string
- deptId int64
- isSuperAdmin int64
- }{
- username: fmt.Sprintf("target_samedept_%d", rand.Intn(100000)),
- deptId: deptId,
- isSuperAdmin: consts.IsSuperAdminNo,
- })
- pmRes, err := svcCtx.SysProductMemberModel.Insert(ctx, &productmember.SysProductMember{
- ProductCode: pc, UserId: targetId, MemberType: consts.MemberTypeMember,
- Status: consts.StatusEnabled, CreateTime: now, UpdateTime: now,
- })
- require.NoError(t, err)
- pmId, _ := pmRes.LastInsertId()
- t.Cleanup(func() {
- testutil.CleanTable(ctx, conn, "`sys_product_member`", pmId)
- testutil.CleanTable(ctx, conn, "`sys_user`", targetId)
- testutil.CleanTable(ctx, conn, "`sys_dept`", deptId)
- })
- callerCtx := ctxhelper.CustomCtx(&loaders.UserDetails{
- UserId: 99997,
- Username: "dev_samedept",
- IsSuperAdmin: false,
- MemberType: consts.MemberTypeDeveloper,
- Status: consts.StatusEnabled,
- ProductCode: pc,
- DeptId: deptId,
- DeptPath: deptObj.Path,
- MinPermsLevel: 1,
- })
- err = CheckManageAccess(callerCtx, svcCtx, targetId, pc)
- assert.NoError(t, err)
- }
- // suppress unused import
- var _ = sqlx.ErrNotFound
|