/** * Pure-function tests for the logo sharp pipeline (no DB / storage). * The `setPortLogo` write-path is exercised by integration tests + Playwright. */ import { describe, expect, it } from 'vitest'; import sharp from 'sharp'; import { processLogoUpload } from '@/lib/services/logo.service'; async function makePng( w: number, h: number, color: { r: number; g: number; b: number; alpha: number } = { r: 255, g: 0, b: 0, alpha: 1 }, ) { return sharp({ create: { width: w, height: h, channels: 4, background: color } }) .png() .toBuffer(); } describe('processLogoUpload - sharp pipeline', () => { it('accepts a healthy PNG with alpha and resizes if needed', async () => { const buf = await makePng(2400, 800); const result = await processLogoUpload(buf); expect(result.originalFormat).toBe('png'); expect(result.finalDimensions.width).toBeLessThanOrEqual(1200); expect(result.finalDimensions.height).toBeLessThanOrEqual(1200); expect(result.pngBuffer.subarray(1, 4).toString('ascii')).toBe('PNG'); expect(result.warnings).toContain('trimmed'); expect(result.warnings).toContain('resized'); }); it('rejects undersized images', async () => { const buf = await makePng(100, 100); await expect(processLogoUpload(buf)).rejects.toThrow(/too small/i); }); it('rejects empty buffers', async () => { await expect(processLogoUpload(Buffer.alloc(0))).rejects.toThrow(/empty/i); }); it('rejects buffers that exceed the raw size cap', async () => { // 6 MB of zero bytes - fails the raw size cap before sharp parses. const buf = Buffer.alloc(6 * 1024 * 1024); await expect(processLogoUpload(buf)).rejects.toThrow(/MB/i); }); it('rejects non-image bytes', async () => { const buf = Buffer.from('this is not an image at all'); await expect(processLogoUpload(buf)).rejects.toThrow(/supported image format/i); }); it('rejects out-of-bounds crop coordinates', async () => { const buf = await makePng(800, 800); await expect(processLogoUpload(buf, { x: 0, y: 0, width: 1000, height: 1000 })).rejects.toThrow( /out of image bounds/i, ); }); it('accepts an in-bounds crop', async () => { const buf = await makePng(800, 800); const result = await processLogoUpload(buf, { x: 100, y: 100, width: 500, height: 500 }); expect(result.finalDimensions.width).toBeGreaterThan(0); }); it('rasterizes SVG input to PNG', async () => { const svg = ` `; const buf = Buffer.from(svg, 'utf8'); const result = await processLogoUpload(buf); expect(result.originalFormat).toBe('svg'); expect(result.warnings).toContain('svg-rasterized'); // Output is PNG even though input was SVG. expect(result.pngBuffer.subarray(1, 4).toString('ascii')).toBe('PNG'); }); it('rejects SVG with embedded script', async () => { const svg = ` `; const buf = Buffer.from(svg, 'utf8'); await expect(processLogoUpload(buf)).rejects.toThrow(/disallowed nodes/i); }); it('rejects SVG with external href', async () => { const svg = ` `; const buf = Buffer.from(svg, 'utf8'); await expect(processLogoUpload(buf)).rejects.toThrow(/disallowed nodes/i); }); it('flags JPEG sources with no alpha', async () => { const buf = await sharp({ create: { width: 1200, height: 1200, channels: 3, background: { r: 200, g: 50, b: 50 } }, }) .jpeg({ quality: 80 }) .toBuffer(); const result = await processLogoUpload(buf); expect(result.warnings).toContain('jpeg-source'); expect(result.warnings).toContain('no-alpha'); }); });