47 lines
3.0 KiB
TypeScript
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()
|
|
})
|