151 lines
5.2 KiB
TypeScript
151 lines
5.2 KiB
TypeScript
/** @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()
|
|
})
|
|
})
|