UNPKG

mcp-server-gpt-image

Version:

MCP server for OpenAI GPT Image-1 and Responses API with dual-mode support, real-time streaming, intelligent caching, and automatic image optimization

238 lines 12.2 kB
import { describe, it, expect, vi, beforeEach } from 'vitest'; import sharp from 'sharp'; import { ImageOptimizer } from './image-optimizer'; vi.mock('sharp'); describe('ImageOptimizer', () => { const mockSharpInstance = { metadata: vi.fn(), resize: vi.fn(), jpeg: vi.fn(), webp: vi.fn(), png: vi.fn(), toBuffer: vi.fn(), }; beforeEach(() => { vi.clearAllMocks(); mockSharpInstance.resize.mockReturnValue(mockSharpInstance); mockSharpInstance.jpeg.mockReturnValue(mockSharpInstance); mockSharpInstance.webp.mockReturnValue(mockSharpInstance); mockSharpInstance.png.mockReturnValue(mockSharpInstance); sharp.mockReturnValue(mockSharpInstance); }); describe('optimizeBase64', () => { const validBase64 = 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8/5+hHgAHggJ/PchI7wAAAABJRU5ErkJggg=='; const dataUriBase64 = `data:image/png;base64,${validBase64}`; it('should handle base64 strings with data URI prefix', async () => { const mockMetadata = { hasAlpha: false, density: 72 }; const mockOutputBuffer = Buffer.from('optimized', 'utf-8'); mockSharpInstance.metadata.mockResolvedValue(mockMetadata); mockSharpInstance.toBuffer.mockResolvedValue(mockOutputBuffer); const result = await ImageOptimizer.optimizeBase64(dataUriBase64); expect(sharp).toHaveBeenCalledWith(Buffer.from(validBase64, 'base64')); expect(result).toBe(mockOutputBuffer.toString('base64')); }); it('should handle plain base64 strings', async () => { const mockMetadata = { hasAlpha: false, density: 72 }; const mockOutputBuffer = Buffer.from('optimized', 'utf-8'); mockSharpInstance.metadata.mockResolvedValue(mockMetadata); mockSharpInstance.toBuffer.mockResolvedValue(mockOutputBuffer); const result = await ImageOptimizer.optimizeBase64(validBase64); expect(sharp).toHaveBeenCalledWith(Buffer.from(validBase64, 'base64')); expect(result).toBe(mockOutputBuffer.toString('base64')); }); it('should resize image when maxWidth or maxHeight are provided', async () => { const mockMetadata = { hasAlpha: false, density: 72 }; const mockOutputBuffer = Buffer.from('resized', 'utf-8'); mockSharpInstance.metadata.mockResolvedValue(mockMetadata); mockSharpInstance.toBuffer.mockResolvedValue(mockOutputBuffer); await ImageOptimizer.optimizeBase64(validBase64, { maxWidth: 800, maxHeight: 600 }); expect(mockSharpInstance.resize).toHaveBeenCalledWith({ width: 800, height: 600, fit: 'inside', withoutEnlargement: true, }); }); it('should convert to JPEG format with proper settings', async () => { const mockMetadata = { hasAlpha: false, density: 150 }; const mockOutputBuffer = Buffer.from('jpeg', 'utf-8'); mockSharpInstance.metadata.mockResolvedValue(mockMetadata); mockSharpInstance.toBuffer.mockResolvedValue(mockOutputBuffer); await ImageOptimizer.optimizeBase64(validBase64, { format: 'jpeg', quality: 90 }); expect(mockSharpInstance.jpeg).toHaveBeenCalledWith({ quality: 90, progressive: true, mozjpeg: true, }); }); it('should convert to WebP format with proper settings', async () => { const mockMetadata = { hasAlpha: false, density: 72 }; const mockOutputBuffer = Buffer.from('webp', 'utf-8'); mockSharpInstance.metadata.mockResolvedValue(mockMetadata); mockSharpInstance.toBuffer.mockResolvedValue(mockOutputBuffer); await ImageOptimizer.optimizeBase64(validBase64, { format: 'webp', quality: 85 }); expect(mockSharpInstance.webp).toHaveBeenCalledWith({ quality: 85, effort: 6, lossless: false, }); }); it('should use lossless WebP when quality is 100', async () => { const mockMetadata = { hasAlpha: false, density: 72 }; const mockOutputBuffer = Buffer.from('webp', 'utf-8'); mockSharpInstance.metadata.mockResolvedValue(mockMetadata); mockSharpInstance.toBuffer.mockResolvedValue(mockOutputBuffer); await ImageOptimizer.optimizeBase64(validBase64, { format: 'webp', quality: 100 }); expect(mockSharpInstance.webp).toHaveBeenCalledWith({ quality: 100, effort: 6, lossless: true, }); }); it('should convert to PNG format with proper settings', async () => { const mockMetadata = { hasAlpha: true }; const mockOutputBuffer = Buffer.from('png', 'utf-8'); mockSharpInstance.metadata.mockResolvedValue(mockMetadata); mockSharpInstance.toBuffer.mockResolvedValue(mockOutputBuffer); await ImageOptimizer.optimizeBase64(validBase64, { format: 'png', quality: 95 }); expect(mockSharpInstance.png).toHaveBeenCalledWith({ compressionLevel: 9, progressive: true, palette: true, quality: 95, }); }); it('should return original image on error', async () => { mockSharpInstance.metadata.mockRejectedValue(new Error('Sharp error')); const result = await ImageOptimizer.optimizeBase64(validBase64); expect(result).toBe(validBase64); }); }); describe('optimizeForOutput', () => { const validBase64 = 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8/5+hHgAHggJ/PchI7wAAAABJRU5ErkJggg=='; it('should optimize with format and quality from input', async () => { const mockMetadata = { hasAlpha: false, density: 72 }; const mockOutputBuffer = Buffer.from('optimized', 'utf-8'); mockSharpInstance.metadata.mockResolvedValue(mockMetadata); mockSharpInstance.toBuffer.mockResolvedValue(mockOutputBuffer); const input = { prompt: 'test', format: 'jpeg', quality: 'high', background: 'opaque', }; await ImageOptimizer.optimizeForOutput(validBase64, input); expect(mockSharpInstance.jpeg).toHaveBeenCalled(); }); it('should set dimensions based on size parameter', async () => { const mockMetadata = { hasAlpha: false, density: 72 }; const mockOutputBuffer = Buffer.from('optimized', 'utf-8'); mockSharpInstance.metadata.mockResolvedValue(mockMetadata); mockSharpInstance.toBuffer.mockResolvedValue(mockOutputBuffer); const input = { prompt: 'test', size: '1024x768', }; await ImageOptimizer.optimizeForOutput(validBase64, input); expect(mockSharpInstance.resize).toHaveBeenCalledWith({ width: 1024, height: 768, fit: 'inside', withoutEnlargement: true, }); }); it('should not resize when size is auto', async () => { const mockMetadata = { hasAlpha: false, density: 72 }; const mockOutputBuffer = Buffer.from('optimized', 'utf-8'); mockSharpInstance.metadata.mockResolvedValue(mockMetadata); mockSharpInstance.toBuffer.mockResolvedValue(mockOutputBuffer); const input = { prompt: 'test', size: 'auto', }; await ImageOptimizer.optimizeForOutput(validBase64, input); expect(mockSharpInstance.resize).not.toHaveBeenCalled(); }); it('should use output_compression when provided', async () => { const mockMetadata = { hasAlpha: false, density: 72 }; const mockOutputBuffer = Buffer.from('optimized', 'utf-8'); mockSharpInstance.metadata.mockResolvedValue(mockMetadata); mockSharpInstance.toBuffer.mockResolvedValue(mockOutputBuffer); const input = { prompt: 'test', format: 'jpeg', output_compression: 75, }; await ImageOptimizer.optimizeForOutput(validBase64, input); expect(mockSharpInstance.jpeg).toHaveBeenCalledWith(expect.objectContaining({ quality: 75 })); }); }); describe('optimizeBatch', () => { it('should optimize multiple images in parallel', async () => { const mockMetadata = { hasAlpha: false, density: 72 }; const mockOutputBuffer = Buffer.from('optimized', 'utf-8'); mockSharpInstance.metadata.mockResolvedValue(mockMetadata); mockSharpInstance.toBuffer.mockResolvedValue(mockOutputBuffer); const images = ['image1', 'image2', 'image3']; const input = { prompt: 'test', format: 'jpeg', }; const results = await ImageOptimizer.optimizeBatch(images, input); expect(results).toHaveLength(3); expect(sharp).toHaveBeenCalledTimes(3); }); }); describe('calculateSizeReduction', () => { it('should calculate correct size reduction percentage', async () => { const original = Buffer.from('a'.repeat(1000)).toString('base64'); const optimized = Buffer.from('a'.repeat(600)).toString('base64'); const reduction = await ImageOptimizer.calculateSizeReduction(original, optimized); expect(reduction).toBe(40); }); it('should round to one decimal place', async () => { const original = Buffer.from('a'.repeat(1000)).toString('base64'); const optimized = Buffer.from('a'.repeat(666)).toString('base64'); const reduction = await ImageOptimizer.calculateSizeReduction(original, optimized); expect(reduction).toBe(33.4); }); }); describe('private methods', () => { describe('detectOptimalFormat', () => { it('should return png for images with alpha channel', () => { const mockMetadata = { hasAlpha: true }; const mockOutputBuffer = Buffer.from('png', 'utf-8'); mockSharpInstance.metadata.mockResolvedValue(mockMetadata); mockSharpInstance.toBuffer.mockResolvedValue(mockOutputBuffer); ImageOptimizer.optimizeBase64('test'); const sharpCall = sharp.mock.calls[0]; expect(sharpCall).toBeDefined(); }); }); describe('mapQualityToNumber', () => { const testCases = [ { quality: 'low', expected: 60 }, { quality: 'medium', expected: 80 }, { quality: 'high', expected: 95 }, { quality: 'auto', expected: 85 }, { quality: undefined, expected: 85 }, ]; testCases.forEach(({ quality, expected }) => { it(`should map quality '${quality}' to ${expected}`, async () => { const mockMetadata = { hasAlpha: false, density: 72 }; const mockOutputBuffer = Buffer.from('test', 'utf-8'); mockSharpInstance.metadata.mockResolvedValue(mockMetadata); mockSharpInstance.toBuffer.mockResolvedValue(mockOutputBuffer); const input = { prompt: 'test', format: 'jpeg', quality, }; await ImageOptimizer.optimizeForOutput('test', input); expect(mockSharpInstance.jpeg).toHaveBeenCalledWith(expect.objectContaining({ quality: expected })); }); }); }); }); }); //# sourceMappingURL=image-optimizer.test.js.map