import sharp from 'sharp' import { PERFORMANCE_SURVEY_IMAGE_LIMITS, assertPerformanceSurveyImageDimensions, assertPerformanceSurveyImageRequestSize, inspectPerformanceSurveyImage, processPerformanceSurveyImage, processPerformanceSurveyImages, serializePerformanceSurveyImagePayload, } from '@/lib/performance-survey-image-processing' describe('performance survey image processing', () => { test('serially converts a long source image into ordered 1500px WebP slices', async () => { const source = await sharp({ create: { width: 24, height: 3100, channels: 4, background: { r: 32, g: 96, b: 180, alpha: 1 }, }, }).png().toBuffer() const processed = await processPerformanceSurveyImages([{ buffer: source, mimeType: 'image/png', sourceName: 'long-image.png', }]) expect(processed).toHaveLength(3) expect(processed.map((item) => item.sliceIndex)).toEqual([0, 1, 2]) const metadata = await Promise.all(processed.map((item) => sharp(item.buffer).metadata())) expect(metadata.map((item) => item.format)).toEqual(['webp', 'webp', 'webp']) expect(metadata.map((item) => item.height)).toEqual([1500, 1500, 100]) }) test('rejects excessive pixel and per-image slice budgets before processing', () => { expect(() => assertPerformanceSurveyImageDimensions(10_000, 10_000, 'huge.png')).toThrow('像素总量过大') expect(() => assertPerformanceSurveyImageDimensions( 1, PERFORMANCE_SURVEY_IMAGE_LIMITS.sliceHeight * (PERFORMANCE_SURVEY_IMAGE_LIMITS.maxSlicesPerImage + 1), 'too-long.png', )).toThrow(`最多允许 ${PERFORMANCE_SURVEY_IMAGE_LIMITS.maxSlicesPerImage} 个切片`) }) test('rejects more than 12 source images before decoding', async () => { const inputs = Array.from({ length: PERFORMANCE_SURVEY_IMAGE_LIMITS.maxSourceCount + 1 }, (_, index) => ({ buffer: Buffer.from('not-decoded'), mimeType: 'image/png', sourceName: `${index}.png`, })) await expect(processPerformanceSurveyImages(inputs)).rejects.toThrow('最多上传 60 张切片') }) test('downscales wide sources before slicing and creates a lightweight top preview', async () => { const source = await sharp({ create: { width: 2000, height: 3000, channels: 3, background: { r: 210, g: 220, b: 230 }, }, }).jpeg().toBuffer() const input = { buffer: source, mimeType: 'image/jpeg', sourceName: 'wide-long.jpg' } const plan = await inspectPerformanceSurveyImage(input) const processed = await processPerformanceSurveyImage(input, plan) expect(plan.outputWidth).toBe(PERFORMANCE_SURVEY_IMAGE_LIMITS.maxOutputWidth) expect(plan.outputHeight).toBe(2400) expect(processed.slices).toHaveLength(2) const sliceMetadata = await Promise.all(processed.slices.map((item) => sharp(item.buffer).metadata())) expect(sliceMetadata.map((item) => item.width)).toEqual([1600, 1600]) expect(sliceMetadata.map((item) => item.height)).toEqual([1500, 900]) const previewMetadata = await sharp(processed.preview.buffer).metadata() expect(previewMetadata.width).toBeLessThanOrEqual(PERFORMANCE_SURVEY_IMAGE_LIMITS.previewWidth) expect(previewMetadata.height).toBeLessThanOrEqual(PERFORMANCE_SURVEY_IMAGE_LIMITS.previewHeight) }) test('streams processed slices to the uploader without retaining the full slice batch', async () => { const source = await sharp({ create: { width: 40, height: 3100, channels: 3, background: { r: 80, g: 120, b: 160 }, }, }).png().toBuffer() const input = { buffer: source, mimeType: 'image/png', sourceName: 'streamed-long.png' } const received: number[] = [] const processed = await processPerformanceSurveyImage(input, undefined, async (slice) => { received.push(slice.sliceIndex) }) expect(received).toEqual([0, 1, 2]) expect(processed.slices).toEqual([]) expect(processed.preview.buffer.length).toBeGreaterThan(0) }) test('rejects oversized multipart requests before form-data parsing', () => { expect(() => assertPerformanceSurveyImageRequestSize(String(65 * 1024 * 1024))).toThrow('总大小不能超过 60MB') expect(() => assertPerformanceSurveyImageRequestSize(String(60 * 1024 * 1024))).not.toThrow() }) test('serializes structured images and groups legacy slice urls by source image', () => { const structured = serializePerformanceSurveyImagePayload([{ version: 1, sourceName: '详情页', width: 750, height: 3000, previewUrl: 'https://s3.example/preview.webp', previewWidth: 720, previewHeight: 560, slices: [ { url: 'https://s3.example/slice-1.webp', width: 750, height: 1500 }, { url: 'https://s3.example/slice-2.webp', width: 750, height: 1500 }, ], }]) expect(structured.imageUrls).toEqual([ 'https://s3.example/slice-1.webp', 'https://s3.example/slice-2.webp', ]) expect(structured.imageGroups[0].previewUrl).toBe('https://s3.example/preview.webp') const legacy = serializePerformanceSurveyImagePayload([ 'https://s3.example/batch-1-1.webp', 'https://s3.example/batch-1-2.webp', 'https://s3.example/batch-2-1.webp', ]) expect(legacy.imageGroups).toHaveLength(2) expect(legacy.imageGroups[0].slices).toHaveLength(2) expect(legacy.imageGroups[1].slices).toHaveLength(1) }) })