gongxiangp2/lib/__tests__/image-processor-slice.test.ts

123 lines
3.8 KiB
TypeScript

/** @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('图片尺寸过大')
})
})