// 超级管理员 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 }) }) })