123 lines
3.8 KiB
TypeScript
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('图片尺寸过大')
|
|
})
|
|
})
|