package auth import ( "context" "database/sql" "errors" "strings" "testing" "time" "perms-system-server/internal/loaders" "perms-system-server/internal/middleware" 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" "golang.org/x/crypto/bcrypt" ) func ctxWithUserId(userId int64) context.Context { return middleware.WithUserDetails(context.Background(), &loaders.UserDetails{UserId: userId}) } func insertTestUser(t *testing.T, ctx context.Context, username, password string) int64 { t.Helper() svcCtx := svc.NewServiceContext(testutil.GetTestConfig()) now := time.Now().Unix() res, err := svcCtx.SysUserModel.Insert(ctx, &userModel.SysUser{ Username: username, Password: password, Nickname: "test", Avatar: sql.NullString{}, Email: username + "@test.com", Phone: "13800000000", Remark: "", DeptId: 0, IsSuperAdmin: 2, MustChangePassword: 1, Status: 1, CreateTime: now, UpdateTime: now, }) require.NoError(t, err) id, _ := res.LastInsertId() return id } // TC-0054: 正常修改 func TestChangePassword_Success(t *testing.T) { svcCtx := svc.NewServiceContext(testutil.GetTestConfig()) conn := testutil.GetTestSqlConn() ctx := context.Background() oldPwd := "Oldpass123" newPwd := "Newpass456" username := testutil.UniqueId() hashed := testutil.HashPassword(oldPwd) userId := insertTestUser(t, ctx, username, hashed) t.Cleanup(func() { testutil.CleanTable(ctx, conn, "`sys_user`", userId) }) logic := NewChangePasswordLogic(ctxWithUserId(userId), svcCtx) err := logic.ChangePassword(&types.ChangePasswordReq{ OldPassword: oldPwd, NewPassword: newPwd, }) require.NoError(t, err) updated, err := svcCtx.SysUserModel.FindOne(ctx, userId) require.NoError(t, err) assert.NoError(t, bcrypt.CompareHashAndPassword([]byte(updated.Password), []byte(newPwd))) } // TC-0055: mustChangePassword重置 func TestChangePassword_MustChangePasswordReset(t *testing.T) { svcCtx := svc.NewServiceContext(testutil.GetTestConfig()) conn := testutil.GetTestSqlConn() ctx := context.Background() oldPwd := "Oldpass123" newPwd := "Newpass456" username := testutil.UniqueId() hashed := testutil.HashPassword(oldPwd) userId := insertTestUser(t, ctx, username, hashed) t.Cleanup(func() { testutil.CleanTable(ctx, conn, "`sys_user`", userId) }) logic := NewChangePasswordLogic(ctxWithUserId(userId), svcCtx) err := logic.ChangePassword(&types.ChangePasswordReq{ OldPassword: oldPwd, NewPassword: newPwd, }) require.NoError(t, err) updated, err := svcCtx.SysUserModel.FindOne(ctx, userId) require.NoError(t, err) assert.Equal(t, int64(2), updated.MustChangePassword) } // TC-0056: 原密码错误 func TestChangePassword_WrongOldPassword(t *testing.T) { svcCtx := svc.NewServiceContext(testutil.GetTestConfig()) conn := testutil.GetTestSqlConn() ctx := context.Background() username := testutil.UniqueId() hashed := testutil.HashPassword("Realpass1") userId := insertTestUser(t, ctx, username, hashed) t.Cleanup(func() { testutil.CleanTable(ctx, conn, "`sys_user`", userId) }) logic := NewChangePasswordLogic(ctxWithUserId(userId), svcCtx) err := logic.ChangePassword(&types.ChangePasswordReq{ OldPassword: "Wrongpass1", NewPassword: "Newpass456", }) var codeErr *response.CodeError require.True(t, errors.As(err, &codeErr)) assert.Equal(t, 400, codeErr.Code()) assert.Equal(t, "原密码错误", codeErr.Error()) } // TC-0057: 新密码少于8字符 func TestChangePassword_NewPasswordTooShort(t *testing.T) { svcCtx := svc.NewServiceContext(testutil.GetTestConfig()) logic := NewChangePasswordLogic(ctxWithUserId(1), svcCtx) err := logic.ChangePassword(&types.ChangePasswordReq{ OldPassword: "oldpass", NewPassword: "Pas1234", }) var codeErr *response.CodeError require.True(t, errors.As(err, &codeErr)) assert.Equal(t, 400, codeErr.Code()) assert.Equal(t, "密码长度不能少于8个字符", codeErr.Error()) } // TC-0058: 新密码恰好8字符(含大小写+数字) func TestChangePassword_NewPasswordExactly8Chars(t *testing.T) { svcCtx := svc.NewServiceContext(testutil.GetTestConfig()) conn := testutil.GetTestSqlConn() ctx := context.Background() oldPwd := "Oldpass123" newPwd := "Abcdef1x" username := testutil.UniqueId() hashed := testutil.HashPassword(oldPwd) userId := insertTestUser(t, ctx, username, hashed) t.Cleanup(func() { testutil.CleanTable(ctx, conn, "`sys_user`", userId) }) logic := NewChangePasswordLogic(ctxWithUserId(userId), svcCtx) err := logic.ChangePassword(&types.ChangePasswordReq{ OldPassword: oldPwd, NewPassword: newPwd, }) require.NoError(t, err) updated, err := svcCtx.SysUserModel.FindOne(ctx, userId) require.NoError(t, err) assert.NoError(t, bcrypt.CompareHashAndPassword([]byte(updated.Password), []byte(newPwd))) } // TC-0059: 新密码空字符串 func TestChangePassword_NewPasswordEmpty(t *testing.T) { svcCtx := svc.NewServiceContext(testutil.GetTestConfig()) logic := NewChangePasswordLogic(ctxWithUserId(1), svcCtx) err := logic.ChangePassword(&types.ChangePasswordReq{ OldPassword: "oldpass", NewPassword: "", }) var codeErr *response.CodeError require.True(t, errors.As(err, &codeErr)) assert.Equal(t, 400, codeErr.Code()) assert.Equal(t, "密码长度不能少于8个字符", codeErr.Error()) } // TC-0060: 新密码超过72字符 func TestChangePassword_NewPasswordTooLong(t *testing.T) { svcCtx := svc.NewServiceContext(testutil.GetTestConfig()) longPwd := "A" + strings.Repeat("a", 71) + "1" logic := NewChangePasswordLogic(ctxWithUserId(1), svcCtx) err := logic.ChangePassword(&types.ChangePasswordReq{ OldPassword: "oldpass", NewPassword: longPwd, }) var codeErr *response.CodeError require.True(t, errors.As(err, &codeErr)) assert.Equal(t, 400, codeErr.Code()) assert.Equal(t, "密码长度不能超过72个字符", codeErr.Error()) } // TC-0061: 新密码恰好72字符 func TestChangePassword_NewPasswordExactly72Chars(t *testing.T) { svcCtx := svc.NewServiceContext(testutil.GetTestConfig()) conn := testutil.GetTestSqlConn() ctx := context.Background() oldPwd := "Oldpass123" newPwd := "B" + strings.Repeat("b", 70) + "1" username := testutil.UniqueId() hashed := testutil.HashPassword(oldPwd) userId := insertTestUser(t, ctx, username, hashed) t.Cleanup(func() { testutil.CleanTable(ctx, conn, "`sys_user`", userId) }) logic := NewChangePasswordLogic(ctxWithUserId(userId), svcCtx) err := logic.ChangePassword(&types.ChangePasswordReq{ OldPassword: oldPwd, NewPassword: newPwd, }) require.NoError(t, err) updated, err := svcCtx.SysUserModel.FindOne(ctx, userId) require.NoError(t, err) assert.NoError(t, bcrypt.CompareHashAndPassword([]byte(updated.Password), []byte(newPwd))) } // TC-0062: 新旧密码相同 func TestChangePassword_SameOldAndNew(t *testing.T) { svcCtx := svc.NewServiceContext(testutil.GetTestConfig()) conn := testutil.GetTestSqlConn() ctx := context.Background() pwd := "Samepass123" username := testutil.UniqueId() hashed := testutil.HashPassword(pwd) userId := insertTestUser(t, ctx, username, hashed) t.Cleanup(func() { testutil.CleanTable(ctx, conn, "`sys_user`", userId) }) logic := NewChangePasswordLogic(ctxWithUserId(userId), svcCtx) err := logic.ChangePassword(&types.ChangePasswordReq{ OldPassword: pwd, NewPassword: pwd, }) var codeErr *response.CodeError require.True(t, errors.As(err, &codeErr)) assert.Equal(t, 400, codeErr.Code()) assert.Equal(t, "新密码不能与原密码相同", codeErr.Error()) } // TC-0063: 用户不存在 func TestChangePassword_UserNotFound(t *testing.T) { svcCtx := svc.NewServiceContext(testutil.GetTestConfig()) logic := NewChangePasswordLogic(ctxWithUserId(99999999), svcCtx) err := logic.ChangePassword(&types.ChangePasswordReq{ OldPassword: "Oldpass123", NewPassword: "Newpass456", }) var codeErr *response.CodeError require.True(t, errors.As(err, &codeErr)) assert.Equal(t, 404, codeErr.Code()) assert.Equal(t, "用户不存在", codeErr.Error()) }