/** @jest-environment node */ import { getServerSession } from 'next-auth' import { prisma } from '@/lib/prisma' import { POST } from '@/app/api/materials/upload/product-package/complete/route' import { completeProductPackageUpload, } from '@/lib/product-package-completion' jest.mock('next-auth', () => ({ getServerSession: jest.fn() })) jest.mock('@/lib/auth', () => ({ authOptions: {} })) jest.mock('@/lib/prisma', () => ({ prisma: { material: { findUnique: jest.fn(), }, }, })) jest.mock('@/lib/product-package-completion', () => { const actual = jest.requireActual('@/lib/product-package-completion') return { ...actual, completeProductPackageUpload: jest.fn(), } }) describe('product-package complete public route security', () => { beforeEach(() => { jest.clearAllMocks() ;(getServerSession as jest.Mock).mockResolvedValue({ user: { id: 'user-1' } }) ;(completeProductPackageUpload as jest.Mock).mockResolvedValue({ materialId: 'material-1', taskNo: null, isFullSet: false, }) }) test.each([ ['fileUrl', { fileUrl: 'http://127.0.0.1:3000/internal' }], ['localFilePath', { localFilePath: 'C:\\Windows\\win.ini' }], ])('公开 complete 路由拒绝 %s 来源', async (_source, sourceInput) => { const response = await POST({ json: jest.fn().mockResolvedValue({ materialId: 'material-1', assets: { main: [{ ...sourceInput, fileName: 'main.png', fileSize: 10 }], }, }), } as any) expect(response.status).toBe(400) expect(prisma.material.findUnique).not.toHaveBeenCalled() }) test('非任务详情素材不能使用专用完成接口', async () => { ;(prisma.material.findUnique as jest.Mock).mockResolvedValue({ id: 'material-1', userId: 'user-1', materialType: '3D场景', taskDetailRequesterUserId: 'requester-1', }) const response = await POST({ json: jest.fn().mockResolvedValue({ materialId: 'material-1', assets: { main: [] }, }), } as any) const body = await response.json() expect(response.status).toBe(400) expect(body.error).toContain('不是任务-详情') expect(completeProductPackageUpload).not.toHaveBeenCalled() }) test('缺少需求运营的新流程素材不能完成上传', async () => { ;(prisma.material.findUnique as jest.Mock).mockResolvedValue({ id: 'material-1', userId: 'user-1', materialType: '任务-详情', taskDetailRequesterUserId: null, }) const response = await POST({ json: jest.fn().mockResolvedValue({ materialId: 'material-1', assets: { main: [] }, }), } as any) const body = await response.json() expect(response.status).toBe(400) expect(body.error).toContain('缺少需求运营人员') expect(completeProductPackageUpload).not.toHaveBeenCalled() }) test('数据库连接繁忙时返回可理解的临时故障提示', async () => { ;(prisma.material.findUnique as jest.Mock).mockResolvedValue({ id: 'material-1', userId: 'user-1', materialType: '任务-详情', taskDetailRequesterUserId: 'requester-1', }) ;(completeProductPackageUpload as jest.Mock).mockRejectedValue( Object.assign( new Error('Transaction API error: Unable to start a transaction in the given time.'), { code: 'P2028' }, ), ) const response = await POST({ json: jest.fn().mockResolvedValue({ materialId: 'material-1', assets: { main: [] }, }), } as any) const body = await response.json() expect(response.status).toBe(503) expect(response.headers.get('Retry-After')).toBe('5') expect(body).toEqual({ success: false, code: 'DATABASE_BUSY', error: '系统繁忙,本次上传未完成,请稍后重新上传', }) }) })