gongxiangp2/lib/__tests__/materials-route-ai-generated.test.ts

289 lines
8.5 KiB
TypeScript

/** @jest-environment node */
import { NextRequest } from 'next/server'
import { getServerSession } from 'next-auth'
import { GET } from '@/app/api/materials/route'
import { prisma } from '@/lib/prisma'
import { getUserViewableMaterialTypes, isSuperAdmin, isSystemAdmin } from '@/lib/department-permission'
jest.mock('next-auth', () => ({
getServerSession: jest.fn(),
}))
jest.mock('@/lib/auth', () => ({
authOptions: {},
}))
jest.mock('@/lib/prisma', () => ({
prisma: {
material: {
count: jest.fn(),
findFirst: jest.fn(),
findMany: jest.fn(),
},
materialLike: {
findMany: jest.fn(),
},
materialFavorite: {
findMany: jest.fn(),
},
materialAsset: {
findMany: jest.fn(),
},
materialSku: {
findMany: jest.fn(),
},
user: {
findMany: jest.fn(),
},
},
}))
jest.mock('@/lib/department-permission', () => ({
isSystemAdmin: jest.fn(),
getUserViewableMaterialTypes: jest.fn(),
isSuperAdmin: jest.fn(),
}))
jest.mock('@/lib/s3', () => ({
deleteFromS3: jest.fn(),
isS3Url: jest.fn(() => false),
}))
jest.mock('@/lib/file-cleanup', () => ({
deleteFiles: jest.fn(),
}))
jest.mock('@/lib/material-cleanup', () => ({
collectMaterialFiles: jest.fn(),
deleteMaterialRelatedRecords: jest.fn(),
}))
jest.mock('@/lib/material-log', () => ({
createMaterialLog: jest.fn(),
}))
jest.mock('@/lib/material-download-log', () => ({
ensureMaterialDownloadLogsTable: jest.fn(),
}))
jest.mock('@/lib/url-rewrite', () => ({
rewriteMaterialStorageUrlsForClient: jest.fn((value) => value),
rewriteMediaUrlForClient: jest.fn((value) => value),
}))
jest.mock('@/lib/network-detection', () => ({
detectIsIntranet: jest.fn(() => false),
}))
jest.mock('@/lib/product-package', () => ({
getMaterialCoverUrl: jest.fn(),
}))
jest.mock('@/lib/material-type-mapper', () => ({
isProductPackageType: jest.fn((materialType: string) => materialType === '任务-详情'),
}))
describe('materials route ai generated', () => {
beforeEach(() => {
jest.clearAllMocks()
;(getServerSession as jest.Mock).mockResolvedValue({
user: {
id: 'user-1',
role: 'USER',
department: '设计部',
managedDepartment: null,
isSuperAdmin: false,
},
})
;(isSuperAdmin as jest.Mock).mockResolvedValue(false)
;(isSystemAdmin as jest.Mock).mockReturnValue(false)
;(getUserViewableMaterialTypes as jest.Mock).mockResolvedValue(['任务-详情'])
;(prisma.material.count as jest.Mock).mockResolvedValue(1)
;(prisma.material.findMany as jest.Mock).mockResolvedValue([
{
id: 'material-1',
userId: 'user-1',
previewUrl: 'https://example.com/preview.jpg',
productModel: 'SPU-001',
categoryName: '旗舰店',
sceneName: '客厅',
platformName: '天猫',
productCategoryName: '沙发',
materialType: '任务-详情',
renderer: '',
authorName: '张三',
zipFileName: 'sample.zip',
zipFileSize: BigInt(1024),
downloadCount: 0,
likeCount: 0,
createdAt: new Date('2026-03-25T08:00:00.000Z'),
videoSourceUrl: null,
displayImages: [],
videoPreview540Url: null,
videoPreviewClipUrl: null,
videoCoverUrl: null,
videoDuration: null,
videoProcessingStatus: null,
transcodeLastError: null,
transcodeAttempts: 0,
isAiGenerated: true,
taskDetailSourceType: 'AI',
user: {
username: 'zhangsan',
realName: '张三',
avatar: null,
},
assets: [],
},
])
;(prisma.materialLike.findMany as jest.Mock).mockResolvedValue([])
;(prisma.materialFavorite.findMany as jest.Mock).mockResolvedValue([])
;(prisma.materialAsset.findMany as jest.Mock).mockResolvedValue([])
})
test('列表接口应返回 isAiGenerated 标记,供首页展示 AI生成 标签', async () => {
const request = new NextRequest('http://localhost/api/materials?page=1&pageSize=20')
const response = await GET(request)
const body = await response.json()
expect(response.status).toBe(200)
expect(prisma.material.findMany).toHaveBeenCalledWith(
expect.objectContaining({
select: expect.objectContaining({
isAiGenerated: true,
taskDetailSourceType: true,
taskDetailRequesterUserId: true,
taskDetailRequesterName: true,
}),
}),
)
expect(body.materials).toHaveLength(1)
expect(body.materials[0]).toEqual(
expect.objectContaining({
id: 'material-1',
isAiGenerated: true,
taskDetailSourceType: 'AI',
}),
)
})
test('未登录访问列表接口应返回 401', async () => {
;(getServerSession as jest.Mock).mockResolvedValue(null)
const request = new NextRequest('http://localhost/api/materials?page=1&pageSize=20')
const response = await GET(request)
const body = await response.json()
expect(response.status).toBe(401)
expect(body.code).toBe('UNAUTHORIZED')
expect(prisma.material.findMany).not.toHaveBeenCalled()
})
test('普通用户不能通过 deleted=true 查看回收站素材', async () => {
const request = new NextRequest('http://localhost/api/materials?deleted=true&page=1&pageSize=20')
const response = await GET(request)
const body = await response.json()
expect(response.status).toBe(403)
expect(body.code).toBe('FORBIDDEN_DELETED_MATERIALS')
expect(prisma.material.findMany).not.toHaveBeenCalled()
})
test('share 参数不能绕过普通素材列表权限过滤', async () => {
;(getUserViewableMaterialTypes as jest.Mock).mockResolvedValue(['买家秀图片'])
const request = new NextRequest('http://localhost/api/materials?share=1&materialType=任务-详情&page=1&pageSize=20')
const response = await GET(request)
const body = await response.json()
expect(response.status).toBe(200)
expect(body.materials).toEqual([])
expect(body.totalCount).toBe(0)
expect(prisma.material.findMany).not.toHaveBeenCalled()
})
test('任务-页面缺少顶层 previewUrl 时应使用 preview 资产作为首页封面', async () => {
;(getUserViewableMaterialTypes as jest.Mock).mockResolvedValue(['任务-页面'])
;(prisma.material.findMany as jest.Mock).mockResolvedValue([
{
id: 'task-page-1',
userId: 'user-1',
previewUrl: '',
productModel: 'SJ0371',
categoryName: '索菲亚',
sceneName: '书房',
platformName: '',
productCategoryName: '',
materialType: '任务-页面',
renderer: '',
authorName: '张三',
zipFileName: 'task-page.zip',
zipFileSize: BigInt(1024),
downloadCount: 0,
likeCount: 0,
createdAt: new Date('2026-06-26T08:00:00.000Z'),
videoSourceUrl: null,
displayImages: [],
videoPreview540Url: null,
videoPreviewClipUrl: null,
videoCoverUrl: null,
videoDuration: null,
videoProcessingStatus: null,
transcodeLastError: null,
transcodeAttempts: 0,
isAiGenerated: false,
taskDetailSourceType: null,
taskDetailRequesterUserId: null,
taskDetailRequesterName: null,
user: {
username: 'zhangsan',
realName: '张三',
avatar: null,
},
assets: [
{
id: 'preview-1',
assetType: 'preview',
fileUrl: 'https://example.com/task-page-preview.webp',
fileName: 'preview-0.webp',
fileSize: null,
sortOrder: 0,
createdAt: new Date('2026-06-26T08:00:00.000Z'),
updatedAt: new Date('2026-06-26T08:00:00.000Z'),
skuCode: null,
barcodes: [],
},
],
},
])
const response = await GET(
new NextRequest('http://localhost/api/materials?materialType=任务-页面&page=1&pageSize=20'),
)
const body = await response.json()
expect(response.status).toBe(200)
expect(prisma.material.findMany).toHaveBeenCalledWith(
expect.objectContaining({
select: expect.objectContaining({
assets: expect.objectContaining({
where: {
assetType: {
in: ['main-preview', 'sku-preview', 'detail-preview', 'preview'],
},
},
}),
}),
}),
)
expect(body.materials[0].previewUrl).toBe('https://example.com/task-page-preview.webp')
})
})