import { NextRequest } from 'next/server' jest.mock('@/lib/auth', () => ({ authOptions: {}, })) jest.mock('next-auth', () => ({ getServerSession: jest.fn(), })) jest.mock('@/lib/prisma', () => ({ prisma: { $executeRawUnsafe: jest.fn(), $queryRawUnsafe: jest.fn(), $transaction: jest.fn(), material: { updateMany: jest.fn(), }, user: { findUnique: jest.fn(), }, }, })) jest.mock('@/lib/s3', () => ({ deleteFromS3: jest.fn(), isS3Url: jest.fn(() => false), })) jest.mock('@/lib/system-integrity', () => ({ checkSystemIntegrity: jest.fn(), repairSystemIntegrity: jest.fn(), ensureBuiltinTemplates: jest.fn(), })) jest.mock('fs/promises', () => ({ access: jest.fn(), unlink: jest.fn(), })) const { getServerSession } = require('next-auth') const { prisma } = require('@/lib/prisma') const { repairSystemIntegrity } = require('@/lib/system-integrity') import { POST as cleanupDatabase } from '../cleanup-database/route' import { POST as migrateMaterialFields } from '../migrate-material-fields/route' import { POST as resetMaterialRankings } from '../reset-material-rankings/route' import { POST as transferMaterialOwner } from '../materials/transfer-owner/route' import { POST as repairSystemIntegrityRoute } from '../../system/integrity/route' describe('admin destructive actions require super admin', () => { beforeEach(() => { jest.resetAllMocks() }) it('普通管理员不能清理数据库', async () => { getServerSession.mockResolvedValue({ user: { id: 'admin-1', role: 'ADMIN', isSuperAdmin: false }, }) prisma.user.findUnique.mockResolvedValue({ isSuperAdmin: false }) const response = await cleanupDatabase( new NextRequest('http://localhost/api/admin/cleanup-database', { method: 'POST' }) ) const data = await response.json() expect(response.status).toBe(403) expect(data.error).toContain('最高权限') expect(prisma.user.findUnique).toHaveBeenCalledWith({ where: { id: 'admin-1' }, select: { isSuperAdmin: true, isActive: true }, }) expect(prisma.$executeRawUnsafe).not.toHaveBeenCalled() expect(prisma.$queryRawUnsafe).not.toHaveBeenCalled() }) it('最高权限账号可以清理数据库', async () => { getServerSession.mockResolvedValue({ user: { id: 'super-1', role: 'ADMIN', isSuperAdmin: true }, }) prisma.user.findUnique.mockResolvedValue({ isSuperAdmin: true }) prisma.$executeRawUnsafe .mockResolvedValueOnce(1) .mockResolvedValueOnce(2) .mockResolvedValueOnce(3) prisma.$queryRawUnsafe .mockResolvedValueOnce([]) .mockResolvedValueOnce([ { table_name: 'invitations', count: 0 }, { table_name: 'danmakus', count: 0 }, { table_name: 'materials', count: 0 }, { table_name: 'users', count: 1 }, ]) const response = await cleanupDatabase( new NextRequest('http://localhost/api/admin/cleanup-database', { method: 'POST', body: JSON.stringify({ confirmPhrase: '清理全部素材' }), }) ) const data = await response.json() expect(response.status).toBe(200) expect(data.success).toBe(true) expect(prisma.$executeRawUnsafe).toHaveBeenCalled() }) it('最高权限账号清理数据库时也必须输入确认短语', async () => { getServerSession.mockResolvedValue({ user: { id: 'super-1', role: 'ADMIN', isSuperAdmin: true }, }) prisma.user.findUnique.mockResolvedValue({ isSuperAdmin: true }) const response = await cleanupDatabase( new NextRequest('http://localhost/api/admin/cleanup-database', { method: 'POST', body: JSON.stringify({ confirmPhrase: '删除' }), }) ) const data = await response.json() expect(response.status).toBe(400) expect(data.error).toContain('确认短语') expect(data.requiredConfirmPhrase).toBe('清理全部素材') expect(prisma.$executeRawUnsafe).not.toHaveBeenCalled() expect(prisma.$queryRawUnsafe).not.toHaveBeenCalled() }) it('普通管理员不能重置排行榜数据', async () => { getServerSession.mockResolvedValue({ user: { id: 'admin-1', role: 'ADMIN', isSuperAdmin: false }, }) prisma.user.findUnique.mockResolvedValue({ isSuperAdmin: false }) const response = await resetMaterialRankings( new NextRequest('http://localhost/api/admin/reset-material-rankings', { method: 'POST' }) ) const data = await response.json() expect(response.status).toBe(403) expect(data.error).toContain('最高权限') expect(prisma.$transaction).not.toHaveBeenCalled() }) it('即使旧会话带最高权限标记,只要数据库已撤销也不能重置排行榜数据', async () => { getServerSession.mockResolvedValue({ user: { id: 'admin-1', role: 'ADMIN', isSuperAdmin: true }, }) prisma.user.findUnique.mockResolvedValue({ isSuperAdmin: false }) const response = await resetMaterialRankings( new NextRequest('http://localhost/api/admin/reset-material-rankings', { method: 'POST' }) ) const data = await response.json() expect(response.status).toBe(403) expect(data.error).toContain('最高权限') expect(prisma.$transaction).not.toHaveBeenCalled() }) it('最高权限账号可以重置排行榜数据', async () => { getServerSession.mockResolvedValue({ user: { id: 'super-1', role: 'ADMIN', isSuperAdmin: true }, }) prisma.user.findUnique.mockResolvedValue({ isSuperAdmin: true }) const tx = { $executeRawUnsafe: jest .fn() .mockResolvedValueOnce(1) .mockResolvedValueOnce(2) .mockResolvedValueOnce(3), } prisma.$transaction.mockImplementation((callback: any) => callback(tx)) const response = await resetMaterialRankings( new NextRequest('http://localhost/api/admin/reset-material-rankings', { method: 'POST' }) ) const data = await response.json() expect(response.status).toBe(200) expect(data.success).toBe(true) expect(tx.$executeRawUnsafe).toHaveBeenCalledTimes(3) }) it('普通管理员不能批量移交素材归属', async () => { getServerSession.mockResolvedValue({ user: { id: 'admin-1', role: 'ADMIN', isSuperAdmin: false }, }) prisma.user.findUnique.mockResolvedValue({ isSuperAdmin: false }) const response = await transferMaterialOwner( new NextRequest('http://localhost/api/admin/materials/transfer-owner', { method: 'POST', body: JSON.stringify({ materialIds: ['material-1'], targetUserId: 'user-2' }), }) ) const data = await response.json() expect(response.status).toBe(403) expect(data.error).toContain('最高权限') expect(prisma.material.updateMany).not.toHaveBeenCalled() }) it('最高权限账号可以批量移交素材归属', async () => { getServerSession.mockResolvedValue({ user: { id: 'super-1', role: 'ADMIN', isSuperAdmin: true }, }) prisma.user.findUnique .mockResolvedValueOnce({ isSuperAdmin: true }) .mockResolvedValueOnce({ id: 'user-2', username: 'target-user', realName: '目标用户', email: 'target@example.com', }) prisma.material.updateMany.mockResolvedValue({ count: 1 }) const response = await transferMaterialOwner( new NextRequest('http://localhost/api/admin/materials/transfer-owner', { method: 'POST', body: JSON.stringify({ materialIds: ['material-1'], targetUserId: 'user-2' }), }) ) const data = await response.json() expect(response.status).toBe(200) expect(data.success).toBe(true) expect(prisma.material.updateMany).toHaveBeenCalledWith({ where: { deletedAt: null, id: { in: ['material-1'] } }, data: expect.objectContaining({ ownerType: 'USER', ownerId: 'user-2', userId: 'user-2', authorName: '目标用户', }), }) }) it('普通管理员不能执行素材字段迁移', async () => { getServerSession.mockResolvedValue({ user: { id: 'admin-1', role: 'ADMIN', isSuperAdmin: false }, }) prisma.user.findUnique.mockResolvedValue({ isSuperAdmin: false }) const response = await migrateMaterialFields( new NextRequest('http://localhost/api/admin/migrate-material-fields', { method: 'POST' }) ) const data = await response.json() expect(response.status).toBe(403) expect(data.error).toContain('最高权限') expect(prisma.$executeRawUnsafe).not.toHaveBeenCalled() expect(prisma.$queryRawUnsafe).not.toHaveBeenCalled() }) it('最高权限账号可以执行素材字段迁移', async () => { getServerSession.mockResolvedValue({ user: { id: 'super-1', role: 'ADMIN', isSuperAdmin: true }, }) prisma.user.findUnique.mockResolvedValue({ isSuperAdmin: true }) prisma.$executeRawUnsafe.mockResolvedValue(1) prisma.$queryRawUnsafe.mockResolvedValue(1) const response = await migrateMaterialFields( new NextRequest('http://localhost/api/admin/migrate-material-fields', { method: 'POST' }) ) const data = await response.json() expect(response.status).toBe(200) expect(data.success).toBe(true) expect(prisma.$executeRawUnsafe).toHaveBeenCalled() expect(prisma.$queryRawUnsafe).toHaveBeenCalled() }) it('普通管理员不能执行系统完整性修复', async () => { getServerSession.mockResolvedValue({ user: { id: 'admin-1', role: 'ADMIN', isSuperAdmin: false }, }) prisma.user.findUnique.mockResolvedValue({ isSuperAdmin: false }) const response = await repairSystemIntegrityRoute( new NextRequest('http://localhost/api/system/integrity', { method: 'POST', body: JSON.stringify({ fixOrphanedMaterials: true }), }) ) const data = await response.json() expect(response.status).toBe(403) expect(data.error).toContain('最高权限') expect(repairSystemIntegrity).not.toHaveBeenCalled() }) it('最高权限账号可以执行系统完整性修复', async () => { getServerSession.mockResolvedValue({ user: { id: 'super-1', role: 'ADMIN', isSuperAdmin: true }, }) prisma.user.findUnique.mockResolvedValue({ isSuperAdmin: true }) repairSystemIntegrity.mockResolvedValue({ success: true, repaired: 1, errors: [] }) const response = await repairSystemIntegrityRoute( new NextRequest('http://localhost/api/system/integrity', { method: 'POST', body: JSON.stringify({ fixOrphanedMaterials: true }), }) ) const data = await response.json() expect(response.status).toBe(200) expect(data.mode).toBe('full') expect(repairSystemIntegrity).toHaveBeenCalledWith({ fixOrphanedMaterials: true }) }) })