mirror of
https://github.com/ipnet-mesh/meshcore-hub.git
synced 2026-08-12 11:52:44 +02:00
86c8079fb1
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)
55 lines
1.9 KiB
TypeScript
55 lines
1.9 KiB
TypeScript
import { expect, test } from "@playwright/test";
|
|
import { MEMBER_STATE } from "../utils/helpers";
|
|
|
|
const BRAVO_KEY = "b2b0" + "0".repeat(60);
|
|
|
|
test.use({ storageState: MEMBER_STATE });
|
|
|
|
test.describe("members", () => {
|
|
test("lists operators and members", async ({ page }) => {
|
|
await page.goto("/members");
|
|
|
|
// Level 2: the group headings (level 1 is the page title "Members").
|
|
await expect(
|
|
page.getByRole("heading", { name: "Operators", level: 2 }),
|
|
).toBeVisible();
|
|
await expect(
|
|
page.getByRole("heading", { name: "Members", level: 2 }),
|
|
).toBeVisible();
|
|
await expect(page.getByText("Op North")).toBeVisible();
|
|
await expect(page.getByText("Mem South")).toBeVisible();
|
|
expect(await page.getByTestId("member-card").count()).toBeGreaterThanOrEqual(4);
|
|
});
|
|
|
|
test("clicking a member shows the profile page", async ({ page }) => {
|
|
await page.goto("/members");
|
|
|
|
await page
|
|
.getByTestId("member-card")
|
|
.filter({ hasText: "Mem South" })
|
|
.first()
|
|
.click();
|
|
await expect(page).toHaveURL(/\/profile\/[0-9a-f-]{36}/);
|
|
await expect(page.getByText("Mem South").first()).toBeVisible();
|
|
await expect(page.locator('nav[aria-label="Breadcrumb"]')).toBeVisible();
|
|
|
|
// A member viewing another user's profile must NOT see the admin edit
|
|
// button.
|
|
await expect(page.getByTestId("profile-admin-edit")).toHaveCount(0);
|
|
});
|
|
|
|
test("clicking a member node shows the node detail page", async ({ page }) => {
|
|
await page.goto("/members");
|
|
|
|
const card = page
|
|
.getByTestId("member-card")
|
|
.filter({ hasText: "Mem South" })
|
|
.first();
|
|
await card
|
|
.locator(`[data-testid="member-node-badge"][data-node-key="${BRAVO_KEY}"]`)
|
|
.click();
|
|
await expect(page).toHaveURL(new RegExp(`/nodes/${BRAVO_KEY}`));
|
|
await expect(page.getByText("Bravo Node").first()).toBeVisible();
|
|
});
|
|
});
|