| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108 |
- package auth
- import (
- "errors"
- "testing"
- "perms-system-server/internal/consts"
- "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/testutil/mocks"
- "perms-system-server/internal/types"
- "github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/require"
- "go.uber.org/mock/gomock"
- "golang.org/x/crypto/bcrypt"
- )
- // ---------------------------------------------------------------------------
- // 覆盖目标:审计 M-R10-4 —— ChangePassword 必须把底层 `userModel.ErrUpdateConflict`
- // 显式映射为 409 "密码已被其他会话修改...";修复前 raw error 会被 rest 兜成 500,
- // 导致前端把"并发冲突"误判为系统故障,也会把告警归到 5xx 噪声池。
- //
- // 口径与 UpdateUserLogic / UpdateUserStatusLogic / UpdateRoleLogic 完全对齐。
- // ---------------------------------------------------------------------------
- // TC-1015: M-R10-4 —— UpdatePassword 返回 ErrUpdateConflict 时,ChangePassword 必须回 409。
- func TestChangePassword_UpdateConflict_Maps409(t *testing.T) {
- ctrl := gomock.NewController(t)
- t.Cleanup(ctrl.Finish)
- const userId = int64(777)
- oldPwd := "Oldpass123"
- newPwd := "Newpass456"
- hashed, err := bcrypt.GenerateFromPassword([]byte(oldPwd), bcrypt.DefaultCost)
- require.NoError(t, err)
- mockUser := mocks.NewMockSysUserModel(ctrl)
- mockUser.EXPECT().FindOne(gomock.Any(), userId).
- Return(&userModel.SysUser{
- Id: userId,
- Username: "m_r10_4_subject",
- Password: string(hashed),
- Status: consts.StatusEnabled,
- UpdateTime: 1000,
- }, nil)
- // 关键:强制底层返回 ErrUpdateConflict。
- mockUser.EXPECT().
- UpdatePassword(gomock.Any(), userId, gomock.Any(), int64(consts.MustChangePasswordNo)).
- Return(userModel.ErrUpdateConflict)
- svcCtx := mocks.NewMockServiceContext(mocks.MockModels{User: mockUser})
- ctx := middleware.WithUserDetails(t.Context(), &loaders.UserDetails{UserId: userId})
- logic := NewChangePasswordLogic(ctx, svcCtx)
- err = logic.ChangePassword(&types.ChangePasswordReq{
- OldPassword: oldPwd,
- NewPassword: newPwd,
- })
- var codeErr *response.CodeError
- require.True(t, errors.As(err, &codeErr), "审计 M-R10-4:必须是 *response.CodeError,否则会被 rest 兜成 500")
- assert.Equal(t, 409, codeErr.Code(), "审计 M-R10-4:ErrUpdateConflict 必须映射为 409 Conflict")
- assert.Contains(t, codeErr.Error(), "密码已被其他会话修改", "审计 M-R10-4:文案与业务契约对齐")
- }
- // TC-1016: M-R10-4 —— 非 ErrUpdateConflict 的原生错误仍应透传(500 由 rest 兜底),
- // 防止修复把所有底层错误都误吞为 409。
- func TestChangePassword_GenericUpdateError_StillPropagates(t *testing.T) {
- ctrl := gomock.NewController(t)
- t.Cleanup(ctrl.Finish)
- const userId = int64(778)
- oldPwd := "Oldpass123"
- newPwd := "Newpass456"
- hashed, err := bcrypt.GenerateFromPassword([]byte(oldPwd), bcrypt.DefaultCost)
- require.NoError(t, err)
- mockUser := mocks.NewMockSysUserModel(ctrl)
- mockUser.EXPECT().FindOne(gomock.Any(), userId).
- Return(&userModel.SysUser{
- Id: userId,
- Username: "m_r10_4_subject2",
- Password: string(hashed),
- Status: consts.StatusEnabled,
- UpdateTime: 2000,
- }, nil)
- genericErr := errors.New("driver: bad connection")
- mockUser.EXPECT().
- UpdatePassword(gomock.Any(), userId, gomock.Any(), int64(consts.MustChangePasswordNo)).
- Return(genericErr)
- svcCtx := mocks.NewMockServiceContext(mocks.MockModels{User: mockUser})
- ctx := middleware.WithUserDetails(t.Context(), &loaders.UserDetails{UserId: userId})
- logic := NewChangePasswordLogic(ctx, svcCtx)
- err = logic.ChangePassword(&types.ChangePasswordReq{
- OldPassword: oldPwd,
- NewPassword: newPwd,
- })
- require.Error(t, err)
- assert.ErrorIs(t, err, genericErr, "审计 M-R10-4:只把 ErrUpdateConflict 映射 409,其余错误原样透传(由 rest 兜 500)")
- var codeErr *response.CodeError
- assert.False(t, errors.As(err, &codeErr), "审计 M-R10-4:非冲突错误不得伪装成 CodeError")
- }
|