fix(profiles): restore admin ability to edit other users' profiles

The update_profile endpoint used RequireUserOwner which returns only the
caller's user_id — no role information. The ownership check blocked ALL
non-owner edits with 403, including admins. This regressed in d37b30a
when the old Member model (RequireAdmin) was replaced with UserProfile.

Backend: read X-User-Roles header directly in update_profile (same
pattern as node_tags.py / routes.py) and bypass the ownership check when
the admin role is present. Regular members editing their own profiles
are unaffected — RequireUserOwner stays as the dependency.

Frontend: extract ProfileEditForm component (with data-testids) from
OwnProfileView. PublicProfileView now shows an inline edit form when an
admin views another user's profile. Owner still gets the existing edit
link; non-admins see nothing.

Tests:
- Backend: test_update_profile_admin_can_edit_other (admin edits other
  user's profile, asserts 200 + all fields updated)
- Vitest: 4 new tests — admin button visibility, non-admin hidden,
  owner link vs admin button, form submission to correct endpoint
- E2E: admin-profile-edit.spec.ts (admin edits Mem South's profile,
  verifies persistence; admin on own profile sees no admin button);
  members.spec.ts negative assertion (member sees no admin button)
This commit is contained in:
Louis King
2026-07-24 22:41:32 +01:00
parent 140bd437ed
commit 86c8079fb1
6 changed files with 400 additions and 126 deletions
+25
View File
@@ -28,6 +28,10 @@ NO_ROLES_HEADERS = {
"X-User-Id": TEST_USER_ID,
"X-User-Roles": "",
}
ADMIN_HEADERS = {
"X-User-Id": OTHER_USER_ID,
"X-User-Roles": "admin",
}
class TestListProfiles:
@@ -383,6 +387,27 @@ class TestUpdateProfile:
)
assert response.status_code == 403
def test_update_profile_admin_can_edit_other(
self, client_no_auth, sample_user_profile
):
"""Test that an admin can update another user's profile."""
response = client_no_auth.put(
f"/api/v1/user/profile/{sample_user_profile.id}",
json={
"name": "Admin Edited",
"callsign": "ADM1",
"description": "Admin revised this",
"url": "https://example.com/admin",
},
headers=ADMIN_HEADERS,
)
assert response.status_code == 200
data = response.json()
assert data["name"] == "Admin Edited"
assert data["callsign"] == "ADM1"
assert data["description"] == "Admin revised this"
assert data["url"] == "https://example.com/admin"
def test_update_profile_rejects_missing_user_id(
self, client_no_auth, sample_user_profile
):