Added user profile modification
This commit is contained in:
@@ -513,3 +513,361 @@ def test_i_cannot_access_protected_route_with_invalid_token(client, mock_auth_se
|
||||
)
|
||||
|
||||
assert response.status_code == 401
|
||||
|
||||
|
||||
def test_i_can_update_my_email(client, mock_auth_service, sample_user):
|
||||
"""
|
||||
Test user can update their own email.
|
||||
|
||||
Verifies that a PATCH request to /auth/me with a new email
|
||||
successfully updates the user's email address.
|
||||
"""
|
||||
updated_user = sample_user.model_copy(update={"email": "newemail@example.com"})
|
||||
mock_auth_service.get_current_user.return_value = sample_user
|
||||
mock_auth_service.update_user.return_value = updated_user
|
||||
|
||||
response = client.patch(
|
||||
"/auth/me",
|
||||
headers={"Authorization": "Bearer sample.access.token"},
|
||||
json={"email": "newemail@example.com"}
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["email"] == "newemail@example.com"
|
||||
mock_auth_service.update_user.assert_called_once()
|
||||
|
||||
|
||||
def test_i_can_update_my_username(client, mock_auth_service, sample_user):
|
||||
"""
|
||||
Test user can update their own username.
|
||||
|
||||
Verifies that a PATCH request to /auth/me with a new username
|
||||
successfully updates the user's username.
|
||||
"""
|
||||
updated_user = sample_user.model_copy(update={"username": "newusername"})
|
||||
mock_auth_service.get_current_user.return_value = sample_user
|
||||
mock_auth_service.update_user.return_value = updated_user
|
||||
|
||||
response = client.patch(
|
||||
"/auth/me",
|
||||
headers={"Authorization": "Bearer sample.access.token"},
|
||||
json={"username": "newusername"}
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["username"] == "newusername"
|
||||
mock_auth_service.update_user.assert_called_once()
|
||||
|
||||
|
||||
def test_i_can_update_my_password(client, mock_auth_service, sample_user):
|
||||
"""
|
||||
Test user can update their own password.
|
||||
|
||||
Verifies that a PATCH request to /auth/me with a new password
|
||||
successfully updates the password (which will be hashed by the service).
|
||||
"""
|
||||
mock_auth_service.get_current_user.return_value = sample_user
|
||||
mock_auth_service.update_user.return_value = sample_user
|
||||
|
||||
response = client.patch(
|
||||
"/auth/me",
|
||||
headers={"Authorization": "Bearer sample.access.token"},
|
||||
json={"password": "NewSecurePass123!"}
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
mock_auth_service.update_user.assert_called_once()
|
||||
# Verify password was included in the update
|
||||
call_args = mock_auth_service.update_user.call_args
|
||||
assert call_args[1]["updates"].password == "NewSecurePass123!"
|
||||
|
||||
|
||||
def test_i_can_update_my_password_and_preserve_session(client, mock_auth_service, sample_user):
|
||||
"""
|
||||
Test user can update password while preserving current session.
|
||||
|
||||
Verifies that when a refresh_token is provided in the request body,
|
||||
it is passed to the service to preserve the current session.
|
||||
"""
|
||||
mock_auth_service.get_current_user.return_value = sample_user
|
||||
mock_auth_service.update_user.return_value = sample_user
|
||||
|
||||
response = client.patch(
|
||||
"/auth/me",
|
||||
headers={"Authorization": "Bearer sample.access.token"},
|
||||
json={
|
||||
"password": "NewSecurePass123!",
|
||||
"refresh_token": "current_refresh_token"
|
||||
}
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
mock_auth_service.update_user.assert_called_once()
|
||||
# Verify refresh_token was passed to preserve session
|
||||
call_args = mock_auth_service.update_user.call_args
|
||||
assert call_args[1]["refresh_token"] == "current_refresh_token"
|
||||
|
||||
|
||||
def test_i_can_update_my_user_settings(client, mock_auth_service, sample_user):
|
||||
"""
|
||||
Test user can update their own settings.
|
||||
|
||||
Verifies that a PATCH request to /auth/me with new user_settings
|
||||
successfully updates the user's custom settings.
|
||||
"""
|
||||
new_settings = {"theme": "dark", "language": "fr", "notifications": True}
|
||||
updated_user = sample_user.model_copy(update={"user_settings": new_settings})
|
||||
mock_auth_service.get_current_user.return_value = sample_user
|
||||
mock_auth_service.update_user.return_value = updated_user
|
||||
|
||||
response = client.patch(
|
||||
"/auth/me",
|
||||
headers={"Authorization": "Bearer sample.access.token"},
|
||||
json={"user_settings": new_settings}
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["user_settings"] == new_settings
|
||||
mock_auth_service.update_user.assert_called_once()
|
||||
|
||||
|
||||
def test_i_can_update_multiple_fields_on_my_profile(client, mock_auth_service, sample_user):
|
||||
"""
|
||||
Test user can update multiple fields simultaneously.
|
||||
|
||||
Verifies that a PATCH request to /auth/me can update multiple
|
||||
fields (email, username, user_settings) at once.
|
||||
"""
|
||||
updated_user = sample_user.model_copy(update={
|
||||
"email": "multiemail@example.com",
|
||||
"username": "multiuser",
|
||||
"user_settings": {"theme": "light"}
|
||||
})
|
||||
mock_auth_service.get_current_user.return_value = sample_user
|
||||
mock_auth_service.update_user.return_value = updated_user
|
||||
|
||||
response = client.patch(
|
||||
"/auth/me",
|
||||
headers={"Authorization": "Bearer sample.access.token"},
|
||||
json={
|
||||
"email": "multiemail@example.com",
|
||||
"username": "multiuser",
|
||||
"user_settings": {"theme": "light"}
|
||||
}
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["email"] == "multiemail@example.com"
|
||||
assert data["username"] == "multiuser"
|
||||
assert data["user_settings"] == {"theme": "light"}
|
||||
mock_auth_service.update_user.assert_called_once()
|
||||
|
||||
|
||||
def test_i_cannot_update_my_profile_without_authentication(client):
|
||||
"""
|
||||
Test updating profile fails without authentication.
|
||||
|
||||
Verifies that a PATCH request to /auth/me without a Bearer token
|
||||
returns 401 Unauthorized status.
|
||||
"""
|
||||
response = client.patch(
|
||||
"/auth/me",
|
||||
json={"username": "shouldfail"}
|
||||
)
|
||||
|
||||
assert response.status_code == 401
|
||||
|
||||
|
||||
def test_i_cannot_update_my_email_to_existing_email(client, mock_auth_service, sample_user):
|
||||
"""
|
||||
Test updating email fails if already exists.
|
||||
|
||||
Verifies that attempting to update email to an already registered
|
||||
email returns 409 Conflict status.
|
||||
"""
|
||||
mock_auth_service.get_current_user.return_value = sample_user
|
||||
mock_auth_service.update_user.side_effect = UserAlreadyExistsError(
|
||||
"Email already in use"
|
||||
)
|
||||
|
||||
response = client.patch(
|
||||
"/auth/me",
|
||||
headers={"Authorization": "Bearer sample.access.token"},
|
||||
json={"email": "existing@example.com"}
|
||||
)
|
||||
|
||||
assert response.status_code == 409
|
||||
assert "already" in response.json()["detail"].lower()
|
||||
|
||||
|
||||
def test_i_cannot_update_my_profile_with_invalid_token(client, mock_auth_service):
|
||||
"""
|
||||
Test updating profile fails with invalid token.
|
||||
|
||||
Verifies that attempting to access /auth/me with an invalid token
|
||||
returns 401 Unauthorized status.
|
||||
"""
|
||||
mock_auth_service.get_current_user.side_effect = InvalidTokenError(
|
||||
"Invalid access token"
|
||||
)
|
||||
|
||||
response = client.patch(
|
||||
"/auth/me",
|
||||
headers={"Authorization": "Bearer invalid.token"},
|
||||
json={"username": "shouldfail"}
|
||||
)
|
||||
|
||||
assert response.status_code == 401
|
||||
|
||||
|
||||
def test_admin_can_update_any_user(client, mock_auth_service, sample_user):
|
||||
"""
|
||||
Test admin can update any user.
|
||||
|
||||
Verifies that an admin can successfully update another user's
|
||||
information via PATCH /auth/users/{user_id}.
|
||||
"""
|
||||
admin_user = sample_user.model_copy(update={"roles": ["admin"]})
|
||||
target_user = sample_user.model_copy(update={"id": "target_user_id", "username": "targetuser"})
|
||||
updated_user = target_user.model_copy(update={"username": "updatedusername"})
|
||||
|
||||
mock_auth_service.get_current_user.return_value = admin_user
|
||||
mock_auth_service.update_user.return_value = updated_user
|
||||
|
||||
response = client.patch(
|
||||
"/auth/users/target_user_id",
|
||||
headers={"Authorization": "Bearer admin.access.token"},
|
||||
json={"username": "updatedusername"}
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["username"] == "updatedusername"
|
||||
mock_auth_service.update_user.assert_called_once()
|
||||
|
||||
|
||||
def test_admin_can_update_user_roles(client, mock_auth_service, sample_user):
|
||||
"""
|
||||
Test admin can update user roles.
|
||||
|
||||
Verifies that an admin can change a user's roles, which is forbidden
|
||||
for regular users on the /auth/me endpoint.
|
||||
"""
|
||||
admin_user = sample_user.model_copy(update={"roles": ["admin"]})
|
||||
target_user = sample_user.model_copy(update={"id": "target_user_id", "roles": ["user"]})
|
||||
updated_user = target_user.model_copy(update={"roles": ["admin", "moderator"]})
|
||||
|
||||
mock_auth_service.get_current_user.return_value = admin_user
|
||||
mock_auth_service.update_user.return_value = updated_user
|
||||
|
||||
response = client.patch(
|
||||
"/auth/users/target_user_id",
|
||||
headers={"Authorization": "Bearer admin.access.token"},
|
||||
json={"roles": ["admin", "moderator"]}
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["roles"] == ["admin", "moderator"]
|
||||
mock_auth_service.update_user.assert_called_once()
|
||||
|
||||
|
||||
def test_admin_can_update_user_is_active(client, mock_auth_service, sample_user):
|
||||
"""
|
||||
Test admin can update user active status.
|
||||
|
||||
Verifies that an admin can activate or deactivate a user account,
|
||||
which is forbidden for regular users on the /auth/me endpoint.
|
||||
"""
|
||||
admin_user = sample_user.model_copy(update={"roles": ["admin"]})
|
||||
target_user = sample_user.model_copy(update={"id": "target_user_id", "is_active": True})
|
||||
updated_user = target_user.model_copy(update={"is_active": False})
|
||||
|
||||
mock_auth_service.get_current_user.return_value = admin_user
|
||||
mock_auth_service.update_user.return_value = updated_user
|
||||
|
||||
response = client.patch(
|
||||
"/auth/users/target_user_id",
|
||||
headers={"Authorization": "Bearer admin.access.token"},
|
||||
json={"is_active": False}
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
# Note: is_active is not in UserResponse, so we just verify the call was made
|
||||
mock_auth_service.update_user.assert_called_once()
|
||||
call_args = mock_auth_service.update_user.call_args
|
||||
assert call_args[1]["updates"].is_active is False
|
||||
|
||||
|
||||
def test_admin_can_update_user_is_verified(client, mock_auth_service, sample_user):
|
||||
"""
|
||||
Test admin can update user verification status.
|
||||
|
||||
Verifies that an admin can change a user's email verification status,
|
||||
which is forbidden for regular users on the /auth/me endpoint.
|
||||
"""
|
||||
admin_user = sample_user.model_copy(update={"roles": ["admin"]})
|
||||
target_user = sample_user.model_copy(update={"id": "target_user_id", "is_verified": False})
|
||||
updated_user = target_user.model_copy(update={"is_verified": True})
|
||||
|
||||
mock_auth_service.get_current_user.return_value = admin_user
|
||||
mock_auth_service.update_user.return_value = updated_user
|
||||
|
||||
response = client.patch(
|
||||
"/auth/users/target_user_id",
|
||||
headers={"Authorization": "Bearer admin.access.token"},
|
||||
json={"is_verified": True}
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
# Note: is_verified is not in UserResponse, so we just verify the call was made
|
||||
mock_auth_service.update_user.assert_called_once()
|
||||
call_args = mock_auth_service.update_user.call_args
|
||||
assert call_args[1]["updates"].is_verified is True
|
||||
|
||||
|
||||
def test_non_admin_cannot_update_other_users(client, mock_auth_service, sample_user):
|
||||
"""
|
||||
Test non-admin cannot update other users.
|
||||
|
||||
Verifies that a regular user (without admin role) cannot access
|
||||
the PATCH /auth/users/{user_id} endpoint and receives 403 Forbidden.
|
||||
"""
|
||||
regular_user = sample_user.model_copy(update={"roles": ["user"]})
|
||||
mock_auth_service.get_current_user.return_value = regular_user
|
||||
|
||||
response = client.patch(
|
||||
"/auth/users/other_user_id",
|
||||
headers={"Authorization": "Bearer user.access.token"},
|
||||
json={"username": "shouldfail"}
|
||||
)
|
||||
|
||||
assert response.status_code == 403
|
||||
assert "admin" in response.json()["detail"].lower()
|
||||
|
||||
|
||||
def test_admin_cannot_update_non_existent_user(client, mock_auth_service, sample_user):
|
||||
"""
|
||||
Test admin cannot update non-existent user.
|
||||
|
||||
Verifies that attempting to update a non-existent user returns
|
||||
404 Not Found status.
|
||||
"""
|
||||
admin_user = sample_user.model_copy(update={"roles": ["admin"]})
|
||||
mock_auth_service.get_current_user.return_value = admin_user
|
||||
mock_auth_service.update_user.side_effect = UserNotFoundError(
|
||||
"User not found"
|
||||
)
|
||||
|
||||
response = client.patch(
|
||||
"/auth/users/non_existent_id",
|
||||
headers={"Authorization": "Bearer admin.access.token"},
|
||||
json={"username": "shouldfail"}
|
||||
)
|
||||
|
||||
assert response.status_code == 404
|
||||
assert "not found" in response.json()["detail"].lower()
|
||||
|
||||
Reference in New Issue
Block a user