test(admin-v2): Phase 2 frontend tests — 61 tests for 5 pages
Some checks failed
CI / Lint & TypeCheck (push) Has been cancelled
CI / Unit Tests (push) Has been cancelled
CI / Build Frontend (push) Has been cancelled
CI / Rust Check (push) Has been cancelled
CI / Security Scan (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled
Some checks failed
CI / Lint & TypeCheck (push) Has been cancelled
CI / Unit Tests (push) Has been cancelled
CI / Build Frontend (push) Has been cancelled
CI / Rust Check (push) Has been cancelled
CI / Security Scan (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled
- Billing (13 tests): plan cards, prices, limits, usage bars, payment flow - ScheduledTasks (16 tests): CRUD table, schedule/target types, color tags - Knowledge (12 tests): 4 tabs, items/categories/search/analytics panels - Roles (12 tests): roles + permission templates tabs - ConfigSync (8 tests): sync log viewer with action labels Fix: Knowledge.tsx missing </Select> and </Modal> closing tags (JSX parse error) Fix: tests/setup.ts added ResizeObserver mock for ProTable compatibility
This commit is contained in:
227
admin-v2/tests/pages/Roles.test.tsx
Normal file
227
admin-v2/tests/pages/Roles.test.tsx
Normal file
@@ -0,0 +1,227 @@
|
||||
import { describe, it, expect, beforeEach, afterEach } from 'vitest'
|
||||
import { render, screen, waitFor } from '@testing-library/react'
|
||||
import { http, HttpResponse } from 'msw'
|
||||
import { setupServer } from 'msw/node'
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
|
||||
|
||||
import Roles from '@/pages/Roles'
|
||||
|
||||
// ── Mock data ──────────────────────────────────────────────────
|
||||
|
||||
const mockRoles = [
|
||||
{
|
||||
id: 'role-admin', name: 'admin', description: '管理员',
|
||||
permissions: ['admin:full'], account_count: 2,
|
||||
created_at: '2026-01-01T00:00:00Z', updated_at: '2026-01-01T00:00:00Z',
|
||||
},
|
||||
{
|
||||
id: 'role-user', name: 'user', description: '普通用户',
|
||||
permissions: ['model:read', 'relay:use', 'config:read', 'prompt:read'],
|
||||
account_count: 15,
|
||||
created_at: '2026-01-01T00:00:00Z', updated_at: '2026-01-01T00:00:00Z',
|
||||
},
|
||||
]
|
||||
|
||||
const mockTemplates = [
|
||||
{
|
||||
id: 'tpl-read-only', name: '只读模板', description: '仅查看权限',
|
||||
permissions: ['model:read', 'config:read'],
|
||||
created_at: '2026-02-01T00:00:00Z', updated_at: '2026-02-01T00:00:00Z',
|
||||
},
|
||||
{
|
||||
id: 'tpl-operator', name: '操作员模板', description: '操作权限',
|
||||
permissions: ['model:read', 'relay:use', 'config:read', 'prompt:read', 'hand:use'],
|
||||
created_at: '2026-02-15T00:00:00Z', updated_at: '2026-02-15T00:00:00Z',
|
||||
},
|
||||
]
|
||||
|
||||
const server = setupServer()
|
||||
|
||||
beforeEach(() => {
|
||||
server.listen({ onUnhandledRequest: 'bypass' })
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
server.close()
|
||||
})
|
||||
|
||||
function renderWithProviders(ui: React.ReactElement) {
|
||||
const queryClient = new QueryClient({
|
||||
defaultOptions: { queries: { retry: false } },
|
||||
})
|
||||
return render(
|
||||
<QueryClientProvider client={queryClient}>
|
||||
{ui}
|
||||
</QueryClientProvider>,
|
||||
)
|
||||
}
|
||||
|
||||
function setupRolesHandlers(overrides: Record<string, unknown> = {}) {
|
||||
server.use(
|
||||
http.get('*/api/v1/roles', () => {
|
||||
return HttpResponse.json(overrides.roles ?? mockRoles)
|
||||
}),
|
||||
http.get('*/api/v1/permission-templates', () => {
|
||||
return HttpResponse.json(overrides.templates ?? mockTemplates)
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
describe('Roles', () => {
|
||||
it('renders page title', () => {
|
||||
setupRolesHandlers()
|
||||
renderWithProviders(<Roles />)
|
||||
expect(screen.getByText('角色与权限')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('displays tabs', () => {
|
||||
setupRolesHandlers()
|
||||
renderWithProviders(<Roles />)
|
||||
// Tabs use label spans with icons
|
||||
expect(screen.getByText('角色')).toBeInTheDocument()
|
||||
expect(screen.getByText('权限模板')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('displays roles in default tab', async () => {
|
||||
setupRolesHandlers()
|
||||
renderWithProviders(<Roles />)
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('admin')).toBeInTheDocument()
|
||||
})
|
||||
expect(screen.getByText('user')).toBeInTheDocument()
|
||||
expect(screen.getByText('管理员')).toBeInTheDocument()
|
||||
expect(screen.getByText('普通用户')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('displays permissions count tags', async () => {
|
||||
setupRolesHandlers()
|
||||
renderWithProviders(<Roles />)
|
||||
|
||||
await waitFor(() => {
|
||||
// "1 项" for admin (1 permission), "4 项" for user (4 permissions)
|
||||
expect(screen.getByText('1 项')).toBeInTheDocument()
|
||||
expect(screen.getByText('4 项')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
it('displays account count column', async () => {
|
||||
setupRolesHandlers()
|
||||
renderWithProviders(<Roles />)
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('admin')).toBeInTheDocument()
|
||||
})
|
||||
// account_count: admin=2, user=15
|
||||
expect(screen.getByText('2')).toBeInTheDocument()
|
||||
expect(screen.getByText('15')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('has 新建角色 button', async () => {
|
||||
setupRolesHandlers()
|
||||
renderWithProviders(<Roles />)
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('新建角色')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
it('renders 操作 column for role rows', async () => {
|
||||
setupRolesHandlers()
|
||||
renderWithProviders(<Roles />)
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('admin')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
// 操作 column header should exist
|
||||
expect(screen.getByText('操作')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('shows empty roles state', async () => {
|
||||
setupRolesHandlers({ roles: [], templates: mockTemplates })
|
||||
renderWithProviders(<Roles />)
|
||||
|
||||
await waitFor(() => {
|
||||
const empties = screen.getAllByText('暂无数据')
|
||||
expect(empties.length).toBeGreaterThanOrEqual(1)
|
||||
})
|
||||
})
|
||||
|
||||
it('switches to templates tab and displays templates', async () => {
|
||||
setupRolesHandlers()
|
||||
renderWithProviders(<Roles />)
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('admin')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
// Click 权限模板 tab
|
||||
screen.getByText('权限模板').click()
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('只读模板')).toBeInTheDocument()
|
||||
})
|
||||
expect(screen.getByText('操作员模板')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('displays template permission counts', async () => {
|
||||
setupRolesHandlers()
|
||||
renderWithProviders(<Roles />)
|
||||
|
||||
screen.getByText('权限模板').click()
|
||||
|
||||
await waitFor(() => {
|
||||
// read-only: 2 permissions, operator: 5 permissions
|
||||
expect(screen.getByText('2 项')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
it('shows empty templates state', async () => {
|
||||
setupRolesHandlers({ roles: mockRoles, templates: [] })
|
||||
renderWithProviders(<Roles />)
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('admin')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
screen.getByText('权限模板').click()
|
||||
|
||||
await waitFor(() => {
|
||||
const empties = screen.getAllByText('暂无数据')
|
||||
expect(empties.length).toBeGreaterThanOrEqual(1)
|
||||
})
|
||||
})
|
||||
|
||||
it('has 新建模板 button in templates tab', async () => {
|
||||
setupRolesHandlers()
|
||||
renderWithProviders(<Roles />)
|
||||
|
||||
screen.getByText('权限模板').click()
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('新建模板')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
it('shows empty on roles API failure', async () => {
|
||||
server.use(
|
||||
http.get('*/api/v1/roles', () => {
|
||||
return HttpResponse.json(
|
||||
{ error: 'internal_error', message: '数据库错误' },
|
||||
{ status: 500 },
|
||||
)
|
||||
}),
|
||||
http.get('*/api/v1/permission-templates', () => {
|
||||
return HttpResponse.json(mockTemplates)
|
||||
}),
|
||||
)
|
||||
renderWithProviders(<Roles />)
|
||||
|
||||
await waitFor(() => {
|
||||
// ProTable shows 暂无数据 when data fetch fails
|
||||
const empties = screen.getAllByText('暂无数据')
|
||||
expect(empties.length).toBeGreaterThanOrEqual(1)
|
||||
})
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user