170 lines
6.3 KiB
TypeScript
170 lines
6.3 KiB
TypeScript
/** @jest-environment node */
|
|
|
|
import sharp from 'sharp'
|
|
import {
|
|
assertBrandKnowledgePreviewMetadata,
|
|
prepareBrandKnowledgePreview,
|
|
validateBrandKnowledgeUploadedPreviewSlices,
|
|
} from '@/lib/brand-knowledge-preview'
|
|
|
|
describe('brand knowledge long preview processing', () => {
|
|
test('keeps previews at or below 3000px as one image and slices above the boundary', async () => {
|
|
const exactBoundary = await sharp({
|
|
create: {
|
|
width: 800,
|
|
height: 3_000,
|
|
channels: 3,
|
|
background: { r: 238, g: 241, b: 244 },
|
|
},
|
|
}).png().toBuffer()
|
|
const overBoundary = await sharp({
|
|
create: {
|
|
width: 800,
|
|
height: 3_001,
|
|
channels: 3,
|
|
background: { r: 238, g: 241, b: 244 },
|
|
},
|
|
}).png().toBuffer()
|
|
|
|
const exactResult = await prepareBrandKnowledgePreview(exactBoundary, 'image/png')
|
|
const overResult = await prepareBrandKnowledgePreview(overBoundary, 'image/png')
|
|
|
|
await expect(sharp(exactResult.slices[0]).metadata()).resolves.toMatchObject({
|
|
format: 'webp', width: 800, height: 3_000,
|
|
})
|
|
expect(exactResult.slices).toHaveLength(1)
|
|
expect(overResult.slices).toHaveLength(3)
|
|
})
|
|
|
|
test('still slices images above 3000px when width scaling makes the output shorter than 1500px', async () => {
|
|
const wideOverBoundary = await sharp({
|
|
create: {
|
|
width: 4_000,
|
|
height: 3_001,
|
|
channels: 3,
|
|
background: { r: 238, g: 241, b: 244 },
|
|
},
|
|
}).png().toBuffer()
|
|
|
|
const result = await prepareBrandKnowledgePreview(wideOverBoundary, 'image/png')
|
|
const metadata = await Promise.all(result.slices.map((slice) => sharp(slice).metadata()))
|
|
|
|
expect(result.slices).toHaveLength(2)
|
|
expect(metadata.map((item) => [item.width, item.height])).toEqual([
|
|
[1_600, 600],
|
|
[1_600, 600],
|
|
])
|
|
})
|
|
|
|
test('slices a long image into ordered WebP segments suitable for seamless rendering', async () => {
|
|
const source = await sharp({
|
|
create: {
|
|
width: 800,
|
|
height: 4_100,
|
|
channels: 3,
|
|
background: { r: 238, g: 241, b: 244 },
|
|
},
|
|
}).png().toBuffer()
|
|
|
|
const result = await prepareBrandKnowledgePreview(source, 'image/png')
|
|
const metadata = await Promise.all(result.slices.map((slice) => sharp(slice).metadata()))
|
|
|
|
expect(result.source).toMatchObject({ contentType: 'image/png', width: 800, height: 4_100 })
|
|
expect(result.slices).toHaveLength(3)
|
|
expect(metadata.map((item) => item.height)).toEqual([1_500, 1_500, 1_100])
|
|
expect(metadata.every((item) => item.format === 'webp' && item.width === 800)).toBe(true)
|
|
})
|
|
|
|
test('rejects MIME mismatches and animated previews', async () => {
|
|
const png = await sharp({
|
|
create: { width: 20, height: 20, channels: 3, background: '#fff' },
|
|
}).png().toBuffer()
|
|
await expect(prepareBrandKnowledgePreview(png, 'image/jpeg')).rejects.toThrow('内容与声明格式不一致')
|
|
|
|
expect(() => assertBrandKnowledgePreviewMetadata({
|
|
format: 'webp', width: 20, height: 40, pages: 2,
|
|
}, 'image/webp')).toThrow('不支持动画或多页图片')
|
|
})
|
|
|
|
test('enforces the 60 million pixel and long-edge limits before slicing', () => {
|
|
expect(() => assertBrandKnowledgePreviewMetadata({
|
|
format: 'png', width: 10_000, height: 6_001, pages: 1,
|
|
}, 'image/png')).toThrow('6000 万像素')
|
|
expect(() => assertBrandKnowledgePreviewMetadata({
|
|
format: 'webp', width: 800, height: 90_001, pages: 1,
|
|
}, 'image/webp')).toThrow('尺寸过大')
|
|
})
|
|
|
|
test('validates ordered browser WebP slices before storage', async () => {
|
|
const first = await sharp({
|
|
create: { width: 1_600, height: 1_500, channels: 3, background: '#fff' },
|
|
}).webp().toBuffer()
|
|
const last = await sharp({
|
|
create: { width: 1_600, height: 800, channels: 3, background: '#fff' },
|
|
}).webp().toBuffer()
|
|
|
|
const result = await validateBrandKnowledgeUploadedPreviewSlices([
|
|
{ buffer: first, declaredContentType: 'image/webp' },
|
|
{ buffer: last, declaredContentType: 'image/webp' },
|
|
])
|
|
|
|
expect(result.slices.map((slice) => [slice.width, slice.height])).toEqual([
|
|
[1_600, 1_500],
|
|
[1_600, 800],
|
|
])
|
|
})
|
|
|
|
test('accepts one browser preview up to 3000px without treating it as a sliced image', async () => {
|
|
const single = await sharp({
|
|
create: { width: 1_000, height: 3_000, channels: 3, background: '#fff' },
|
|
}).webp().toBuffer()
|
|
|
|
const result = await validateBrandKnowledgeUploadedPreviewSlices([
|
|
{ buffer: single, declaredContentType: 'image/webp' },
|
|
])
|
|
|
|
expect(result.slices.map((slice) => [slice.width, slice.height])).toEqual([[1_000, 3_000]])
|
|
})
|
|
|
|
test('accepts a short equal-height pair created after scaling a wide source image', async () => {
|
|
const first = await sharp({
|
|
create: { width: 1_600, height: 600, channels: 3, background: '#fff' },
|
|
}).webp().toBuffer()
|
|
const last = await sharp({
|
|
create: { width: 1_600, height: 600, channels: 3, background: '#fff' },
|
|
}).webp().toBuffer()
|
|
|
|
const result = await validateBrandKnowledgeUploadedPreviewSlices([
|
|
{ buffer: first, declaredContentType: 'image/webp' },
|
|
{ buffer: last, declaredContentType: 'image/webp' },
|
|
])
|
|
|
|
expect(result.slices.map((slice) => [slice.width, slice.height])).toEqual([
|
|
[1_600, 600],
|
|
[1_600, 600],
|
|
])
|
|
})
|
|
|
|
test('rejects forged or geometrically inconsistent browser slices', async () => {
|
|
await expect(validateBrandKnowledgeUploadedPreviewSlices([
|
|
{ buffer: Buffer.from('not a webp'), declaredContentType: 'image/webp' },
|
|
])).rejects.toThrow('无法解码')
|
|
|
|
const shortFirst = await sharp({
|
|
create: { width: 1_600, height: 1_000, channels: 3, background: '#fff' },
|
|
}).webp().toBuffer()
|
|
const last = await sharp({
|
|
create: { width: 1_600, height: 800, channels: 3, background: '#fff' },
|
|
}).webp().toBuffer()
|
|
await expect(validateBrandKnowledgeUploadedPreviewSlices([
|
|
{ buffer: shortFirst, declaredContentType: 'image/webp' },
|
|
{ buffer: last, declaredContentType: 'image/webp' },
|
|
])).rejects.toThrow('切片高度不一致')
|
|
|
|
await expect(validateBrandKnowledgeUploadedPreviewSlices([
|
|
{ buffer: shortFirst, declaredContentType: 'image/webp' },
|
|
{ buffer: shortFirst, declaredContentType: 'image/webp' },
|
|
])).rejects.toThrow('切片高度不一致')
|
|
})
|
|
})
|