| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367 |
- package user
- import (
- "errors"
- "sync"
- "sync/atomic"
- "testing"
- "perms-system-server/internal/consts"
- 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"
- "perms-system-server/internal/types"
- "perms-system-server/internal/util"
- "github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/require"
- )
- // TC-1285 超管重置普通用户密码
- func TestResetPassword_SuperAdmin(t *testing.T) {
- ctx := ctxhelper.SuperAdminCtx()
- svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
- conn := testutil.GetTestSqlConn()
- username := testutil.UniqueId()
- hashed := testutil.HashPassword("OldPass123")
- userId := insertTestUser(t, ctx, username, hashed)
- t.Cleanup(func() { testutil.CleanTable(ctx, conn, "`sys_user`", userId) })
- logic := NewResetPasswordLogic(ctx, svcCtx)
- resp, err := logic.ResetPassword(&types.ResetPasswordReq{UserId: userId})
- require.NoError(t, err)
- require.NotNil(t, resp)
- assert.NotEmpty(t, resp.CredentialsTicket)
- assert.Greater(t, resp.CredentialsExpiresAt, int64(0))
- // 清理 Redis ticket
- t.Cleanup(func() { svcCtx.Redis.DelCtx(ctx, userCredentialsKeyPrefix+resp.CredentialsTicket) })
- }
- // TC-1287 不能重置超管密码
- func TestResetPassword_CannotResetSuperAdmin(t *testing.T) {
- ctx := ctxhelper.SuperAdminCtxWithUserId(999)
- svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
- conn := testutil.GetTestSqlConn()
- username := testutil.UniqueId()
- hashed := testutil.HashPassword("Pass123456")
- superUserId := insertTestUserFull(t, ctx, &userModel.SysUser{
- Username: username,
- Password: hashed,
- IsSuperAdmin: consts.IsSuperAdminYes,
- Status: consts.StatusEnabled,
- MustChangePassword: consts.MustChangePasswordNo,
- })
- t.Cleanup(func() { testutil.CleanTable(ctx, conn, "`sys_user`", superUserId) })
- logic := NewResetPasswordLogic(ctx, svcCtx)
- _, err := logic.ResetPassword(&types.ResetPasswordReq{UserId: superUserId})
- require.Error(t, err)
- var codeErr *response.CodeError
- require.True(t, errors.As(err, &codeErr))
- assert.Equal(t, 403, codeErr.Code())
- assert.Contains(t, codeErr.Error(), "超级管理员")
- }
- // TC-1288 MEMBER无权重置
- func TestResetPassword_MemberForbidden(t *testing.T) {
- ctx := ctxhelper.MemberCtx("test-product")
- svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
- logic := NewResetPasswordLogic(ctx, svcCtx)
- _, err := logic.ResetPassword(&types.ResetPasswordReq{UserId: 1})
- require.Error(t, err)
- var codeErr *response.CodeError
- require.True(t, errors.As(err, &codeErr))
- assert.Equal(t, 403, codeErr.Code())
- }
- // TC-1289 重置后旧token失效(tokenVersion递增)
- func TestResetPassword_TokenVersionIncremented(t *testing.T) {
- ctx := ctxhelper.SuperAdminCtx()
- svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
- conn := testutil.GetTestSqlConn()
- username := testutil.UniqueId()
- hashed := testutil.HashPassword("OldPass123")
- userId := insertTestUser(t, ctx, username, hashed)
- t.Cleanup(func() { testutil.CleanTable(ctx, conn, "`sys_user`", userId) })
- userBefore, err := svcCtx.SysUserModel.FindOne(ctx, userId)
- require.NoError(t, err)
- logic := NewResetPasswordLogic(ctx, svcCtx)
- resp, err := logic.ResetPassword(&types.ResetPasswordReq{UserId: userId})
- require.NoError(t, err)
- t.Cleanup(func() { svcCtx.Redis.DelCtx(ctx, userCredentialsKeyPrefix+resp.CredentialsTicket) })
- userAfter, err := svcCtx.SysUserModel.FindOne(ctx, userId)
- require.NoError(t, err)
- assert.Equal(t, userBefore.TokenVersion+1, userAfter.TokenVersion)
- }
- // TC-1290 重置后mustChangePassword=1
- func TestResetPassword_MustChangePassword(t *testing.T) {
- ctx := ctxhelper.SuperAdminCtx()
- svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
- conn := testutil.GetTestSqlConn()
- username := testutil.UniqueId()
- hashed := testutil.HashPassword("OldPass123")
- userId := insertTestUserFull(t, ctx, &userModel.SysUser{
- Username: username,
- Password: hashed,
- IsSuperAdmin: consts.IsSuperAdminNo,
- Status: consts.StatusEnabled,
- MustChangePassword: consts.MustChangePasswordNo,
- })
- t.Cleanup(func() { testutil.CleanTable(ctx, conn, "`sys_user`", userId) })
- logic := NewResetPasswordLogic(ctx, svcCtx)
- resp, err := logic.ResetPassword(&types.ResetPasswordReq{UserId: userId})
- require.NoError(t, err)
- t.Cleanup(func() { svcCtx.Redis.DelCtx(ctx, userCredentialsKeyPrefix+resp.CredentialsTicket) })
- user, err := svcCtx.SysUserModel.FindOne(ctx, userId)
- require.NoError(t, err)
- assert.Equal(t, int64(consts.MustChangePasswordYes), user.MustChangePassword)
- }
- // TC-1291 目标用户不存在
- func TestResetPassword_UserNotFound(t *testing.T) {
- ctx := ctxhelper.SuperAdminCtx()
- svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
- logic := NewResetPasswordLogic(ctx, svcCtx)
- _, err := logic.ResetPassword(&types.ResetPasswordReq{UserId: 999999999})
- require.Error(t, err)
- var codeErr *response.CodeError
- require.True(t, errors.As(err, &codeErr))
- assert.Equal(t, 404, codeErr.Code())
- }
- // TC-1293 重置后用新密码可验证
- func TestResetPassword_NewPasswordValid(t *testing.T) {
- ctx := ctxhelper.SuperAdminCtx()
- svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
- conn := testutil.GetTestSqlConn()
- username := testutil.UniqueId()
- hashed := testutil.HashPassword("OldPass123")
- userId := insertTestUser(t, ctx, username, hashed)
- t.Cleanup(func() { testutil.CleanTable(ctx, conn, "`sys_user`", userId) })
- logic := NewResetPasswordLogic(ctx, svcCtx)
- resp, err := logic.ResetPassword(&types.ResetPasswordReq{UserId: userId})
- require.NoError(t, err)
- t.Cleanup(func() { svcCtx.Redis.DelCtx(ctx, userCredentialsKeyPrefix+resp.CredentialsTicket) })
- fetchLogic := NewFetchUserCredentialsLogic(ctx, svcCtx)
- cred, err := fetchLogic.FetchUserCredentials(&types.FetchUserCredentialsReq{Ticket: resp.CredentialsTicket})
- require.NoError(t, err)
- assert.Equal(t, username, cred.Username)
- assert.NotEmpty(t, cred.Password)
- msg := util.ValidatePassword(cred.Password)
- assert.Empty(t, msg)
- }
- // TC-1294 正常消费ticket
- func TestFetchUserCredentials_Success(t *testing.T) {
- ctx := ctxhelper.SuperAdminCtx()
- svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
- conn := testutil.GetTestSqlConn()
- username := testutil.UniqueId()
- createLogic := NewCreateUserLogic(ctx, svcCtx)
- createResp, err := createLogic.CreateUser(&types.CreateUserReq{
- Username: username,
- DeptId: 0,
- })
- require.NoError(t, err)
- t.Cleanup(func() { testutil.CleanTable(ctx, conn, "`sys_user`", createResp.Id) })
- fetchLogic := NewFetchUserCredentialsLogic(ctx, svcCtx)
- cred, err := fetchLogic.FetchUserCredentials(&types.FetchUserCredentialsReq{Ticket: createResp.CredentialsTicket})
- require.NoError(t, err)
- assert.Equal(t, username, cred.Username)
- assert.NotEmpty(t, cred.Password)
- }
- // TC-1295 二次消费同一ticket
- func TestFetchUserCredentials_ConsumedTwice(t *testing.T) {
- ctx := ctxhelper.SuperAdminCtx()
- svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
- conn := testutil.GetTestSqlConn()
- username := testutil.UniqueId()
- createLogic := NewCreateUserLogic(ctx, svcCtx)
- createResp, err := createLogic.CreateUser(&types.CreateUserReq{
- Username: username,
- DeptId: 0,
- })
- require.NoError(t, err)
- t.Cleanup(func() { testutil.CleanTable(ctx, conn, "`sys_user`", createResp.Id) })
- fetchLogic := NewFetchUserCredentialsLogic(ctx, svcCtx)
- _, err = fetchLogic.FetchUserCredentials(&types.FetchUserCredentialsReq{Ticket: createResp.CredentialsTicket})
- require.NoError(t, err)
- _, err = fetchLogic.FetchUserCredentials(&types.FetchUserCredentialsReq{Ticket: createResp.CredentialsTicket})
- require.Error(t, err)
- var codeErr *response.CodeError
- require.True(t, errors.As(err, &codeErr))
- assert.Equal(t, 400, codeErr.Code())
- assert.Contains(t, codeErr.Error(), "无效或已过期")
- }
- // TC-1296 空ticket
- func TestFetchUserCredentials_EmptyTicket(t *testing.T) {
- ctx := ctxhelper.SuperAdminCtx()
- svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
- fetchLogic := NewFetchUserCredentialsLogic(ctx, svcCtx)
- _, err := fetchLogic.FetchUserCredentials(&types.FetchUserCredentialsReq{Ticket: ""})
- require.Error(t, err)
- var codeErr *response.CodeError
- require.True(t, errors.As(err, &codeErr))
- assert.Equal(t, 400, codeErr.Code())
- assert.Contains(t, codeErr.Error(), "ticket 不能为空")
- }
- // TC-1297 非法ticket
- func TestFetchUserCredentials_InvalidTicket(t *testing.T) {
- ctx := ctxhelper.SuperAdminCtx()
- svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
- fetchLogic := NewFetchUserCredentialsLogic(ctx, svcCtx)
- _, err := fetchLogic.FetchUserCredentials(&types.FetchUserCredentialsReq{Ticket: "random_garbage_ticket_value"})
- require.Error(t, err)
- var codeErr *response.CodeError
- require.True(t, errors.As(err, &codeErr))
- assert.Equal(t, 400, codeErr.Code())
- assert.Contains(t, codeErr.Error(), "无效或已过期")
- }
- // TC-1298 非ADMIN无权消费
- func TestFetchUserCredentials_MemberForbidden(t *testing.T) {
- ctx := ctxhelper.MemberCtx("test-product")
- svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
- fetchLogic := NewFetchUserCredentialsLogic(ctx, svcCtx)
- _, err := fetchLogic.FetchUserCredentials(&types.FetchUserCredentialsReq{Ticket: "any_ticket"})
- require.Error(t, err)
- var codeErr *response.CodeError
- require.True(t, errors.As(err, &codeErr))
- assert.Equal(t, 403, codeErr.Code())
- }
- // TC-1299 并发消费同一ticket
- func TestFetchUserCredentials_ConcurrentConsume(t *testing.T) {
- ctx := ctxhelper.SuperAdminCtx()
- svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
- conn := testutil.GetTestSqlConn()
- username := testutil.UniqueId()
- createLogic := NewCreateUserLogic(ctx, svcCtx)
- createResp, err := createLogic.CreateUser(&types.CreateUserReq{
- Username: username,
- DeptId: 0,
- })
- require.NoError(t, err)
- t.Cleanup(func() { testutil.CleanTable(ctx, conn, "`sys_user`", createResp.Id) })
- var successCount atomic.Int32
- var wg sync.WaitGroup
- for i := 0; i < 10; i++ {
- wg.Add(1)
- go func() {
- defer wg.Done()
- fetchLogic := NewFetchUserCredentialsLogic(ctx, svcCtx)
- _, ferr := fetchLogic.FetchUserCredentials(&types.FetchUserCredentialsReq{Ticket: createResp.CredentialsTicket})
- if ferr == nil {
- successCount.Add(1)
- }
- }()
- }
- wg.Wait()
- assert.Equal(t, int32(1), successCount.Load())
- }
- // TC-1280 正常创建用户返回ticket
- func TestCreateUser_TicketFlow(t *testing.T) {
- ctx := ctxhelper.SuperAdminCtx()
- svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
- conn := testutil.GetTestSqlConn()
- username := testutil.UniqueId()
- logic := NewCreateUserLogic(ctx, svcCtx)
- resp, err := logic.CreateUser(&types.CreateUserReq{
- Username: username,
- Nickname: "测试用户",
- DeptId: 0,
- })
- require.NoError(t, err)
- require.NotNil(t, resp)
- assert.Greater(t, resp.Id, int64(0))
- assert.NotEmpty(t, resp.CredentialsTicket)
- assert.Greater(t, resp.CredentialsExpiresAt, int64(0))
- t.Cleanup(func() { testutil.CleanTable(ctx, conn, "`sys_user`", resp.Id) })
- }
- // TC-1282 生成密码满足强度要求
- func TestCreateUser_PasswordStrength(t *testing.T) {
- ctx := ctxhelper.SuperAdminCtx()
- svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
- conn := testutil.GetTestSqlConn()
- username := testutil.UniqueId()
- createLogic := NewCreateUserLogic(ctx, svcCtx)
- createResp, err := createLogic.CreateUser(&types.CreateUserReq{
- Username: username,
- DeptId: 0,
- })
- require.NoError(t, err)
- t.Cleanup(func() { testutil.CleanTable(ctx, conn, "`sys_user`", createResp.Id) })
- fetchLogic := NewFetchUserCredentialsLogic(ctx, svcCtx)
- cred, err := fetchLogic.FetchUserCredentials(&types.FetchUserCredentialsReq{Ticket: createResp.CredentialsTicket})
- require.NoError(t, err)
- msg := util.ValidatePassword(cred.Password)
- assert.Empty(t, msg, "generated password should pass strength validation")
- }
- // TC-1284 创建后mustChangePassword=1
- func TestCreateUser_MustChangePassword(t *testing.T) {
- ctx := ctxhelper.SuperAdminCtx()
- svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
- conn := testutil.GetTestSqlConn()
- username := testutil.UniqueId()
- logic := NewCreateUserLogic(ctx, svcCtx)
- resp, err := logic.CreateUser(&types.CreateUserReq{
- Username: username,
- DeptId: 0,
- })
- require.NoError(t, err)
- t.Cleanup(func() {
- testutil.CleanTable(ctx, conn, "`sys_user`", resp.Id)
- svcCtx.Redis.DelCtx(ctx, userCredentialsKeyPrefix+resp.CredentialsTicket)
- })
- user, err := svcCtx.SysUserModel.FindOne(ctx, resp.Id)
- require.NoError(t, err)
- assert.Equal(t, int64(consts.MustChangePasswordYes), user.MustChangePassword)
- }
|