gongxiangp2/lib/__tests__/detail-generator-materials.test.ts

47 lines
3.0 KiB
TypeScript

/** @jest-environment node */
import { prisma } from '@/lib/prisma'
import { canUserViewMaterial } from '@/lib/department-permission'
import { canAccessMaterialPrivacy } from '@/lib/task-detail-privacy'
import { findDetailMaterialMatches, readDetailMaterialImage } from '@/lib/detail-generator/materials'
import { getS3ObjectStream } from '@/lib/s3'
jest.mock('next-auth', () => ({ getServerSession: jest.fn() }))
jest.mock('@/lib/auth', () => ({ authOptions: {} }))
jest.mock('@/lib/prisma', () => ({ prisma: { material: { findMany: jest.fn(), findUnique: jest.fn() } } }))
jest.mock('@/lib/department-permission', () => ({ isSuperAdmin: jest.fn(), canUserViewMaterial: jest.fn() }))
jest.mock('@/lib/task-detail-privacy', () => ({ canAccessMaterialPrivacy: jest.fn() }))
jest.mock('@/lib/s3', () => ({ getS3ObjectStream: jest.fn(), isS3Url: jest.fn(), getTrustedS3MutationKey: jest.fn() }))
const material = () => ({ id: 'material-1', productModel: 'SPU-1', materialType: '标准模型', deletedAt: null, isIncompleteShell: false, taskDetailPrivate: false,
skus: [{ id: 'sku-1', skuName: 'SKU-1', filePath: 'uploads/sku.zip', previewUrl: null, deletedAt: null, barcodes: [{ barcode: 'BAR-1' }] }],
assets: [{ id: 'asset-1', materialId: 'material-1', assetType: 'standard-model-white-bg-view:sku-1:front', fileUrl: 'uploads/white/front.png', fileName: '正面.png', skuCode: null, barcodes: [] }],
})
beforeEach(() => {
jest.clearAllMocks()
;(prisma.material.findMany as jest.Mock).mockResolvedValue([material()])
;(prisma.material.findUnique as jest.Mock).mockResolvedValue(material())
;(canUserViewMaterial as jest.Mock).mockResolvedValue(true)
;(canAccessMaterialPrivacy as jest.Mock).mockResolvedValue(true)
})
test('SPU and barcode matches preserve concrete white-background SKU identity', async () => {
const bySpu = await findDetailMaterialMatches('owner-1', 'spu', 'spu-1')
expect(bySpu).toEqual([expect.objectContaining({ productModel: 'SPU-1', variant: 'SKU-1', skuCode: 'SKU-1', assets: [expect.objectContaining({ id: 'asset-1', source: 'asset' })] })])
const byBarcode = await findDetailMaterialMatches('owner-1', 'barcode', 'BAR-1')
expect(byBarcode[0].variant).toBe('SKU-1')
expect(canUserViewMaterial).toHaveBeenCalledWith(undefined, '', undefined, '标准模型', 'owner-1')
})
test('private-material gate runs before type permissions and no private metadata escapes', async () => {
;(canAccessMaterialPrivacy as jest.Mock).mockResolvedValue(false)
expect(await findDetailMaterialMatches('owner-1', 'spu', 'SPU-1')).toEqual([])
await expect(readDetailMaterialImage('owner-1', 'material-1', 'asset', 'asset-1')).rejects.toMatchObject({ status: 403 })
expect(canUserViewMaterial).not.toHaveBeenCalled()
expect(getS3ObjectStream).not.toHaveBeenCalled()
})
test('cross-material source IDs are rejected before reading file bytes', async () => {
await expect(readDetailMaterialImage('owner-1', 'material-1', 'asset', 'foreign-asset')).rejects.toMatchObject({ status: 404 })
expect(getS3ObjectStream).not.toHaveBeenCalled()
})