gongxiangp2/lib/__tests__/product-package-completion.test.ts
chenpei 02ebaabd0e feat(外部接口): 新增自动上架成套通知能力
- 新增任务-详情全套入库后的自动上架通知与重试补发能力

- 扩展 product-assets 支持按 materialId 拉取正式资源

- 补充后台通知目标管理、投递记录和对接文档
2026-03-25 20:39:56 +08:00

132 lines
3.9 KiB
TypeScript

/** @jest-environment node */
import { completeProductPackageUpload } from '@/lib/product-package-completion'
import { prisma } from '@/lib/prisma'
import { queueTaskDetailFullSetNotification } from '@/lib/external-auto-listing'
import { createMaterialLog } from '@/lib/material-log'
jest.mock('@/lib/prisma', () => ({
prisma: {
material: {
findUnique: jest.fn(),
update: jest.fn(),
},
},
}))
jest.mock('@/lib/product-package', () => ({
assignTaskNoOnComplete: jest.fn().mockResolvedValue(null),
validateTaskNo: jest.fn(() => true),
}))
jest.mock('@/lib/task-material-processor', () => ({
cleanupOnError: jest.fn().mockResolvedValue(undefined),
cleanupTempFiles: jest.fn().mockResolvedValue(undefined),
createZipFromFiles: jest.fn()
.mockResolvedValueOnce({ filePath: 'zip-main', fileName: 'main.zip', fileSize: 11 })
.mockResolvedValueOnce({ filePath: 'zip-sku', fileName: 'sku.zip', fileSize: 22 })
.mockResolvedValueOnce({ filePath: 'zip-detail', fileName: 'detail.zip', fileSize: 33 }),
createZipFromZips: jest.fn().mockResolvedValue({ filePath: 'zip-all', fileName: 'all.zip', fileSize: 66 }),
saveProductPackageAssets: jest.fn().mockResolvedValue(undefined),
}))
jest.mock('@/lib/upload-sessions', () => ({
getUploadedFile: jest.fn(),
}))
jest.mock('@/lib/material-log', () => ({
createMaterialLog: jest.fn().mockResolvedValue(undefined),
}))
jest.mock('@/lib/task-detail-full-set', () => ({
isTaskDetailFullSetFromUploadGroups: jest.fn(() => true),
}))
jest.mock('@/lib/public-display', () => ({
buildTaskDetailPublicDisplayPath: jest.fn((_materialId: string, group: string, fileName: string) => `task-detail/${group}/${fileName}`),
uploadPublicDisplayToS3: jest.fn((_: Buffer, path: string) => Promise.resolve(`https://s3.example/${path}`)),
}))
jest.mock('@/lib/external-auto-listing', () => ({
queueTaskDetailFullSetNotification: jest.fn().mockResolvedValue(undefined),
}))
jest.mock('@/lib/image-processor', () => ({
compressImage: jest.fn(async (buffer: Buffer) => buffer),
sliceImage: jest.fn(async (buffer: Buffer) => [buffer]),
}))
jest.mock('sharp', () => ({
__esModule: true,
default: jest.fn(() => ({
metadata: jest.fn().mockResolvedValue({ width: 800, height: 800 }),
})),
}))
describe('product-package-completion', () => {
beforeEach(() => {
jest.clearAllMocks()
;(prisma.material.findUnique as jest.Mock).mockResolvedValue({
id: 'material-1',
productModel: 'SPU-001',
categoryName: '旗舰店',
previewUrl: '',
user: {
realName: '张三',
username: 'zhangsan',
},
})
;(prisma.material.update as jest.Mock).mockResolvedValue(undefined)
global.fetch = jest.fn(async () =>
new Response(Buffer.from('image-binary'), {
status: 200,
headers: {
'Content-Type': 'image/png',
},
}),
) as any
})
test('全套任务详情完成后应触发自动上架通知排队', async () => {
const result = await completeProductPackageUpload({
materialId: 'material-1',
taskNo: null,
operatorUserId: 'user-1',
assets: {
main: [
{
fileUrl: 'https://img.example/main.png',
fileName: 'main.png',
fileSize: 10,
sortOrder: 0,
},
],
sku: [
{
fileUrl: 'https://img.example/sku.png',
fileName: 'sku.png',
fileSize: 10,
sortOrder: 0,
},
],
detail: [
{
fileUrl: 'https://img.example/detail.png',
fileName: 'detail.png',
fileSize: 10,
sortOrder: 0,
},
],
},
})
expect(result).toEqual({
materialId: 'material-1',
taskNo: null,
isFullSet: true,
})
expect(queueTaskDetailFullSetNotification).toHaveBeenCalledWith('material-1')
expect(createMaterialLog).toHaveBeenCalled()
})
})