gongxiangp2/lib/__tests__/material-edit-routes.test.ts

253 lines
7.3 KiB
TypeScript

/** @jest-environment node */
import { NextRequest } from 'next/server'
import { GET as getMaterialById, PUT as putMaterialById } from '@/app/api/materials/[id]/route'
import { PUT as putMaterialUpdate } from '@/app/api/materials/update/route'
import { getServerSession } from 'next-auth'
import { updateMaterialWithSharedRules } from '@/lib/material-update'
import { prisma } from '@/lib/prisma'
import {
canUserDownloadMaterial,
canUserDownloadSource,
canUserViewMaterial,
isShareLinkAccess,
isSuperAdmin,
isSystemAdmin,
} from '@/lib/department-permission'
import { deriveTaskDetailIsFullSet } from '@/lib/task-detail-full-set'
jest.mock('next-auth', () => ({
getServerSession: jest.fn(),
}))
jest.mock('@/lib/auth', () => ({
authOptions: {},
}))
jest.mock('@/lib/prisma', () => ({
prisma: {
material: {
findFirst: jest.fn(),
},
user: {
findUnique: jest.fn(),
},
},
}))
jest.mock('@/lib/department-permission', () => ({
canUserDownloadMaterial: jest.fn(),
canUserDownloadSource: jest.fn(),
canUserViewMaterial: jest.fn(),
isShareLinkAccess: jest.fn(),
isSuperAdmin: jest.fn(),
isSystemAdmin: jest.fn(),
}))
jest.mock('@/lib/task-detail-full-set', () => ({
deriveTaskDetailIsFullSet: jest.fn(),
}))
jest.mock('@/lib/material-update', () => ({
updateMaterialWithSharedRules: jest.fn(),
}))
describe('material edit routes', () => {
const originalFile = globalThis.File
beforeEach(() => {
jest.clearAllMocks()
;(getServerSession as jest.Mock).mockResolvedValue({
user: {
id: 'user-1',
role: 'USER',
department: '测试部门',
managedDepartment: null,
},
})
;(isShareLinkAccess as jest.Mock).mockReturnValue(false)
;(isSuperAdmin as jest.Mock).mockResolvedValue(false)
;(isSystemAdmin as jest.Mock).mockReturnValue(false)
;(canUserViewMaterial as jest.Mock).mockResolvedValue(true)
;(canUserDownloadMaterial as jest.Mock).mockResolvedValue(true)
;(canUserDownloadSource as jest.Mock).mockResolvedValue(true)
;(prisma.user.findUnique as jest.Mock).mockResolvedValue({ canDownload: true })
;(deriveTaskDetailIsFullSet as jest.Mock).mockReturnValue(false)
;(updateMaterialWithSharedRules as jest.Mock).mockResolvedValue({
material: {
id: 'material-1',
productModel: 'SPU-001',
categoryName: '店铺A',
sceneName: '场景A',
platformName: 'Amazon',
productCategoryName: '椅子',
materialType: '标准模型',
renderer: 'CR',
authorName: '测试作者',
taskNo: null,
taskDetailSourceType: null,
taskStatus: null,
taskFetchedAt: null,
isFullSet: false,
zipFileSize: 0,
},
})
Object.defineProperty(globalThis, 'File', {
configurable: true,
writable: true,
value: undefined,
})
})
afterAll(() => {
Object.defineProperty(globalThis, 'File', {
configurable: true,
writable: true,
value: originalFile,
})
})
function createFormData() {
const formData = new FormData()
formData.append('productModel', 'SPU-001')
formData.append('categoryName', '店铺A')
formData.append('sceneName', '场景A')
formData.append('platformName', 'Amazon')
formData.append('productCategoryName', '椅子')
formData.append('materialType', '标准模型')
formData.append('renderer', 'CR')
formData.append('authorName', '测试作者')
formData.append('userId', 'user-1')
formData.append('taskNo', '')
formData.append('taskDetailSourceType', 'AI')
return formData
}
test('PUT /api/materials/[id] 在没有全局 File 时仍可正常处理表单更新', async () => {
const request = new NextRequest('http://localhost/api/materials/material-1', {
method: 'PUT',
body: createFormData(),
})
const response = await putMaterialById(request, {
params: { id: 'material-1' },
})
const body = await response.json()
expect(response.status).toBe(200)
expect(body.success).toBe(true)
expect(updateMaterialWithSharedRules).toHaveBeenCalledWith(
expect.objectContaining({
materialId: 'material-1',
previewImage: null,
}),
)
})
test('PUT /api/materials/update 在没有全局 File 时仍可正常处理表单更新', async () => {
const formData = createFormData()
formData.append('id', 'material-1')
const request = new NextRequest('http://localhost/api/materials/update', {
method: 'PUT',
body: formData,
})
const response = await putMaterialUpdate(request)
const body = await response.json()
expect(response.status).toBe(200)
expect(body.message).toBe('更新成功')
expect(updateMaterialWithSharedRules).toHaveBeenCalledWith(
expect.objectContaining({
materialId: 'material-1',
previewImage: null,
}),
)
})
test('GET /api/materials/[id] 应将 assets 中的 BigInt fileSize 序列化为字符串', async () => {
;(prisma.material.findFirst as jest.Mock).mockResolvedValue({
id: 'material-1',
userId: 'user-1',
productModel: 'SPU-001',
categoryName: '店铺A',
sceneName: '场景A',
platformName: 'Amazon',
productCategoryName: '椅子',
materialType: '任务-AI原图',
isAiGenerated: false,
renderer: 'CR',
authorName: '测试作者',
previewUrl: 'https://s3.jiansun.vip/preview.webp',
zipFileName: 'material.zip',
zipFilePath: 'uploads/brand-vi/original.pdf',
zipFileSize: BigInt(123),
downloadCount: 1,
likeCount: 2,
displayImages: [],
createdAt: new Date('2026-04-03T00:00:00.000Z'),
videoSourceUrl: null,
videoPreview540Url: null,
videoPreviewClipUrl: null,
videoCoverUrl: null,
videoDuration: null,
videoProcessingStatus: null,
taskNo: 'TASK202603261129596600-01-01',
taskStatus: 'fetched',
taskFetchedAt: new Date('2026-04-03T00:00:01.000Z'),
isFullSet: false,
taskDetailSourceType: '渲染',
user: {
id: 'user-1',
username: '测试用户',
realName: '测试用户',
},
assets: [
{
id: 'asset-1',
assetType: 'zip',
fileUrl: 'uploads/material.zip',
fileName: 'material.zip',
fileSize: BigInt(456),
sortOrder: 0,
},
],
})
const request = new NextRequest('http://localhost/api/materials/material-1', {
method: 'GET',
})
const response = await getMaterialById(request, {
params: { id: 'material-1' },
})
const body = await response.json()
expect(response.status).toBe(200)
expect(body.zipFileSize).toBe('123')
expect(body.assets[0].fileSize).toBe('456')
expect(body.taskDetailSourceType).toBe('渲染')
expect(body.sourceFileUrl).toBe('/api/materials/download?id=material-1')
})
test('PUT 路由应透传任务详情出图方式字段', async () => {
const request = new NextRequest('http://localhost/api/materials/material-1', {
method: 'PUT',
body: createFormData(),
})
await putMaterialById(request, {
params: { id: 'material-1' },
})
expect(updateMaterialWithSharedRules).toHaveBeenCalledWith(
expect.objectContaining({
input: expect.objectContaining({
taskDetailSourceType: 'AI',
}),
}),
)
})
})