/** @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(), }, }, })) jest.mock('next-auth', () => ({ getServerSession: jest.fn(), })) jest.mock('@/lib/auth', () => ({ authOptions: {}, })) describe('materials share security', () => { beforeEach(() => { jest.clearAllMocks() ;(getServerSession as jest.Mock).mockResolvedValue(null) ;(prisma.material.findFirst 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('公开分享响应应包含钉钉审批编号', async () => { ;(prisma.material.findFirst as jest.Mock).mockResolvedValueOnce({ id: 'material-1', previewUrl: '', productModel: 'SPU-001', reportTitle: null, categoryName: '旗舰店', sceneName: '客厅', platformName: '', productCategoryName: '', materialType: '图片', isAiGenerated: false, taskDetailSourceType: null, authorName: '张三', zipFileName: '', zipFilePath: '', zipFileSize: BigInt(0), downloadCount: 0, likeCount: 0, displayImages: [], createdAt: new Date('2026-07-31T00:00:00.000Z'), userId: 'user-1', videoSourceUrl: null, videoPreview540Url: null, videoPreviewClipUrl: null, videoCoverUrl: null, videoDuration: null, videoProcessingStatus: null, taskNo: 'TASK202607310001', dingtalkApprovalNo: '202508211445000157693', taskStatus: 'completed', taskFetchedAt: null, isFullSet: 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.taskNo).toBe('TASK202607310001') expect(body.dingtalkApprovalNo).toBe('202508211445000157693') }) 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'") }) })