177 lines
4.8 KiB
TypeScript
177 lines
4.8 KiB
TypeScript
/** @jest-environment node */
|
|
|
|
import fs from 'fs'
|
|
import path from 'path'
|
|
import { NextRequest } from 'next/server'
|
|
import { getServerSession } from 'next-auth'
|
|
import { GET } from '@/app/api/materials/share/route'
|
|
import { prisma } from '@/lib/prisma'
|
|
|
|
jest.mock('@/lib/prisma', () => ({
|
|
prisma: {
|
|
material: {
|
|
findFirst: jest.fn(),
|
|
},
|
|
materialLike: {
|
|
findFirst: jest.fn(),
|
|
},
|
|
materialFavorite: {
|
|
findFirst: jest.fn(),
|
|
},
|
|
user: {
|
|
findUnique: jest.fn(),
|
|
},
|
|
},
|
|
}))
|
|
|
|
jest.mock('next-auth', () => ({
|
|
getServerSession: jest.fn(),
|
|
}))
|
|
|
|
jest.mock('@/lib/auth', () => ({
|
|
authOptions: {},
|
|
}))
|
|
|
|
function buildMaterial(overrides: Record<string, unknown> = {}) {
|
|
return {
|
|
id: 'material-1',
|
|
previewUrl: '',
|
|
productModel: 'SPU-001',
|
|
reportTitle: null,
|
|
categoryName: '索菲亚',
|
|
sceneName: '客厅',
|
|
platformName: null,
|
|
productCategoryName: null,
|
|
materialType: '图片',
|
|
isAiGenerated: false,
|
|
taskDetailSourceType: null,
|
|
authorName: '测试人员',
|
|
zipFileName: '',
|
|
zipFilePath: '',
|
|
zipFileSize: BigInt(0),
|
|
downloadCount: 0,
|
|
likeCount: 0,
|
|
displayImages: null,
|
|
createdAt: new Date('2026-07-30T00:00:00.000Z'),
|
|
userId: 'owner-1',
|
|
videoSourceUrl: null,
|
|
videoPreview540Url: null,
|
|
videoPreviewClipUrl: null,
|
|
videoCoverUrl: null,
|
|
videoDuration: null,
|
|
videoProcessingStatus: null,
|
|
taskNo: null,
|
|
taskStatus: null,
|
|
taskFetchedAt: null,
|
|
isFullSet: false,
|
|
...overrides,
|
|
}
|
|
}
|
|
|
|
describe('materials share security', () => {
|
|
beforeEach(() => {
|
|
jest.clearAllMocks()
|
|
;(getServerSession as jest.Mock).mockResolvedValue(null)
|
|
;(prisma.material.findFirst as jest.Mock).mockResolvedValue(null)
|
|
;(prisma.materialLike.findFirst as jest.Mock).mockResolvedValue(null)
|
|
;(prisma.materialFavorite.findFirst as jest.Mock).mockResolvedValue(null)
|
|
;(prisma.user.findUnique as jest.Mock).mockResolvedValue(null)
|
|
})
|
|
|
|
test('公开分享只查询未删除素材', async () => {
|
|
const response = await GET(
|
|
new NextRequest('http://localhost/api/materials/share?id=material-deleted'),
|
|
)
|
|
const body = await response.json()
|
|
|
|
expect(response.status).toBe(404)
|
|
expect(body.error).toBe('素材不存在')
|
|
expect(prisma.material.findFirst).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
where: {
|
|
id: 'material-deleted',
|
|
deletedAt: null,
|
|
},
|
|
}),
|
|
)
|
|
})
|
|
|
|
test('鉴权下载响应禁止共享缓存', () => {
|
|
const routeSource = fs.readFileSync(
|
|
path.join(process.cwd(), 'app/api/materials/download/route.ts'),
|
|
'utf8',
|
|
)
|
|
|
|
expect(routeSource).toContain("'Cache-Control': 'private, no-store, max-age=0'")
|
|
expect(routeSource).not.toContain("'Cache-Control': 'public, max-age=86400, must-revalidate'")
|
|
})
|
|
|
|
test('匿名访问只能使用公开分享能力', async () => {
|
|
;(prisma.material.findFirst as jest.Mock).mockResolvedValue(buildMaterial())
|
|
|
|
const response = await GET(
|
|
new NextRequest('http://localhost/api/materials/share?id=material-1'),
|
|
)
|
|
const body = await response.json()
|
|
|
|
expect(response.status).toBe(200)
|
|
expect(body.capabilities).toEqual({
|
|
canFavorite: false,
|
|
canEdit: false,
|
|
canDelete: false,
|
|
})
|
|
})
|
|
|
|
test('素材所有者可收藏和编辑,但不能获得管理员删除权限', async () => {
|
|
;(getServerSession as jest.Mock).mockResolvedValue({
|
|
user: { id: 'owner-1' },
|
|
})
|
|
;(prisma.material.findFirst as jest.Mock).mockResolvedValue(buildMaterial())
|
|
;(prisma.user.findUnique as jest.Mock).mockResolvedValue({
|
|
role: 'USER',
|
|
department: null,
|
|
managedDepartment: null,
|
|
canDownload: false,
|
|
isSuperAdmin: false,
|
|
})
|
|
|
|
const response = await GET(
|
|
new NextRequest('http://localhost/api/materials/share?id=material-1'),
|
|
)
|
|
const body = await response.json()
|
|
|
|
expect(response.status).toBe(200)
|
|
expect(body.capabilities).toEqual({
|
|
canFavorite: true,
|
|
canEdit: true,
|
|
canDelete: false,
|
|
})
|
|
})
|
|
|
|
test('公开详情由服务端返回与写接口一致的操作能力', async () => {
|
|
;(getServerSession as jest.Mock).mockResolvedValue({
|
|
user: { id: 'admin-1' },
|
|
})
|
|
;(prisma.material.findFirst as jest.Mock).mockResolvedValue(buildMaterial())
|
|
;(prisma.user.findUnique as jest.Mock).mockResolvedValue({
|
|
role: 'SYSTEM_ADMIN',
|
|
department: null,
|
|
managedDepartment: null,
|
|
canDownload: false,
|
|
isSuperAdmin: false,
|
|
})
|
|
|
|
const response = await GET(
|
|
new NextRequest('http://localhost/api/materials/share?id=material-1'),
|
|
)
|
|
const body = await response.json()
|
|
|
|
expect(response.status).toBe(200)
|
|
expect(body.capabilities).toEqual({
|
|
canFavorite: true,
|
|
canEdit: true,
|
|
canDelete: true,
|
|
})
|
|
})
|
|
})
|