mirror of
https://github.com/iflytek/skillhub.git
synced 2026-08-27 11:14:59 +00:00
fix(web): ISSUE-56 show security settings for local accounts
Signed-off-by: dongmucat <1127093059@qq.com>
This commit is contained in:
parent
dc185861d4
commit
be51f173f5
3 changed files with 113 additions and 15 deletions
100
web/e2e/user-menu-security-settings.spec.ts
Normal file
100
web/e2e/user-menu-security-settings.spec.ts
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
import { expect, test, type Page } from '@playwright/test'
|
||||
import { setEnglishLocale } from './helpers/auth-fixtures'
|
||||
|
||||
type AuthUser = {
|
||||
userId: string
|
||||
displayName: string
|
||||
email: string
|
||||
avatarUrl: string
|
||||
oauthProvider?: string
|
||||
platformRoles: string[]
|
||||
}
|
||||
|
||||
function apiEnvelope(data: unknown) {
|
||||
return {
|
||||
code: 0,
|
||||
msg: 'ok',
|
||||
data,
|
||||
timestamp: new Date().toISOString(),
|
||||
requestId: 'user-menu-security-settings',
|
||||
}
|
||||
}
|
||||
|
||||
async function mockSession(page: Page, user: AuthUser) {
|
||||
await page.route('**/api/v1/auth/me', async (route) => {
|
||||
await route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify(apiEnvelope(user)),
|
||||
})
|
||||
})
|
||||
|
||||
await page.route('**/api/web/me/namespaces', async (route) => {
|
||||
await route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify(apiEnvelope([])),
|
||||
})
|
||||
})
|
||||
|
||||
await page.route('**/api/web/notifications/unread-count', async (route) => {
|
||||
await route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify(apiEnvelope({ count: 0 })),
|
||||
})
|
||||
})
|
||||
|
||||
await page.route('**/api/web/notifications/sse', async (route) => {
|
||||
await route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'text/event-stream',
|
||||
body: '',
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
async function openUserMenu(page: Page, displayName: string) {
|
||||
await page.goto('/settings/security')
|
||||
await expect(page.getByRole('heading', { name: 'Security Settings' })).toBeVisible()
|
||||
await page.getByRole('button', { name: displayName }).click()
|
||||
await expect(page.getByRole('menu')).toBeVisible()
|
||||
}
|
||||
|
||||
test.describe('User menu security settings visibility', () => {
|
||||
test.use({ baseURL: 'http://127.0.0.1:3000' })
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await setEnglishLocale(page)
|
||||
})
|
||||
|
||||
test('shows security settings for local password accounts', async ({ page }) => {
|
||||
await mockSession(page, {
|
||||
userId: 'docker-admin',
|
||||
displayName: 'Platform Admin',
|
||||
email: 'admin@skillhub.local',
|
||||
avatarUrl: '',
|
||||
oauthProvider: 'local',
|
||||
platformRoles: ['SUPER_ADMIN'],
|
||||
})
|
||||
|
||||
await openUserMenu(page, 'Platform Admin')
|
||||
|
||||
await expect(page.getByRole('menu').getByRole('link', { name: 'Security Settings' })).toBeVisible()
|
||||
})
|
||||
|
||||
test('hides security settings for OAuth provider accounts', async ({ page }) => {
|
||||
await mockSession(page, {
|
||||
userId: 'oauth-admin',
|
||||
displayName: 'OAuth Admin',
|
||||
email: 'oauth-admin@example.com',
|
||||
avatarUrl: '',
|
||||
oauthProvider: 'github',
|
||||
platformRoles: ['SUPER_ADMIN'],
|
||||
})
|
||||
|
||||
await openUserMenu(page, 'OAuth Admin')
|
||||
|
||||
await expect(page.getByRole('menu').getByRole('link', { name: 'Security Settings' })).toHaveCount(0)
|
||||
})
|
||||
})
|
||||
|
|
@ -1,18 +1,12 @@
|
|||
import { describe, expect, it } from 'vitest'
|
||||
import * as mod from './user-menu'
|
||||
import { canShowSecuritySettings } from './user-menu'
|
||||
|
||||
/**
|
||||
* UserMenu is a React component that renders a hover/click dropdown menu with
|
||||
* role-based navigation links (dashboard, reviews, admin, etc.) and logout.
|
||||
* Internal helpers (hasRole, closeMenu, handleMouseEnter/Leave) and the
|
||||
* menuItemClassName constant are scoped inside the component function.
|
||||
* There are no exported pure helpers or constants to test here.
|
||||
*
|
||||
* We verify the module shape so downstream consumers break fast
|
||||
* if the export contract changes.
|
||||
*/
|
||||
describe('user-menu module exports', () => {
|
||||
it('exports the UserMenu component', () => {
|
||||
expect(mod.UserMenu).toBeTypeOf('function')
|
||||
describe('canShowSecuritySettings', () => {
|
||||
it('allows local password accounts to open security settings', () => {
|
||||
expect(canShowSecuritySettings({ oauthProvider: 'local' })).toBe(true)
|
||||
})
|
||||
|
||||
it('keeps security settings hidden for OAuth provider accounts', () => {
|
||||
expect(canShowSecuritySettings({ oauthProvider: 'github' })).toBe(false)
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -21,6 +21,10 @@ interface UserMenuProps {
|
|||
triggerClassName?: string
|
||||
}
|
||||
|
||||
export function canShowSecuritySettings(user: Pick<User, 'oauthProvider'>) {
|
||||
return !user.oauthProvider || user.oauthProvider === 'local'
|
||||
}
|
||||
|
||||
export function UserMenu({ user, triggerClassName }: UserMenuProps) {
|
||||
const { t } = useTranslation()
|
||||
const queryClient = useQueryClient()
|
||||
|
|
@ -37,7 +41,7 @@ export function UserMenu({ user, triggerClassName }: UserMenuProps) {
|
|||
const isAuditor = hasRole('AUDITOR') || hasRole('SUPER_ADMIN')
|
||||
const isSuperAdmin = hasRole('SUPER_ADMIN')
|
||||
const reviewCenterVisible = canAccessReviewCenter(user.platformRoles, myNamespaces)
|
||||
const isLocalAccount = !user.oauthProvider
|
||||
const isLocalAccount = canShowSecuritySettings(user)
|
||||
const open = isHovered || isClickOpen
|
||||
|
||||
const clearCloseTimer = () => {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue