gongxiangp2/lib/__tests__/material-query.test.ts

249 lines
7.7 KiB
TypeScript

import { prisma } from '@/lib/prisma'
import {
SPECIAL_INIT_MATERIAL_TYPES,
buildMaterialSearchOrConditions,
buildMaterialWhereFromSearchParams,
createMaterialQueryContext,
isProvablyIncompleteSpecialInitShell,
} from '@/lib/material-query'
import {
getUserViewableMaterialTypes,
isSuperAdmin,
isSystemAdmin,
} from '@/lib/department-permission'
jest.mock('@/lib/prisma', () => ({
prisma: {
materialSku: {
findMany: jest.fn(),
},
user: {
findMany: jest.fn(),
},
},
}))
jest.mock('@/lib/department-permission', () => ({
getUserViewableMaterialTypes: jest.fn(),
isSuperAdmin: jest.fn(),
isSystemAdmin: jest.fn(),
}))
describe('material search conditions', () => {
beforeEach(() => {
jest.clearAllMocks()
;(prisma.materialSku.findMany as jest.Mock).mockResolvedValue([])
;(prisma.user.findMany as jest.Mock).mockResolvedValue([])
;(getUserViewableMaterialTypes as jest.Mock).mockResolvedValue(['任务-详情'])
;(isSuperAdmin as jest.Mock).mockResolvedValue(false)
;(isSystemAdmin as unknown as jest.Mock).mockReturnValue(false)
})
test('完整素材 UUID 应使用精确匹配', async () => {
const materialId = '954554fb-50b4-45c4-9234-9e94b6948953'
const conditions = await buildMaterialSearchOrConditions(materialId)
expect(conditions).toEqual(expect.arrayContaining([{ id: materialId }]))
})
test('非 UUID 的历史素材 ID 也应使用精确匹配', async () => {
const materialId = 'legacy-material-1'
const conditions = await buildMaterialSearchOrConditions(materialId)
expect(conditions).toEqual(expect.arrayContaining([{ id: materialId }]))
})
test('同一请求的多组筛选条件复用权限和搜索上下文', async () => {
const searchParams = new URLSearchParams('search=GBGJ-BG0070&category=顾家')
const user = {
id: 'user-1',
role: 'USER',
department: '品牌创意部',
}
const context = await createMaterialQueryContext(searchParams, user)
const first = await buildMaterialWhereFromSearchParams(searchParams, user, { context })
const second = await buildMaterialWhereFromSearchParams(searchParams, user, {
context,
exclude: 'category',
})
expect(first.empty).toBe(false)
expect(first.where.categoryName).toBe('顾家')
expect(second.where.categoryName).toBeUndefined()
expect(getUserViewableMaterialTypes).toHaveBeenCalledTimes(1)
expect(isSuperAdmin).toHaveBeenCalledTimes(1)
expect(prisma.materialSku.findMany).toHaveBeenCalledTimes(1)
expect(prisma.user.findMany).toHaveBeenCalledTimes(1)
})
test('复用上下文后仍拒绝普通用户查看回收站素材', async () => {
await expect(
createMaterialQueryContext(
new URLSearchParams('showDeleted=true'),
{ id: 'user-1', role: 'USER', department: '品牌创意部' },
),
).rejects.toMatchObject({
code: 'FORBIDDEN_DELETED_MATERIALS',
status: 403,
})
})
test('复用上下文后仍拦截用户无权查看的素材类型', async () => {
const searchParams = new URLSearchParams('materialType=3D场景')
const user = {
id: 'user-1',
role: 'USER',
department: '品牌创意部',
}
const context = await createMaterialQueryContext(searchParams, user)
const result = await buildMaterialWhereFromSearchParams(searchParams, user, { context })
expect(result.empty).toBe(true)
})
test('超级管理员不读取部门素材权限', async () => {
;(isSuperAdmin as jest.Mock).mockResolvedValue(true)
const context = await createMaterialQueryContext(
new URLSearchParams(),
{ id: 'super-admin', role: 'ADMIN' },
)
expect(context.isSuperAdmin).toBe(true)
expect(getUserViewableMaterialTypes).not.toHaveBeenCalled()
})
test('正常列表应统一隐藏专用 init 产生的无文件未完成空壳', async () => {
;(getUserViewableMaterialTypes as jest.Mock).mockResolvedValue(['任务-页面'])
const result = await buildMaterialWhereFromSearchParams(
new URLSearchParams('materialType=任务-页面'),
{ id: 'user-1', role: 'USER', department: '品牌创意部' },
)
expect(result.empty).toBe(false)
expect(result.where.isIncompleteShell).toBe(false)
expect(result.where.NOT).toBeUndefined()
})
test.each(SPECIAL_INIT_MATERIAL_TYPES)(
'%s 的 uploading 无文件记录应判定为不可见空壳',
(materialType) => {
expect(isProvablyIncompleteSpecialInitShell({
materialType,
taskStatus: 'uploading',
previewUrl: '',
zipFilePath: '',
zipFileName: '',
displayImages: [],
assets: [],
})).toBe(true)
},
)
test.each(SPECIAL_INIT_MATERIAL_TYPES)(
'%s 的 completed 记录即使字段异常为空也不应被空壳规则隐藏',
(materialType) => {
expect(isProvablyIncompleteSpecialInitShell({
materialType,
taskStatus: 'completed',
previewUrl: '',
zipFilePath: '',
zipFileName: '',
displayImages: [],
assets: [],
})).toBe(false)
},
)
test.each([
['previewUrl', { previewUrl: '/uploads/preview.jpg' }],
['zipFilePath', { zipFilePath: '/uploads/source.zip' }],
['zipFileName', { zipFileName: 'https://storage.example/source.zip' }],
['displayImages', { displayImages: ['/uploads/display.jpg'] }],
['assets', { assets: [{ id: 'asset-1' }] }],
])('有真实 %s 证据的 uploading 记录应继续可见', (_label, evidence) => {
expect(isProvablyIncompleteSpecialInitShell({
materialType: '任务-页面',
taskStatus: 'uploading',
previewUrl: '',
zipFilePath: '',
zipFileName: '',
displayImages: [],
assets: [],
...evidence,
})).toBe(false)
})
test('product-package init 的 null 状态只有具备专属初始化标记时才隐藏', () => {
expect(isProvablyIncompleteSpecialInitShell({
materialType: '任务-详情',
taskStatus: null,
previewUrl: '',
zipFilePath: '',
zipFileName: '',
displayImages: [],
assets: [],
taskDetailSourceType: 'AI',
taskDetailRequesterUserId: 'requester-1',
})).toBe(true)
})
test('历史任务详情的有效 null 状态没有新 init 双标记时不应误隐藏', () => {
expect(isProvablyIncompleteSpecialInitShell({
materialType: '任务-详情',
taskStatus: null,
previewUrl: '',
zipFilePath: '',
zipFileName: '',
displayImages: [],
assets: [],
taskDetailSourceType: null,
taskDetailRequesterUserId: null,
})).toBe(false)
})
test('month=2026 按整年筛选', async () => {
;(isSuperAdmin as jest.Mock).mockResolvedValue(true)
const result = await buildMaterialWhereFromSearchParams(
new URLSearchParams('month=2026'),
{ id: 'super-admin', role: 'ADMIN' },
)
expect(result.where.createdAt).toEqual({
gte: new Date(2026, 0, 1),
lt: new Date(2027, 0, 1),
})
})
test('month=2026-08 仍按单月筛选', async () => {
;(isSuperAdmin as jest.Mock).mockResolvedValue(true)
const result = await buildMaterialWhereFromSearchParams(
new URLSearchParams('month=2026-08'),
{ id: 'super-admin', role: 'ADMIN' },
)
expect(result.where.createdAt).toEqual({
gte: new Date(2026, 7, 1),
lt: new Date(2026, 8, 1),
})
})
test('回收站查询不应套用任务-页面空记录隐藏条件', async () => {
;(isSuperAdmin as jest.Mock).mockResolvedValue(true)
const result = await buildMaterialWhereFromSearchParams(
new URLSearchParams('showDeleted=true'),
{ id: 'super-admin', role: 'ADMIN' },
)
expect(result.where.deletedAt).toEqual({ not: null })
expect(result.where.NOT).toBeUndefined()
})
})