Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions ruoyi-fastapi-backend/module_admin/controller/user_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@
EditUserModel,
ResetPasswordModel,
ResetUserModel,
UpdateUserProfileModel,
UserDetailModel,
UserInfoModel,
UserModel,
UserPageQueryModel,
UserProfileModel,
Expand Down Expand Up @@ -368,12 +368,16 @@ async def change_system_user_profile_avatar(
@Log(title='个人信息', business_type=BusinessType.UPDATE)
async def change_system_user_profile_info(
request: Request,
user_info: UserInfoModel,
user_info: UpdateUserProfileModel,
query_db: Annotated[AsyncSession, DBSessionDependency()],
current_user: Annotated[CurrentUserModel, CurrentUserDependency()],
) -> Response:
edit_user = EditUserModel(
**user_info.model_dump(exclude_unset=True, by_alias=True, exclude={'role_ids', 'post_ids'}),
**user_info.model_dump(
exclude_unset=True,
by_alias=True,
include={'nick_name', 'email', 'phonenumber', 'sex'},
),
userId=current_user.user.user_id,
userName=current_user.user.user_name,
updateBy=current_user.user.user_name,
Expand Down
13 changes: 13 additions & 0 deletions ruoyi-fastapi-backend/module_admin/entity/vo/user_vo.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,19 @@ class UserInfoModel(UserModel):
role: list[RoleModel | None] | None = Field(default=[], description='角色信息')


class UpdateUserProfileModel(BaseModel):
"""
修改用户个人资料模型
"""

model_config = ConfigDict(alias_generator=to_camel, extra='forbid')

nick_name: str | None = Field(default=None, max_length=30, description='用户昵称')
email: str | None = Field(default=None, max_length=50, description='用户邮箱')
phonenumber: str | None = Field(default=None, max_length=11, description='手机号码')
sex: Literal['0', '1', '2'] | None = Field(default=None, description='用户性别(0男 1女 2未知)')


class CurrentUserModel(BaseModel):
model_config = ConfigDict(alias_generator=to_camel)

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import inspect
from types import SimpleNamespace
from unittest.mock import AsyncMock, patch

import pytest
from pydantic import ValidationError

from common.enums import PasswordCharacterType
from module_admin.controller.user_controller import change_system_user_profile_info, reset_system_user_pwd
from module_admin.entity.vo.user_vo import EditUserModel, UpdateUserProfileModel
from module_admin.service.user_service import UserService
from utils.pwd_util import PwdUtil


@pytest.mark.asyncio
async def test_admin_reset_password_uses_front_end_default_rule() -> None:
redis = SimpleNamespace()
request = SimpleNamespace(app=SimpleNamespace(state=SimpleNamespace(redis=redis)))
reset_user = EditUserModel(userId=2, password='abcdef')
current_user = SimpleNamespace(user=SimpleNamespace(admin=True, user_name='admin'))
expected = object()

with (
patch.object(UserService, 'check_user_allowed_services', new_callable=AsyncMock),
patch.object(UserService, 'validate_password_services', new_callable=AsyncMock) as validate_password,
patch.object(
UserService,
'edit_user_services',
new=AsyncMock(return_value=SimpleNamespace(message='重置成功')),
),
patch.object(PwdUtil, 'get_password_hash', return_value='hashed-password'),
patch(
'module_admin.controller.user_controller.ResponseUtil.success',
return_value=expected,
),
):
result = await inspect.unwrap(reset_system_user_pwd)(
request,
reset_user,
object(),
current_user,
object(),
)

assert result is expected
validate_password.assert_awaited_once_with(redis, 'abcdef', PasswordCharacterType.DEFAULT)


@pytest.mark.parametrize(
'management_field',
[
{'deptId': 200},
{'password': 'attacker-controlled'},
{'status': '1'},
{'delFlag': '2'},
{'userType': '99'},
{'createBy': 'attacker'},
],
)
def test_update_user_profile_model_rejects_management_fields(management_field: dict) -> None:
with pytest.raises(ValidationError):
UpdateUserProfileModel(nickName='普通用户', **management_field)


@pytest.mark.asyncio
async def test_update_user_profile_only_forwards_editable_fields() -> None:
request = SimpleNamespace()
user_info = UpdateUserProfileModel(
nickName='新昵称',
email='user@example.com',
phonenumber='13800138000',
sex='2',
)
current_user = SimpleNamespace(
user=SimpleNamespace(
user_id=2,
user_name='user',
role_ids='2',
post_ids='3',
role=[],
)
)
expected = object()

with (
patch.object(
UserService,
'edit_user_services',
new=AsyncMock(return_value=SimpleNamespace(message='更新成功')),
) as edit_user_services,
patch(
'module_admin.controller.user_controller.ResponseUtil.success',
return_value=expected,
),
):
result = await inspect.unwrap(change_system_user_profile_info)(
request,
user_info,
object(),
current_user,
)

assert result is expected
edit_user = edit_user_services.await_args.args[1]
update_fields = edit_user.model_dump(exclude_unset=True)
assert update_fields['nick_name'] == '新昵称'
assert update_fields['email'] == 'user@example.com'
assert update_fields['phonenumber'] == '13800138000'
assert update_fields['sex'] == '2'
assert not {
'dept_id',
'password',
'status',
'del_flag',
'user_type',
'create_by',
'create_time',
}.intersection(update_fields)

This file was deleted.

Loading