test(e2e): assert internal-user navbar identity is scoped to that user (#29077)

* test(e2e): assert internal-user navbar identity is scoped to that user

The existing login.spec.ts only checks the admin's navbar identity.
This adds the symmetric check for the internal user — verifying the
account button + dropdown surface the internal user's email, id, and
role, and that no admin-scoped values leak through.

* test(e2e): harden navbar identity test per review feedback

Locate the user dropdown panel by a data-testid on the popupRender div
instead of Ant Design internal + Tailwind class names, so styling
refactors no longer risk breaking the identity-scoping assertions.
Source the seeded user emails/ids from shared constants (match seed.sql)
instead of hardcoding them inline.
This commit is contained in:
ryan-crabbe-berri 2026-05-30 00:29:30 -07:00 • committed by GitHub
parent 892838963c
commit 37e6e2da1c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 55 additions and 1 deletions

View file

@ -5,6 +5,12 @@ export const INTERNAL_USER_STORAGE_PATH = "internalUser.storageState.json";
export const INTERNAL_VIEWER_STORAGE_PATH = "internalViewer.storageState.json";
export const TEAM_ADMIN_STORAGE_PATH = "teamAdmin.storageState.json";
// Seeded user identities (match seed.sql)
export const E2E_PROXY_ADMIN_USER_ID = "e2e-proxy-admin";
export const E2E_PROXY_ADMIN_EMAIL = "admin@test.local";
export const E2E_INTERNAL_USER_ID = "e2e-internal-user";
export const E2E_INTERNAL_USER_EMAIL = "internal@test.local";
// Key aliases for seeded test keys (match seed.sql)
export const E2E_UPDATE_LIMITS_KEY_ALIAS = "e2eUpdateLimitsKey";
export const E2E_DELETE_KEY_ALIAS = "e2eDeleteKey";

View file

@ -0,0 +1,48 @@
import { test, expect } from "@playwright/test";
import {
E2E_INTERNAL_USER_EMAIL,
E2E_INTERNAL_USER_ID,
E2E_PROXY_ADMIN_EMAIL,
E2E_PROXY_ADMIN_USER_ID,
INTERNAL_USER_STORAGE_PATH,
} from "../../constants";
const escapeRegExp = (value: string) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
test.describe("Navbar identity scoping", () => {
test.use({ storageState: INTERNAL_USER_STORAGE_PATH });
test("Internal user navbar dropdown shows their own role and user id, not the admin's", async ({ page }) => {
await page.goto("/ui");
await expect(page.getByText("Virtual Keys")).toBeVisible({ timeout: 10_000 });
// The account menu button carries the user's role and email/id in its
// aria-label (see UserDropdown.tsx). Match by partial role.
const accountButton = page.locator('button[aria-label^="Account menu"]').first();
await expect(accountButton).toHaveAttribute("aria-label", /Internal User/, { timeout: 5_000 });
await expect(accountButton).toHaveAttribute(
"aria-label",
new RegExp(
`signed in as (${escapeRegExp(E2E_INTERNAL_USER_EMAIL)}|${escapeRegExp(E2E_INTERNAL_USER_ID)})`,
),
{ timeout: 5_000 },
);
// Open the dropdown (UserDropdown configures trigger=["click"]).
await accountButton.click();
// Locate the panel by its test id (data-testid on the popupRender div in
// UserDropdown.tsx) rather than Ant/Tailwind class names, so styling
// refactors don't silently break the identity-scoping assertions below.
const popup = page.getByTestId("user-dropdown-panel");
await expect(popup).toBeVisible({ timeout: 5_000 });
// The popup must show the internal user's identity — not the seeded
// proxy admin's email/id, which would indicate a session/scope leak.
await expect(popup.getByText(E2E_INTERNAL_USER_EMAIL)).toBeVisible({ timeout: 5_000 });
await expect(popup.getByText(E2E_INTERNAL_USER_ID)).toBeVisible({ timeout: 5_000 });
await expect(popup.getByText("Internal User", { exact: true })).toBeVisible({ timeout: 5_000 });
await expect(popup.getByText(E2E_PROXY_ADMIN_EMAIL)).toHaveCount(0);
await expect(popup.getByText(E2E_PROXY_ADMIN_USER_ID)).toHaveCount(0);
});
});

View file

@ -221,7 +221,7 @@ const UserDropdown: React.FC<UserDropdownProps> = ({ onLogout }) => {
trigger={["click"]}
menu={{ items: userItems }}
popupRender={(menu) => (
<div className="rounded-lg bg-white shadow-lg">
<div className="rounded-lg bg-white shadow-lg" data-testid="user-dropdown-panel">
{renderUserInfoSection()}
<Divider style={{ margin: 0 }} />
{React.cloneElement(menu as React.ReactElement, {