Skip to main content
Glama
edge-cases.test.ts22.2 kB
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'; import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { Config } from '../../src/config/index.js'; import { OpenRouterClient } from '../../src/utils/openrouter-client.js'; import { ImageProcessor } from '../../src/utils/image-processor.js'; import { Logger } from '../../src/utils/logger.js'; import { TestHelpers } from '../utils/test-helpers.js'; describe('Edge Cases and Error Scenarios', () => { let mockServer: Server; let mockConfig: any; let mockOpenRouterClient: any; let mockImageProcessor: any; let mockLogger: any; beforeEach(() => { vi.clearAllMocks(); // Create comprehensive mocks mockServer = { setRequestHandler: vi.fn(), close: vi.fn().mockResolvedValue(undefined), onerror: vi.fn(), } as any; mockConfig = { getOpenRouterConfig: vi.fn(() => ({ apiKey: 'test-api-key', model: 'test-model', baseUrl: 'https://test.api.com', })), getServerConfig: vi.fn(() => ({ maxImageSize: 10485760, // 10MB })), }; mockOpenRouterClient = { testConnection: vi.fn(), validateModel: vi.fn(), analyzeImage: vi.fn(), }; mockImageProcessor = { processImage: vi.fn(), isValidImageType: vi.fn(), }; mockLogger = { getInstance: vi.fn(() => ({ debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn(), })), }; // Setup mock returns vi.mocked(Config.getInstance).mockReturnValue(mockConfig); vi.mocked(OpenRouterClient.getInstance).mockReturnValue(mockOpenRouterClient); vi.mocked(ImageProcessor.getInstance).mockReturnValue(mockImageProcessor); vi.mocked(Logger.getInstance).mockReturnValue(mockLogger.getInstance()); }); afterEach(() => { vi.resetAllMocks(); }); describe('Image Processing Edge Cases', () => { it('should handle empty base64 strings', async () => { mockImageProcessor.processImage.mockRejectedValue(new Error('Empty image data')); const { registerAnalyzeImageTool } = await import('../../src/tools/analyze-image.js'); registerAnalyzeImageTool(mockServer); const toolHandler = mockServer.setRequestHandler.mock.calls[0][1]; const request = TestHelpers.createMockToolRequest('analyze_image', { type: 'base64', data: '', mimeType: 'image/png', }); const result = await toolHandler(request); expect(result.isError).toBe(true); expect(result.content[0].text).toContain('Error:'); }); it('should handle extremely large images', async () => { const largeImageData = 'x'.repeat(50 * 1024 * 1024); // 50MB of data mockImageProcessor.processImage.mockResolvedValue({ data: largeImageData, mimeType: 'image/png', size: 50 * 1024 * 1024, }); const { registerAnalyzeImageTool } = await import('../../src/tools/analyze-image.js'); registerAnalyzeImageTool(mockServer); const toolHandler = mockServer.setRequestHandler.mock.calls[0][1]; const request = TestHelpers.createMockToolRequest('analyze_image', { type: 'base64', data: largeImageData, mimeType: 'image/png', }); const result = await toolHandler(request); expect(result.isError).toBe(true); expect(result.content[0].text).toContain('exceeds maximum allowed size'); }); it('should handle corrupted image data', async () => { mockImageProcessor.processImage.mockRejectedValue(new Error('Invalid image data')); const { registerAnalyzeImageTool } = await import('../../src/tools/analyze-image.js'); registerAnalyzeImageTool(mockServer); const toolHandler = mockServer.setRequestHandler.mock.calls[0][1]; const request = TestHelpers.createMockToolRequest('analyze_image', { type: 'base64', data: 'corrupted_base64_data_!@#$%^&*()', }); const result = await toolHandler(request); expect(result.isError).toBe(true); expect(result.content[0].text).toContain('Error:'); }); it('should handle non-existent file paths', async () => { const fileError = new Error('ENOENT: no such file or directory'); mockImageProcessor.processImage.mockRejectedValue(fileError); const { registerAnalyzeImageTool } = await import('../../src/tools/analyze-image.js'); registerAnalyzeImageTool(mockServer); const toolHandler = mockServer.setRequestHandler.mock.calls[0][1]; const request = TestHelpers.createMockToolRequest('analyze_image', { type: 'file', data: '/non/existent/path/image.jpg', }); const result = await toolHandler(request); expect(result.isError).toBe(true); expect(result.content[0].text).toContain('Error:'); }); it('should handle invalid URLs', async () => { const urlError = new Error('Invalid URL format'); mockImageProcessor.processImage.mockRejectedValue(urlError); const { registerAnalyzeImageTool } = await import('../../src/tools/analyze-image.js'); registerAnalyzeImageTool(mockServer); const toolHandler = mockServer.setRequestHandler.mock.calls[0][1]; const request = TestHelpers.createMockToolRequest('analyze_image', { type: 'url', data: 'not-a-valid-url', }); const result = await toolHandler(request); expect(result.isError).toBe(true); expect(result.content[0].text).toContain('Error:'); }); it('should handle network timeouts for URL images', async () => { const timeoutError = new Error('Network timeout after 30 seconds'); mockImageProcessor.processImage.mockRejectedValue(timeoutError); const { registerAnalyzeImageTool } = await import('../../src/tools/analyze-image.js'); registerAnalyzeImageTool(mockServer); const toolHandler = mockServer.setRequestHandler.mock.calls[0][1]; const request = TestHelpers.createMockToolRequest('analyze_image', { type: 'url', data: 'https://example.com/slow-image.jpg', }); const result = await toolHandler(request); expect(result.isError).toBe(true); expect(result.content[0].text).toContain('Error:'); }); it('should handle unsupported image formats', async () => { mockImageProcessor.processImage.mockResolvedValue({ data: 'supporteddatabutwrongtype', mimeType: 'application/pdf', // Unsupported format size: 1000, }); mockImageProcessor.isValidImageType.mockReturnValue(false); const { registerAnalyzeImageTool } = await import('../../src/tools/analyze-image.js'); registerAnalyzeImageTool(mockServer); const toolHandler = mockServer.setRequestHandler.mock.calls[0][1]; const request = TestHelpers.createMockToolRequest('analyze_image', { type: 'base64', data: TestHelpers.SAMPLE_IMAGES.MINIMAL_JPEG, mimeType: 'application/pdf', }); const result = await toolHandler(request); expect(result.isError).toBe(true); expect(result.content[0].text).toContain('Unsupported image type'); }); }); describe('API Communication Edge Cases', () => { it('should handle API authentication failures', async () => { mockImageProcessor.processImage.mockResolvedValue(TestHelpers.createMockProcessedImage()); mockOpenRouterClient.analyzeImage.mockResolvedValue({ success: false, error: 'Authentication failed: Invalid API key', }); const { registerAnalyzeImageTool } = await import('../../src/tools/analyze-image.js'); registerAnalyzeImageTool(mockServer); const toolHandler = mockServer.setRequestHandler.mock.calls[0][1]; const request = TestHelpers.createMockToolRequest('analyze_image', TestHelpers.createMockImageInput()); const result = await toolHandler(request); expect(result.isError).toBe(true); expect(result.content[0].text).toContain('Authentication failed'); }); it('should handle API rate limiting', async () => { mockImageProcessor.processImage.mockResolvedValue(TestHelpers.createMockProcessedImage()); mockOpenRouterClient.analyzeImage.mockResolvedValue({ success: false, error: 'Rate limit exceeded. Please try again later.', }); const { registerAnalyzeImageTool } = await import('../../src/tools/analyze-image.js'); registerAnalyzeImageTool(mockServer); const toolHandler = mockServer.setRequestHandler.mock.calls[0][1]; const request = TestHelpers.createMockToolRequest('analyze_image', TestHelpers.createMockImageInput()); const result = await toolHandler(request); expect(result.isError).toBe(true); expect(result.content[0].text).toContain('Rate limit exceeded'); }); it('should handle API model unavailable', async () => { mockImageProcessor.processImage.mockResolvedValue(TestHelpers.createMockProcessedImage()); mockOpenRouterClient.analyzeImage.mockResolvedValue({ success: false, error: 'Model "test-model" is currently unavailable', }); const { registerAnalyzeImageTool } = await import('../../src/tools/analyze-image.js'); registerAnalyzeImageTool(mockServer); const toolHandler = mockServer.setRequestHandler.mock.calls[0][1]; const request = TestHelpers.createMockToolRequest('analyze_image', TestHelpers.createMockImageInput()); const result = await toolHandler(request); expect(result.isError).toBe(true); expect(result.content[0].text).toContain('unavailable'); }); it('should handle API timeout errors', async () => { mockImageProcessor.processImage.mockResolvedValue(TestHelpers.createMockProcessedImage()); mockOpenRouterClient.analyzeImage.mockResolvedValue({ success: false, error: 'Request timeout after 60 seconds', }); const { registerAnalyzeImageTool } = await import('../../src/tools/analyze-image.js'); registerAnalyzeImageTool(mockServer); const toolHandler = mockServer.setRequestHandler.mock.calls[0][1]; const request = TestHelpers.createMockToolRequest('analyze_image', TestHelpers.createMockImageInput()); const result = await toolHandler(request); expect(result.isError).toBe(true); expect(result.content[0].text).toContain('timeout'); }); it('should handle API response parsing errors', async () => { mockImageProcessor.processImage.mockResolvedValue(TestHelpers.createMockProcessedImage()); mockOpenRouterClient.analyzeImage.mockResolvedValue({ success: true, analysis: undefined, // Missing analysis }); const { registerAnalyzeImageTool } = await import('../../src/tools/analyze-image.js'); registerAnalyzeImageTool(mockServer); const toolHandler = mockServer.setRequestHandler.mock.calls[0][1]; const request = TestHelpers.createMockToolRequest('analyze_image', TestHelpers.createMockImageInput()); const result = await toolHandler(request); expect(result.isError).toBeUndefined(); // Should handle gracefully expect(result.content[0].text).toBe('No analysis available'); }); }); describe('Input Validation Edge Cases', () => { it('should handle missing required parameters', async () => { const { registerAnalyzeImageTool } = await import('../../src/tools/analyze-image.js'); registerAnalyzeImageTool(mockServer); const toolHandler = mockServer.setRequestHandler.mock.calls[0][1]; // Test missing type const request1 = TestHelpers.createMockToolRequest('analyze_image', { data: 'somedata', }); await expect(toolHandler(request1)).rejects.toThrow(); // Test missing data const request2 = TestHelpers.createMockToolRequest('analyze_image', { type: 'base64', }); await expect(toolHandler(request2)).rejects.toThrow(); // Test completely empty arguments const request3 = { params: { name: 'analyze_image' }, }; await expect(toolHandler(request3)).rejects.toThrow('Arguments are required'); }); it('should handle invalid parameter types', async () => { const { registerAnalyzeImageTool } = await import('../../src/tools/analyze-image.js'); registerAnalyzeImageTool(mockServer); const toolHandler = mockServer.setRequestHandler.mock.calls[0][1]; // Test invalid type const request1 = TestHelpers.createMockToolRequest('analyze_image', { type: 'invalid_type', data: 'somedata', }); // Should not throw immediately but will be handled in processing await expect(toolHandler(request1)).resolves.not.toThrow(); // Test invalid format const request2 = TestHelpers.createMockToolRequest('analyze_image', { type: 'base64', data: 'somedata', format: 'invalid_format', }); await expect(toolHandler(request2)).resolves.not.toThrow(); // Test invalid temperature const request3 = TestHelpers.createMockToolRequest('analyze_image', { type: 'base64', data: 'somedata', temperature: 5.0, // Exceeds maximum of 2.0 }); await expect(toolHandler(request3)).resolves.not.toThrow(); }); it('should handle null and undefined values', async () => { const { registerAnalyzeImageTool } = await import('../../src/tools/analyze-image.js'); registerAnalyzeImageTool(mockServer); const toolHandler = mockServer.setRequestHandler.mock.calls[0][1]; // Test null values const request1 = TestHelpers.createMockToolRequest('analyze_image', { type: null, data: null, mimeType: null, }); await expect(toolHandler(request1)).rejects.toThrow(); // Test undefined values const request2 = TestHelpers.createMockToolRequest('analyze_image', { type: undefined, data: undefined, }); await expect(toolHandler(request2)).rejects.toThrow(); }); it('should handle extremely long parameter values', async () => { const longPrompt = 'x'.repeat(100000); // 100KB prompt mockImageProcessor.processImage.mockResolvedValue(TestHelpers.createMockProcessedImage()); mockOpenRouterClient.analyzeImage.mockResolvedValue(TestHelpers.createMockAnalysisResult()); const { registerAnalyzeImageTool } = await import('../../src/tools/analyze-image.js'); registerAnalyzeImageTool(mockServer); const toolHandler = mockServer.setRequestHandler.mock.calls[0][1]; const request = TestHelpers.createMockToolRequest('analyze_image', { type: 'base64', data: TestHelpers.SAMPLE_IMAGES.MINIMAL_JPEG, prompt: longPrompt, }); const result = await toolHandler(request); expect(mockOpenRouterClient.analyzeImage).toHaveBeenCalledWith( expect.any(String), expect.any(String), longPrompt, expect.any(Object) ); }); }); describe('Memory and Resource Management', () => { it('should handle memory pressure scenarios', async () => { // Simulate memory pressure by processing many large images const largeImageData = 'x'.repeat(1024 * 1024); // 1MB per image const concurrentRequests = 10; mockImageProcessor.processImage.mockResolvedValue({ data: largeImageData, mimeType: 'image/png', size: 1024 * 1024, }); mockOpenRouterClient.analyzeImage.mockResolvedValue(TestHelpers.createMockAnalysisResult()); const { registerAnalyzeImageTool } = await import('../../src/tools/analyze-image.js'); registerAnalyzeImageTool(mockServer); const toolHandler = mockServer.setRequestHandler.mock.calls[0][1]; const requests = Array(concurrentRequests).fill(null).map((_, index) => TestHelpers.createMockToolRequest('analyze_image', { type: 'base64', data: largeImageData, prompt: `Test request ${index}`, }) ); const results = await Promise.all(requests.map(req => toolHandler(req))); expect(results).toHaveLength(concurrentRequests); results.forEach(result => { expect(result.isError).toBeUndefined(); expect(result.content[0].text).toContain('Mock analysis result'); }); }); it('should handle rapid successive requests', async () => { mockImageProcessor.processImage.mockResolvedValue(TestHelpers.createMockProcessedImage()); mockOpenRouterClient.analyzeImage.mockResolvedValue(TestHelpers.createMockAnalysisResult()); const { registerAnalyzeImageTool } = await import('../../src/tools/analyze-image.js'); registerAnalyzeImageTool(mockServer); const toolHandler = mockServer.setRequestHandler.mock.calls[0][1]; // Send 100 rapid requests const requests = Array(100).fill(null).map((_, index) => toolHandler(TestHelpers.createMockToolRequest('analyze_image', { type: 'base64', data: TestHelpers.SAMPLE_IMAGES.MINIMAL_JPEG, prompt: `Rapid request ${index}`, })) ); const results = await Promise.all(requests); expect(results).toHaveLength(100); expect(mockOpenRouterClient.analyzeImage).toHaveBeenCalledTimes(100); }); }); describe('Configuration Edge Cases', () => { it('should handle missing configuration values', async () => { // Test with missing API key const invalidConfig = { getOpenRouterConfig: vi.fn(() => ({ apiKey: '', // Empty API key model: 'test-model', })), getServerConfig: vi.fn(() => ({ maxImageSize: 10485760, })), }; vi.mocked(Config.getInstance).mockReturnValue(invalidConfig); expect(() => { Config.getInstance(); }).not.toThrow(); // Should handle gracefully }); it('should handle invalid configuration values', async () => { const invalidConfig = { getOpenRouterConfig: vi.fn(() => ({ apiKey: 'invalid-key', model: '', // Empty model baseUrl: 'invalid-url', })), getServerConfig: vi.fn(() => ({ maxImageSize: -1, // Invalid size logLevel: 'invalid-level', })), }; vi.mocked(Config.getInstance).mockReturnValue(invalidConfig); expect(() => { Config.getInstance(); }).not.toThrow(); }); }); describe('Network and Connectivity Issues', () => { it('should handle DNS resolution failures', async () => { mockImageProcessor.processImage.mockRejectedValue( new Error('getaddrinfo ENOTFOUND invalid-domain.com') ); const { registerAnalyzeImageTool } = await import('../../src/tools/analyze-image.js'); registerAnalyzeImageTool(mockServer); const toolHandler = mockServer.setRequestHandler.mock.calls[0][1]; const request = TestHelpers.createMockToolRequest('analyze_image', { type: 'url', data: 'https://invalid-domain.com/image.jpg', }); const result = await toolHandler(request); expect(result.isError).toBe(true); expect(result.content[0].text).toContain('Error:'); }); it('should handle SSL/TLS certificate errors', async () => { mockImageProcessor.processImage.mockRejectedValue( new Error('self-signed certificate') ); const { registerAnalyzeImageTool } = await import('../../src/tools/analyze-image.js'); registerAnalyzeImageTool(mockServer); const toolHandler = mockServer.setRequestHandler.mock.calls[0][1]; const request = TestHelpers.createMockToolRequest('analyze_image', { type: 'url', data: 'https://expired-certificate.com/image.jpg', }); const result = await toolHandler(request); expect(result.isError).toBe(true); expect(result.content[0].text).toContain('Error:'); }); it('should handle connection refused errors', async () => { mockImageProcessor.processImage.mockRejectedValue( new Error('ECONNREFUSED: Connection refused') ); const { registerAnalyzeImageTool } = await import('../../src/tools/analyze-image.js'); registerAnalyzeImageTool(mockServer); const toolHandler = mockServer.setRequestHandler.mock.calls[0][1]; const request = TestHelpers.createMockToolRequest('analyze_image', { type: 'url', data: 'https://localhost:9999/image.jpg', }); const result = await toolHandler(request); expect(result.isError).toBe(true); expect(result.content[0].text).toContain('Error:'); }); }); describe('Concurrent Operation Edge Cases', () => { it('should handle concurrent operations on shared resources', async () => { let processingCount = 0; const maxConcurrentProcessing = 5; mockImageProcessor.processImage.mockImplementation(async () => { processingCount++; expect(processingCount).toBeLessThanOrEqual(maxConcurrentProcessing); // Simulate processing time await new Promise(resolve => setTimeout(resolve, 100)); processingCount--; return TestHelpers.createMockProcessedImage(); }); mockOpenRouterClient.analyzeImage.mockResolvedValue(TestHelpers.createMockAnalysisResult()); const { registerAnalyzeImageTool } = await import('../../src/tools/analyze-image.js'); registerAnalyzeImageTool(mockServer); const toolHandler = mockServer.setRequestHandler.mock.calls[0][1]; // Launch concurrent requests const requests = Array(maxConcurrentProcessing).fill(null).map((_, index) => toolHandler(TestHelpers.createMockToolRequest('analyze_image', { type: 'base64', data: TestHelpers.SAMPLE_IMAGES.MINIMAL_JPEG, prompt: `Concurrent test ${index}`, })) ); const results = await Promise.all(requests); expect(results).toHaveLength(maxConcurrentProcessing); expect(processingCount).toBe(0); // All should be completed }); }); });

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/JonathanJude/openrouter-image-mcp'

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