/** @jest-environment node */ import sharp from 'sharp' import { assertProcessableMetadata, processImage, sliceImage, } from '@/lib/image-processor' describe('sliceImage width guard', () => { test('宽图服务器兜底会先等比缩到 1600px 再切片', async () => { const source = await sharp({ create: { width: 2000, height: 2000, channels: 3, background: { r: 220, g: 220, b: 220 }, }, }).png().toBuffer() const slices = await sliceImage(source, 1500, 200, 1600) const metadata = await Promise.all(slices.map((slice) => sharp(slice).metadata())) expect(slices.length).toBeGreaterThan(0) expect(slices.length).toBeLessThanOrEqual(60) for (const item of metadata) { expect(item.format).toBe('webp') expect(item.width).toBeLessThanOrEqual(1600) expect(item.height).toBeLessThanOrEqual(1500) } }) test('任务详情专用门禁允许超过全局 20000px 但仍在 60 片内的长图', async () => { const source = await sharp({ create: { width: 100, height: 25_000, channels: 3, background: { r: 210, g: 210, b: 210 }, }, }).png().toBuffer() await expect(sliceImage(source, 1500, 200, 1600)).rejects.toThrow('图片切割失败') const slices = await sliceImage(source, 1500, 200, 1600, { maxInputWidth: 12_000, maxInputHeight: 90_000, maxInputPixels: 60_000_000, allowAnimatedFirstFrame: true, }) const metadata = await Promise.all(slices.map((slice) => sharp(slice).metadata())) expect(slices).toHaveLength(17) expect(metadata.every((item) => (item.width || 0) <= 1600 && (item.height || 0) <= 1500)).toBe(true) }) test('动图在任务详情兜底中安全取首帧,默认链路仍拒绝', async () => { const width = 16 const height = 16 const redFrame = Buffer.alloc(width * height * 4) const blueFrame = Buffer.alloc(width * height * 4) for (let index = 0; index < width * height; index += 1) { redFrame[index * 4] = 255 redFrame[index * 4 + 3] = 255 blueFrame[index * 4 + 2] = 255 blueFrame[index * 4 + 3] = 255 } const animatedGif = await sharp(Buffer.concat([redFrame, blueFrame]), { raw: { width, height: height * 2, pageHeight: height, channels: 4 }, }).gif({ loop: 0, delay: [100, 100] }).toBuffer() await expect(processImage(animatedGif)).rejects.toThrow('图片处理失败') const preview = await processImage(animatedGif, { maxWidth: 800, maxHeight: 1200, maxSizeKB: 200, format: 'webp', allowAnimatedFirstFrame: true, maxInputWidth: 12_000, maxInputHeight: 90_000, maxInputPixels: 60_000_000, }) const metadata = await sharp(preview, { animated: true }).metadata() expect(metadata.format).toBe('webp') expect(metadata.pages ?? 1).toBe(1) }) test('任务详情服务器兜底与浏览器保持 12000x90000/60M 输入边界', () => { const taskDetailLimits = { maxInputWidth: 12_000, maxInputHeight: 90_000, maxInputPixels: 60_000_000, allowAnimatedFirstFrame: true, } expect(() => assertProcessableMetadata({ width: 12_001, height: 1_000, pages: 1, } as sharp.Metadata, taskDetailLimits)).toThrow('图片尺寸过大') expect(() => assertProcessableMetadata({ width: 800, height: 75_000, pages: 1, } as sharp.Metadata, taskDetailLimits)).not.toThrow() expect(() => assertProcessableMetadata({ width: 800, height: 75_001, pages: 1, } as sharp.Metadata, taskDetailLimits)).toThrow('图片像素总量过大') expect(() => assertProcessableMetadata({ width: 1, height: 90_001, pages: 1, } as sharp.Metadata, taskDetailLimits)).toThrow('图片尺寸过大') }) })