Files
Agent-System-Frontend/test/super-admin-ui.test.ts

98 lines
5.8 KiB
TypeScript

// 超级管理员 UI 门控(计划 2026-10-19-0113 R3/R4/R8):身份标签可见;建号为管理员与
// 任命/撤销入口只对超级管理员出现;超级管理员不可降级、停用或被普通管理员维护。
// 服务端授权仍为权威,这里只锁定前端呈现与请求体。
import { afterEach, describe, expect, it, vi } from 'vitest'
import { createElement } from 'react'
import { renderToStaticMarkup } from 'react-dom/server'
import { api } from '../src/api'
import { auth } from '../src/auth'
import { accountActionPolicy, accountIdentityKind, canAppointAdmins } from '../src/accountAuthority'
import { AdminAppointmentButton, CreateAdminOption, IdentityChip } from '../src/components/AccountUsersPanel'
afterEach(() => vi.restoreAllMocks())
const superAdmin = { isAdmin: true, isSuperAdmin: true }
const admin = { isAdmin: true, isSuperAdmin: false }
const user = { isAdmin: false, isSuperAdmin: false }
const noop = () => undefined
describe('身份标签', () => {
it('超级管理员、管理员与普通用户各有可辨识标签', () => {
expect(accountIdentityKind(superAdmin)).toBe('super')
expect(accountIdentityKind(admin)).toBe('admin')
expect(accountIdentityKind(user)).toBe('user')
expect(renderToStaticMarkup(createElement(IdentityChip, { account: superAdmin }))).toContain('超级管理员')
expect(renderToStaticMarkup(createElement(IdentityChip, { account: admin }))).toContain('>管理员<')
expect(renderToStaticMarkup(createElement(IdentityChip, { account: user }))).toContain('普通用户')
})
it('isAdmin 不推导超级管理员身份', () => {
expect(accountIdentityKind({ isAdmin: true })).toBe('admin')
})
})
describe('管理员任命入口只对超级管理员开放', () => {
it('建号:超级管理员看到「创建为管理员账号」,普通管理员只见普通建号说明', () => {
expect(canAppointAdmins(superAdmin)).toBe(true)
expect(canAppointAdmins(admin)).toBe(false)
expect(canAppointAdmins(null)).toBe(false)
const superHtml = renderToStaticMarkup(createElement(CreateAdminOption, { me: superAdmin, checked: false, disabled: false, onChange: noop }))
expect(superHtml).toContain('type="checkbox"')
expect(superHtml).toContain('创建为管理员账号')
const adminHtml = renderToStaticMarkup(createElement(CreateAdminOption, { me: admin, checked: false, disabled: false, onChange: noop }))
expect(adminHtml).not.toContain('checkbox')
expect(adminHtml).toContain('仅超级管理员可创建或任命管理员')
})
it('任命/撤销:超级管理员可任命普通用户、撤销管理员;普通管理员看不到任何任命按钮', () => {
expect(accountActionPolicy(superAdmin, user).adminChange).toBe('promote')
expect(accountActionPolicy(superAdmin, admin).adminChange).toBe('demote')
expect(renderToStaticMarkup(createElement(AdminAppointmentButton, { me: superAdmin, account: user, busy: false, onChange: noop }))).toContain('设为管理员')
expect(renderToStaticMarkup(createElement(AdminAppointmentButton, { me: superAdmin, account: admin, busy: false, onChange: noop }))).toContain('撤销管理员')
for (const target of [user, admin, superAdmin]) {
expect(accountActionPolicy(admin, target).adminChange).toBeNull()
expect(renderToStaticMarkup(createElement(AdminAppointmentButton, { me: admin, account: target, busy: false, onChange: noop }))).toBe('')
}
})
})
describe('超级管理员账号受保护', () => {
it('超级管理员本人不可被降级或停用,也不走管理员重置密码', () => {
const self = accountActionPolicy(superAdmin, superAdmin)
expect(self).toMatchObject({ adminChange: null, toggleEnabled: false, resetPassword: false, maintain: true })
expect(self.protectedReason).toContain('不能停用、降级')
expect(renderToStaticMarkup(createElement(AdminAppointmentButton, { me: superAdmin, account: superAdmin, busy: false, onChange: noop }))).toBe('')
})
it('普通管理员不能编辑、停用、降级或重置超级管理员,并获得明确说明', () => {
const policy = accountActionPolicy(admin, superAdmin)
expect(policy).toMatchObject({ maintain: false, toggleEnabled: false, resetPassword: false, adminChange: null })
expect(policy.protectedReason).toContain('超级管理员账号受保护')
})
it('普通账号的既有维护能力不受影响', () => {
expect(accountActionPolicy(admin, user)).toMatchObject({ maintain: true, toggleEnabled: true, resetPassword: true, protectedReason: '' })
})
})
describe('账号管理请求体', () => {
const stub = (body: unknown) => vi.spyOn(auth, 'fetch').mockImplementation(async () => new Response(JSON.stringify(body)))
it('adminSetUserAdmin 以乐观版本 PUT /api/admin/users/:userId/admin', async () => {
const request = stub({ userId: 'u 1' })
await api.adminSetUserAdmin('u 1', 7, true)
const [url, options] = request.mock.calls[0]
expect(url).toBe('/api/admin/users/u%201/admin')
expect(options?.method).toBe('PUT')
expect(JSON.parse(String(options?.body))).toEqual({ expectedStateVersion: 7, isAdmin: true })
})
it('建号只在请求管理员身份时携带 isAdmin', async () => {
const request = stub({ userId: 'u1', owner: 'alice' })
await api.createManagedUser('alice', { name: 'A' })
await api.createManagedUser('bob', {}, true)
await api.createAdminUser('carol', 'C')
await api.createAdminUser('dave', 'D', undefined, true)
const bodies = request.mock.calls.map(call => JSON.parse(String(call[1]?.body)))
expect(bodies[0]).toEqual({ owner: 'alice', profile: { name: 'A' } })
expect(bodies[1]).toEqual({ owner: 'bob', profile: {}, isAdmin: true })
expect(bodies[2]).toEqual({ owner: 'carol', profile: { name: 'C' } })
expect(bodies[3]).toEqual({ owner: 'dave', profile: { name: 'D' }, isAdmin: true })
})
})