186 lines
5.7 KiB
TypeScript
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')
|
|
})
|
|
})
|