Skip to main content
Glama
dual-png-tools-integration.test.ts16.9 kB
/** * Integration tests for dual background PNG tools */ import { createPalettePngTool } from '../../src/tools/create-palette-png'; import { createGradientPngTool } from '../../src/tools/create-gradient-png'; import { createColorComparisonPngTool } from '../../src/tools/create-color-comparison-png'; import { enhancedFileOutputManager } from '../../src/utils/enhanced-file-output-manager'; import { fileOutputManager } from '../../src/utils/file-output-manager'; import { FileBasedToolResponse, ErrorResponse } from '../../src/types/index'; import { existsSync } from 'fs'; import { promises as fs } from 'fs'; import sharp from 'sharp'; import { testFileManager } from '../helpers/test-file-manager'; describe('Dual PNG Tools Integration', () => { beforeAll(async () => { // Enable file output for this test suite process.env['TEST_ENABLE_FILE_OUTPUT'] = 'true'; process.env['TEST_OUTPUT_DIR'] = './test-output'; // Ensure test output directory exists await testFileManager.ensureTestOutputDir(); await enhancedFileOutputManager.initialize(); await fileOutputManager.initialize(); }); afterAll(async () => { // Cleanup test files try { const shouldCleanup = process.env['TEST_CLEANUP_FILES'] !== 'false'; if (shouldCleanup) { await testFileManager.cleanupTestFiles(); await enhancedFileOutputManager.cleanupVisualizations(true); } } catch (error) { // Ignore cleanup errors in tests } // Reset environment variables delete process.env['TEST_ENABLE_FILE_OUTPUT']; delete process.env['TEST_OUTPUT_DIR']; }, 120000); // 2 minute timeout for cleanup describe('create_palette_png with dual backgrounds', () => { it('should generate dual background PNG files', async () => { const result = (await createPalettePngTool.handler({ palette: ['#FF0000', '#00FF00', '#0000FF'], layout: 'horizontal', resolution: 150, })) as FileBasedToolResponse; expect(result.success).toBe(true); expect(result.data).toHaveProperty('light_file_size'); expect(result.data).toHaveProperty('dark_file_size'); expect(result.data).toHaveProperty('total_file_size'); // With file output enabled, we should get png_files expect(result.visualizations.png_files).toBeDefined(); const pngFiles = result.visualizations.png_files!; expect(pngFiles).toHaveLength(2); const lightFile = pngFiles.find(f => f.filename.includes('light')); const darkFile = pngFiles.find(f => f.filename.includes('dark')); // Check light background file expect(lightFile).toBeDefined(); expect(lightFile!.type).toBe('png'); expect(lightFile!.filename).toMatch(/light/); expect(existsSync(lightFile!.file_path)).toBe(true); // Check dark background file expect(darkFile).toBeDefined(); expect(darkFile!.type).toBe('png'); expect(darkFile!.filename).toMatch(/dark/); expect(existsSync(darkFile!.file_path)).toBe(true); }); it('should handle different layouts', async () => { const layouts = ['horizontal', 'vertical', 'grid', 'circular'] as const; for (const layout of layouts) { const result = (await createPalettePngTool.handler({ palette: ['#FF0000', '#00FF00', '#0000FF', '#FFFF00'], layout, })) as FileBasedToolResponse; expect(result.success).toBe(true); expect(result.data).toHaveProperty('layout', layout); expect(result.visualizations.png_files).toBeDefined(); expect(result.visualizations.png_files).toHaveLength(2); // Verify files exist const pngFiles = result.visualizations.png_files!; const lightFile = pngFiles.find(f => f.filename.includes('light')); const darkFile = pngFiles.find(f => f.filename.includes('dark')); expect(lightFile).toBeDefined(); expect(darkFile).toBeDefined(); expect(existsSync(lightFile!.file_path)).toBe(true); expect(existsSync(darkFile!.file_path)).toBe(true); } }); it('should include accessibility notes for dual backgrounds', async () => { const result = (await createPalettePngTool.handler({ palette: ['#FF0000', '#00FF00'], })) as FileBasedToolResponse; expect(result.success).toBe(true); expect(result.metadata.accessibility_notes).toContain( 'Light background variant optimized for light themes' ); expect(result.metadata.accessibility_notes).toContain( 'Dark background variant optimized for dark themes' ); }); it('should handle large palettes efficiently', async () => { const largePalette = Array.from( { length: 50 }, (_, i) => `hsl(${i * 7}, 70%, 50%)` ); const startTime = Date.now(); const result = (await createPalettePngTool.handler({ palette: largePalette, layout: 'grid', })) as FileBasedToolResponse; const endTime = Date.now(); expect(result.success).toBe(true); expect(endTime - startTime).toBeLessThan(10000); // Should complete within 10 seconds expect(result.data).toHaveProperty('color_count', 50); }); }); describe('create_gradient_png with dual backgrounds', () => { it('should generate dual background gradient PNG files', async () => { const result = (await createGradientPngTool.handler({ gradient: { type: 'linear', colors: ['#FF0000', '#0000FF'], angle: 45, }, dimensions: [400, 200], quality: 'standard', })) as FileBasedToolResponse; expect(result.success).toBe(true); expect(result.data).toHaveProperty('gradient_type', 'linear'); expect(result.data).toHaveProperty('light_file_size'); expect(result.data).toHaveProperty('dark_file_size'); expect(result.visualizations.png_files).toBeDefined(); expect(result.visualizations.png_files).toHaveLength(2); const pngFiles = result.visualizations.png_files!; const lightFile = pngFiles.find(f => f.filename.includes('light')); const darkFile = pngFiles.find(f => f.filename.includes('dark')); expect(lightFile).toBeDefined(); expect(darkFile).toBeDefined(); expect(lightFile!.filename).toMatch(/gradient.*light/); expect(darkFile!.filename).toMatch(/gradient.*dark/); expect(existsSync(lightFile!.file_path)).toBe(true); expect(existsSync(darkFile!.file_path)).toBe(true); }); it('should handle different gradient types', async () => { const gradientTypes = ['linear', 'radial', 'conic'] as const; for (const type of gradientTypes) { const result = (await createGradientPngTool.handler({ gradient: { type, colors: ['#FF0000', '#00FF00', '#0000FF'], }, dimensions: [300, 300], })) as FileBasedToolResponse; expect(result.success).toBe(true); expect(result.data).toHaveProperty('gradient_type', type); expect(result.visualizations.png_files).toBeDefined(); expect(result.visualizations.png_files).toHaveLength(2); } }); it('should handle quality settings', async () => { const qualities = ['draft', 'standard', 'high', 'ultra'] as const; for (const quality of qualities) { const result = (await createGradientPngTool.handler({ gradient: { type: 'linear', colors: ['#FF0000', '#0000FF'], }, dimensions: [200, 200], quality, })) as FileBasedToolResponse; expect(result.success).toBe(true); expect(result.visualizations.png_files).toBeDefined(); expect(result.visualizations.png_files).toHaveLength(2); } }); }); describe('create_color_comparison_png with dual backgrounds', () => { it('should generate dual background comparison PNG files', async () => { const result = (await createColorComparisonPngTool.handler({ color_sets: [ ['#FF0000', '#FF4444', '#FF8888'], ['#0000FF', '#4444FF', '#8888FF'], ], comparison_type: 'side_by_side', })) as FileBasedToolResponse; expect(result.success).toBe(true); expect(result.data).toHaveProperty('comparison_type', 'side_by_side'); expect(result.data).toHaveProperty('color_sets_count', 2); expect(result.data).toHaveProperty('total_colors', 6); expect(result.visualizations.png_files).toBeDefined(); expect(result.visualizations.png_files).toHaveLength(2); const pngFiles = result.visualizations.png_files!; const lightFile = pngFiles.find(f => f.filename.includes('light')); const darkFile = pngFiles.find(f => f.filename.includes('dark')); expect(lightFile).toBeDefined(); expect(darkFile).toBeDefined(); expect(lightFile!.filename).toMatch(/comparison.*light/); expect(darkFile!.filename).toMatch(/comparison.*dark/); expect(existsSync(lightFile!.file_path)).toBe(true); expect(existsSync(darkFile!.file_path)).toBe(true); }); it('should handle different comparison types', async () => { const comparisonTypes = [ 'side_by_side', 'overlay', 'difference', 'harmony', ] as const; for (const comparisonType of comparisonTypes) { const result = (await createColorComparisonPngTool.handler({ color_sets: [ ['#FF0000', '#00FF00'], ['#0000FF', '#FFFF00'], ], comparison_type: comparisonType, })) as FileBasedToolResponse; expect(result.success).toBe(true); expect(result.data).toHaveProperty('comparison_type', comparisonType); expect(result.visualizations.png_files).toBeDefined(); expect(result.visualizations.png_files).toHaveLength(2); } }); }); describe('file naming consistency', () => { it('should generate consistent file names with background variants', async () => { const result = (await createPalettePngTool.handler({ palette: ['#FF0000', '#00FF00'], })) as FileBasedToolResponse; expect(result.success).toBe(true); const pngFiles = result.visualizations.png_files!; const lightFile = pngFiles.find(f => f.filename.includes('light')); const darkFile = pngFiles.find(f => f.filename.includes('dark')); expect(lightFile).toBeDefined(); expect(darkFile).toBeDefined(); // Both files should have similar base names but different variants // Extract the base name by removing the variant suffix and timestamp/id const lightBaseName = lightFile!.filename.replace(/-light\.png$/, ''); const darkBaseName = darkFile!.filename.replace(/-dark\.png$/, ''); expect(lightBaseName).toBe(darkBaseName); expect(lightFile!.filename).toMatch(/-light\.png$/); expect(darkFile!.filename).toMatch(/-dark\.png$/); }); it('should include tool name in file names', async () => { const paletteResult = (await createPalettePngTool.handler({ palette: ['#FF0000'], })) as FileBasedToolResponse; const gradientResult = (await createGradientPngTool.handler({ gradient: { type: 'linear', colors: ['#FF0000', '#0000FF'] }, dimensions: [100, 100], })) as FileBasedToolResponse; expect(paletteResult.visualizations.png_files?.[0]?.filename).toMatch( /palette/ ); expect(gradientResult.visualizations.png_files?.[0]?.filename).toMatch( /gradient/ ); }); }); describe('visual quality validation', () => { it('should generate visually consistent PNG files', async () => { const result = (await createPalettePngTool.handler({ palette: ['#FF0000', '#00FF00', '#0000FF'], layout: 'horizontal', dimensions: [300, 100], })) as FileBasedToolResponse; expect(result.success).toBe(true); const pngFiles = result.visualizations.png_files!; const lightFile = pngFiles.find(f => f.filename.includes('light')); const darkFile = pngFiles.find(f => f.filename.includes('dark')); expect(lightFile).toBeDefined(); expect(darkFile).toBeDefined(); // Read and validate both files const lightBuffer = await fs.readFile(lightFile!.file_path); const darkBuffer = await fs.readFile(darkFile!.file_path); const lightMeta = await sharp(lightBuffer).metadata(); const darkMeta = await sharp(darkBuffer).metadata(); // Both should have same dimensions expect(lightMeta.width).toBe(darkMeta.width); expect(lightMeta.height).toBe(darkMeta.height); expect(lightMeta.channels).toBe(darkMeta.channels); // Both should be reasonable file sizes expect(lightBuffer.length).toBeGreaterThan(500); // At least 500 bytes expect(darkBuffer.length).toBeGreaterThan(500); // At least 500 bytes expect(lightBuffer.length).toBeLessThan(10 * 1024 * 1024); // Less than 10MB expect(darkBuffer.length).toBeLessThan(10 * 1024 * 1024); // Less than 10MB }); }); describe('error handling', () => { it('should handle invalid colors gracefully', async () => { const result = (await createPalettePngTool.handler({ palette: ['invalid-color', '#FF0000'], })) as ErrorResponse; expect(result.success).toBe(false); expect(result.error.code).toBe('INVALID_COLOR_FORMAT'); expect(result.error.message).toContain('Invalid colors found'); }); it('should handle oversized dimensions', async () => { const result = (await createGradientPngTool.handler({ gradient: { type: 'linear', colors: ['#FF0000', '#0000FF'], }, dimensions: [50000, 50000], // Too large })) as ErrorResponse; expect(result.success).toBe(false); expect(result.error.code).toBe('INVALID_PARAMETERS'); }); it('should handle file system errors gracefully', async () => { // This test would require mocking the file system to simulate errors // For now, we'll just ensure the tools handle the happy path correctly const result = (await createPalettePngTool.handler({ palette: ['#FF0000'], })) as FileBasedToolResponse; expect(result.success).toBe(true); }); }); describe('performance benchmarks', () => { it('should generate small palettes quickly', async () => { const startTime = Date.now(); const result = (await createPalettePngTool.handler({ palette: ['#FF0000', '#00FF00', '#0000FF'], })) as FileBasedToolResponse; const endTime = Date.now(); expect(result.success).toBe(true); expect(endTime - startTime).toBeLessThan(5000); // Should complete within 5 seconds }); it('should handle concurrent requests', async () => { const requests = Array.from({ length: 3 }, (_, i) => createPalettePngTool.handler({ palette: [`hsl(${i * 120}, 100%, 50%)`, '#FFFFFF'], }) ); const results = await Promise.all(requests); results.forEach(result => { expect((result as FileBasedToolResponse).success).toBe(true); }); }); }); describe('metadata and response format', () => { it('should return correct metadata structure', async () => { const result = (await createPalettePngTool.handler({ palette: ['#FF0000', '#00FF00'], })) as FileBasedToolResponse; expect(result.success).toBe(true); expect(result.metadata).toHaveProperty('execution_time'); expect(result.metadata).toHaveProperty('tool', 'create_palette_png'); expect(result.metadata).toHaveProperty('timestamp'); expect(result.metadata).toHaveProperty('color_space_used', 'sRGB'); expect(result.metadata).toHaveProperty('accessibility_notes'); expect(result.metadata).toHaveProperty('recommendations'); }); it('should include file metadata in response', async () => { const result = (await createPalettePngTool.handler({ palette: ['#FF0000'], })) as FileBasedToolResponse; expect(result.success).toBe(true); const [lightFile, darkFile] = result.visualizations.png_files!; expect(lightFile).toHaveProperty('file_path'); expect(lightFile).toHaveProperty('filename'); expect(lightFile).toHaveProperty('size'); expect(lightFile).toHaveProperty('created_at'); expect(lightFile).toHaveProperty('type', 'png'); expect(lightFile).toHaveProperty('description'); expect(darkFile).toHaveProperty('file_path'); expect(darkFile).toHaveProperty('filename'); expect(darkFile).toHaveProperty('size'); expect(darkFile).toHaveProperty('created_at'); expect(darkFile).toHaveProperty('type', 'png'); expect(darkFile).toHaveProperty('description'); }); }); });

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/keyurgolani/ColorMcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server