2026-05-25 15:36:51 +08:00

186 lines
5.7 KiB
TypeScript

import { NextRequest } from 'next/server'
import { GET, POST } from '../route'
jest.mock('@/lib/auth', () => ({
authOptions: {},
}))
jest.mock('next-auth', () => ({
getServerSession: jest.fn(),
}))
jest.mock('@/lib/prisma', () => ({
prisma: {
material: {
findFirst: jest.fn(),
findUnique: jest.fn(),
},
user: {
findUnique: jest.fn(),
},
},
}))
jest.mock('@/lib/material-share-token', () => ({
createMaterialShareToken: jest.fn(),
findValidMaterialShareToken: jest.fn(),
}))
jest.mock('@/lib/url-rewrite', () => ({
toSharedMaterialAssetProxyUrl: jest.fn((url, token) =>
url ? `/api/materials/asset?url=${encodeURIComponent(url)}&shareToken=${encodeURIComponent(token)}` : null
),
}))
const { getServerSession } = require('next-auth')
const { prisma } = require('@/lib/prisma')
const { createMaterialShareToken, findValidMaterialShareToken } = require('@/lib/material-share-token')
describe('/api/materials/share', () => {
beforeEach(() => {
jest.clearAllMocks()
})
it('未登录时拒绝创建分享票据', async () => {
getServerSession.mockResolvedValue(null)
const response = await POST(
new NextRequest('http://localhost/api/materials/share', {
method: 'POST',
body: JSON.stringify({ materialId: 'material-1' }),
})
)
const data = await response.json()
expect(response.status).toBe(401)
expect(data.error).toBe('请先登录')
expect(createMaterialShareToken).not.toHaveBeenCalled()
})
it('已登录用户可以为未删除素材创建分享票据', async () => {
getServerSession.mockResolvedValue({ user: { id: 'user-1', role: 'USER' } })
prisma.user.findUnique.mockResolvedValue({ id: 'user-1', role: 'USER', isSuperAdmin: false })
prisma.material.findFirst.mockResolvedValue({
id: 'material-1',
userId: 'user-1',
ownerType: 'USER',
ownerId: 'user-1',
createdByUserId: 'user-1',
})
createMaterialShareToken.mockResolvedValue({
token: 'share-token',
expiresAt: new Date('2026-05-31T00:00:00.000Z'),
})
const response = await POST(
new NextRequest('http://localhost/api/materials/share', {
method: 'POST',
body: JSON.stringify({ materialId: 'material-1' }),
})
)
const data = await response.json()
expect(response.status).toBe(200)
expect(prisma.material.findFirst).toHaveBeenCalledWith({
where: { id: 'material-1', deletedAt: null },
select: {
id: true,
userId: true,
ownerType: true,
ownerId: true,
createdByUserId: true,
},
})
expect(createMaterialShareToken).toHaveBeenCalledWith('material-1', 'user-1')
expect(data.shareToken).toBe('share-token')
})
it('无关普通用户不能为别人的素材创建分享票据', async () => {
getServerSession.mockResolvedValue({ user: { id: 'user-3', role: 'USER' } })
prisma.user.findUnique.mockResolvedValue({ id: 'user-3', role: 'USER', isSuperAdmin: false })
prisma.material.findFirst.mockResolvedValue({
id: 'material-1',
userId: 'user-1',
ownerType: 'USER',
ownerId: 'user-1',
createdByUserId: 'user-1',
})
const response = await POST(
new NextRequest('http://localhost/api/materials/share', {
method: 'POST',
body: JSON.stringify({ materialId: 'material-1' }),
})
)
const data = await response.json()
expect(response.status).toBe(403)
expect(data.error).toBe('无权分享该素材')
expect(createMaterialShareToken).not.toHaveBeenCalled()
})
it('没有有效分享票据时拒绝公开读取素材', async () => {
findValidMaterialShareToken.mockResolvedValue(null)
const response = await GET(
new NextRequest('http://localhost/api/materials/share?id=material-1&token=bad-token')
)
const data = await response.json()
expect(response.status).toBe(403)
expect(data.error).toBe('分享链接无效或已过期')
expect(prisma.material.findFirst).not.toHaveBeenCalled()
})
it('有效分享票据可读取分享数据并把图片改成带票据的代理地址', async () => {
findValidMaterialShareToken.mockResolvedValue({ id: 'token-row', materialId: 'material-1' })
prisma.material.findFirst.mockResolvedValue({
id: 'material-1',
previewUrl: 'http://minio:9000/bucket/preview.webp',
productModel: 'SPU-001',
brandName: null,
seriesName: null,
skuCode: null,
materialTexture: null,
categoryName: '店铺',
sceneName: '场景',
materialType: '3DMAX',
renderer: 'CR',
renderTime: null,
imageResolution: null,
colorMode: null,
authorName: '作者',
zipFileName: 'material.zip',
zipFileSize: BigInt(1024),
downloadCount: 0,
likeCount: 0,
displayImages: JSON.stringify(['http://minio:9000/bucket/detail.webp']),
createdAt: new Date('2026-05-24T00:00:00.000Z'),
isPremium: false,
videoCoverUrl: null,
videoPreview540Url: null,
videoPreviewClipUrl: null,
audioUrl: null,
})
const response = await GET(
new NextRequest('http://localhost/api/materials/share?id=material-1&token=share-token')
)
const data = await response.json()
expect(response.status).toBe(200)
expect(findValidMaterialShareToken).toHaveBeenCalledWith('share-token', 'material-1')
expect(prisma.material.findFirst).toHaveBeenCalledWith({
where: { id: 'material-1', deletedAt: null },
select: expect.objectContaining({
id: true,
previewUrl: true,
audioUrl: true,
}),
})
expect(data.zipFileSize).toBe('1024')
expect(data.previewUrl).toContain('shareToken=share-token')
expect(data.displayImages[0]).toContain('shareToken=share-token')
})
})