/** @jest-environment node */ import { getServerSession } from 'next-auth' import { prisma } from '@/lib/prisma' import { canDownloadSkuMaterial, canManageSkuMaterial } from '@/app/api/materials/[id]/skus/authorization' import { POST as createSku } from '@/app/api/materials/[id]/skus/route' import { DELETE as deleteSku, GET as getSku, PUT as updateSku, } from '@/app/api/materials/[id]/skus/[skuId]/route' import { POST as uploadSku } from '@/app/api/materials/[id]/skus/upload/route' import { PUT as reorderSkus } from '@/app/api/materials/[id]/skus/reorder/route' import { GET as downloadSku } from '@/app/api/materials/[id]/skus/[skuId]/download/route' import { POST as batchDownloadSkus } from '@/app/api/materials/[id]/skus/batch-download/route' jest.mock('next-auth', () => ({ getServerSession: jest.fn() })) jest.mock('@/lib/auth', () => ({ authOptions: {} })) jest.mock('@/lib/material-log', () => ({ createMaterialLog: jest.fn() })) jest.mock('@/lib/material-download-log', () => ({ ensureMaterialDownloadLogsTable: jest.fn() })) jest.mock('@/lib/file-cleanup', () => ({ deleteFileIfUnreferenced: jest.fn(), deleteLocalFile: jest.fn(), })) jest.mock('@/app/api/materials/[id]/skus/authorization', () => ({ canManageSkuMaterial: jest.fn(), canDownloadSkuMaterial: jest.fn(), })) jest.mock('@/lib/prisma', () => ({ prisma: { material: { findUnique: jest.fn(), findFirst: jest.fn(), update: jest.fn(), }, materialSku: { findFirst: jest.fn(), findMany: jest.fn(), aggregate: jest.fn(), create: jest.fn(), update: jest.fn(), delete: jest.fn(), }, materialAsset: { findMany: jest.fn(), }, user: { findUnique: jest.fn(), findMany: jest.fn(), }, materialDownloadLog: { create: jest.fn(), }, $transaction: jest.fn(), }, })) describe('SKU routes object authorization', () => { const session = { user: { id: 'user-2', role: 'USER', department: '设计部' } } const material = { id: 'material-1', userId: 'user-1', materialType: '标准模型', productModel: 'SPU-1' } beforeEach(() => { jest.clearAllMocks() ;(getServerSession as jest.Mock).mockResolvedValue(session) ;(canManageSkuMaterial as jest.Mock).mockResolvedValue(false) ;(canDownloadSkuMaterial as jest.Mock).mockResolvedValue(false) ;(prisma.user.findUnique as jest.Mock).mockResolvedValue({ canDownload: true }) }) test.each([ ['创建', async () => { ;(prisma.material.findUnique as jest.Mock).mockResolvedValue(material) return createSku({ json: jest.fn() } as any, { params: { id: 'material-1' } }) }], ['上传', async () => { ;(prisma.material.findUnique as jest.Mock).mockResolvedValue(material) return uploadSku({ formData: jest.fn() } as any, { params: { id: 'material-1' } }) }], ['排序', async () => { ;(prisma.material.findUnique as jest.Mock).mockResolvedValue(material) return reorderSkus({ json: jest.fn() } as any, { params: { id: 'material-1' } }) }], ])('非所有者不得%s SKU', async (_label, invoke) => { const response = await invoke() expect(response.status).toBe(403) expect(prisma.materialSku.create).not.toHaveBeenCalled() expect(prisma.materialSku.update).not.toHaveBeenCalled() }) test.each([ ['更新', updateSku], ['删除', deleteSku], ])('非所有者不得%s目标 SKU', async (_label, handler) => { ;(prisma.materialSku.findFirst as jest.Mock).mockResolvedValue({ id: 'sku-1', materialId: 'material-1', material: { userId: 'user-1' }, }) const response = await handler({ json: jest.fn() } as any, { params: { id: 'material-1', skuId: 'sku-1' }, }) expect(response.status).toBe(403) expect(prisma.materialSku.update).not.toHaveBeenCalled() expect(prisma.materialSku.delete).not.toHaveBeenCalled() }) test('SKU 详情查询必须同时绑定 materialId 与 skuId', async () => { ;(prisma.materialSku.findFirst as jest.Mock).mockResolvedValue({ id: 'sku-1', materialId: 'material-1', skuName: 'SKU-1', previewUrl: '', fileName: null, filePath: '', fileSize: BigInt(0), sortOrder: 0, whiteBackgroundUploadedBy: null, }) const response = await getSku({} as any, { params: { id: 'material-1', skuId: 'sku-1' }, }) expect(response.status).toBe(200) expect(prisma.materialSku.findFirst).toHaveBeenCalledWith(expect.objectContaining({ where: { id: 'sku-1', materialId: 'material-1', deletedAt: null }, })) }) test('无素材下载权限时单个和批量下载均在读文件前拒绝', async () => { ;(prisma.material.findFirst as jest.Mock).mockResolvedValue(material) const singleResponse = await downloadSku( { url: 'http://localhost/api/materials/material-1/skus/sku-1/download' } as any, { params: { id: 'material-1', skuId: 'sku-1' } }, ) const batchResponse = await batchDownloadSkus( { json: jest.fn().mockResolvedValue({ skuIds: ['sku-1'] }) } as any, { params: { id: 'material-1' } }, ) expect(singleResponse.status).toBe(403) expect(batchResponse.status).toBe(403) expect(prisma.materialSku.findMany).not.toHaveBeenCalled() }) })